掌握這套精編Android 高級面試題解析,媽媽再也不擔心我找工作了

閱讀前請點擊右上角“關注”,每天免費獲取Android知識解析及面試解答。Android架構解析,只做職場乾貨,完全免費分享!


掌握這套精編Android 高級面試題解析,媽媽再也不擔心我找工作了


1.說下你所知道的設計模式與使用場景

建造者模式:觀察者模式:代理模式:門面模式:單例模式:生產者消費者模式:

2.Java語言的特點與OOP思想

這個通過對比來描述,比如面向對象和麵向過程的對比,針對這兩種思想的對比,還可以舉個開發中的例子,比如播放器的實現,面向過程的實現方式就是將播放視頻的這個功能分解成多個過程,比如,加載視頻地址,獲取視頻信息,初始化解碼器,選擇合適的解碼器進行解碼,讀取解碼後的幀進行視頻格式轉換和音頻重採樣,然後讀取幀進行播放,這是一個完整的過程,這個過程中不涉及類的概念,而面向對象最大的特點就是類,封裝繼承和多態是核心,同樣的以播放器為例,一面向對象的方式來實現,將會針對每一個功能封裝出一個對象,每一個功能對應一個對象,由這個對象來完成對應的功能,並且遵循單一職責原則,一個對象只做它相關的事情

3.說下java中的線程創建方式,線程池的工作原理。

Java中有三種創建線程的方式,或者說四種

1.繼承Thread類實現多線程2.實現Runnable接口3.實現Callable接口4.通過線程池

線程池的工作原理:

線程池可以減少創建和銷燬線程的次數,從而減少系統資源的消耗,當一個任務提交到線程池時.a. 首先判斷核心線程池中的線程是否已經滿了,如果沒滿,則創建一個核心線程執行任務,否則進入下一步.b. 判斷工作隊列是否已滿,沒有滿則加入工作隊列,否則執行下一步.c. 判斷線程數是否達到了最大值,如果不是,則創建非核心線程執行任務,否則執行飽和策略,默認拋出異常.

4.說下handler原理

Handler,Message,looper和MessageQueue構成了安卓的消息機制,handler創建後可以通過sendMessage將消息加入消息隊列,然後looper不斷的將消息從MessageQueue中取出來,回調到Hander的handleMessage方法,從而實現線程的通信。

從兩種情況來說,第一在UI線程創建Handler,此時我們不需要手動開啟looper,因為在應用啟動時,在ActivityThread的main方法中就創建了一個當前主線程的looper,並開啟了消息隊列,消息隊列是一個無限循環,為什麼無限循環不會ANR?因為可以說,應用的整個生命週期就是運行在這個消息循環中的,安卓是由事件驅動的,Looper.loop不斷的接收處理事件,每一個點擊觸摸或者Activity每一個生命週期都是在Looper.loop的控制之下的,looper.loop一旦結束,應用程序的生命週期也就結束了。我們可以想想什麼情況下會發生ANR?第一,事件沒有得到處理第二,事件正在處理,但是沒有及時完成,而對事件進行處理的就是looper,所以只能說事件的處理如果阻塞會導致ANR,而不能說looper的無限循環會ANR另一種情況就是在子線程創建Handler,此時由於這個線程中沒有默認開啟的消息隊列,所以我們需要手動調用looper.prepare(),並通過looper.loop開啟消息主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,並且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環並不會對CPU性能有過多的消耗。

5.內存洩漏的場景和解決辦法

1.非靜態內部類的靜態實例

非靜態內部類會持有外部類的引用,如果非靜態內部類的實例是靜態的,就會長期的維持著外部類的引用,組織被系統回收,解決辦法是使用靜態內部類

2.多線程相關的匿名內部類和非靜態內部類

匿名內部類同樣會持有外部類的引用,如果在線程中執行耗時操作就有可能發生內存洩漏,導致外部類無法被回收,直到耗時任務結束,解決辦法是在頁面退出時結束線程中的任務

3.Handler內存洩漏

Handler導致的內存洩漏也可以被歸納為非靜態內部類導致的,Handler內部message是被存儲在MessageQueue中的,有些message不能馬上被處理,存在的時間會很長,導致handler無法被回收,如果handler是非靜態的,就會導致它的外部類無法被回收,解決辦法是1.使用靜態handler,外部類引用使用弱引用處理2.在退出頁面時移除消息隊列中的消息

4.Context導致內存洩漏

根據場景確定使用Activity的Context還是Application的Context,因為二者生命週期不同,對於不必須使用Activity的Context的場景(Dialog),一律採用Application的Context,單例模式是最常見的發生此洩漏的場景,比如傳入一個Activity的Context被靜態類引用,導致無法回收

5.靜態View導致洩漏

使用靜態View可以避免每次啟動Activity都去讀取並渲染View,但是靜態View會持有Activity的引用,導致無法回收解決辦法是在Activity銷燬的時候將靜態View設置為null(View一旦被加載到界面中將會持有一個Context對象的引用,在這個例子中,這個context對象是我們的Activity,聲明一個靜態變量引用這個View,也就引用了activity)

6.WebView導致的內存洩漏

WebView只要使用一次,內存就不會被釋放,所以WebView都存在內存洩漏的問題,通常的解決辦法是為WebView單開一個進程,使用AIDL進行通信,根據業務需求在合適的時機釋放掉

7.資源對象未關閉導致

如Cursor,File等,內部往往都使用了緩衝,會造成內存洩漏,一定要確保關閉它並將引用置為null

8.集合中的對象未清理

集合用於保存對象,如果集合越來越大,不進行合理的清理,尤其是入股集合是靜態的

9.Bitmap導致內存洩漏

bitmap是比較佔內存的,所以一定要在不使用的時候及時進行清理,避免靜態變量持有大的bitmap對象

10.監聽器未關閉

很多需要register和unregister的系統服務要在合適的時候進行unregister,手動添加的listener也需要及時移除

6.如何避免OOM?

1.使用更加輕量的數據結構:

如使用ArrayMap/SparseArray替代HashMap,HashMap更耗內存,因為它需要額外的實例對象來記錄Mapping操作,SparseArray更加高效,因為它避免了Key Value的自動裝箱,和裝箱後的解箱操作

2.枚舉的使用

可以用靜態常量或者註解@IntDef替代

3.Bitmap優化:

a.尺寸壓縮:通過InSampleSize設置合適的縮放

b.顏色質量:設置合適的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差異.

c.inBitmap:使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的內存區域,新解碼的Bitmap會嘗試去使用之前那張Bitmap在Heap中所佔據的pixel data內存區域,而不是去問內存重新申請一塊區域來存放Bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要佔用屏幕所能夠顯示的圖片數量的內存大小,但複用存在一些限制,具體體現在:在Android 4.4之前只能重用相同大小的Bitmap的內存,而Android 4.4及以後版本則只要後來的Bitmap比之前的小即可。使用inBitmap參數前,每創建一個Bitmap對象都會分配一塊內存供其使用,而使用了inBitmap參數後,多個Bitmap可以複用一塊內存,這樣可以提高性能

d.StringBuilder替代String: 在有些時候,代碼中會需要使用到大量的字符串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”

e.避免在類似onDraw這樣的方法中創建對象,因為它會迅速佔用大量內存,引起頻繁的GC`甚至內存抖動

f.減少內存洩漏也是一種避免OOM的方法

7.說下Activity的啟動模式,生命週期,兩個Activity跳轉的生命週期,如果一個Activity跳轉另一個Activity再按下Home鍵在回到Activity的生命週期是什麼樣的

啟動模式

Standard模式:Activity可以有多個實例,每次啟動Activity,無論任務棧中是否已經有這個Activity的實例,系統都會創建一個新的Activity實例

SingleTop模式:當一個singleTop模式的Activity已經位於任務棧的棧頂,再去啟動它時,不會再創建新的實例,如果不位於棧頂,就會創建新的實例

SingleTask模式:如果Activity已經位於棧頂,系統不會創建新的Activity實例,和singleTop模式一樣。但Activity已經存在但不位於棧頂時,系統就會把該Activity移到棧頂,並把它上面的activity出棧

SingleInstance模式:singleInstance模式也是單例的,但和singleTask不同,singleTask只是任務棧內單例,系統裡是可以有多個singleTask Activity實例的,而singleInstance Activity在整個系統裡只有一個實例,啟動一singleInstanceActivity時,系統會創建一個新的任務棧,並且這個任務棧只有他一個Activity

生命週期

onCreate onStart onResume onPause onStop onDestroy

兩個Activity跳轉的生命週期

1.啟動AonCreate - onStart - onResume2.在A中啟動B

<code>ActivityA onPause
ActivityB onCreate
ActivityB onStart
ActivityB onResume
ActivityA onStop
/<code>

3.從B中返回A(按物理硬件返回鍵)

<code>ActivityB onPause
ActivityA onRestart
ActivityA onStart
ActivityA onResume
ActivityB onStop
ActivityB onDestroy
/<code>

4.繼續返回

<code>ActivityA onPause
ActivityA onStop
ActivityA onDestroy
/<code>

8.onRestart的調用場景

(1)按下home鍵之後,然後切換回來,會調用onRestart()。(2)從本Activity跳轉到另一個Activity之後,按back鍵返回原來Activity,會調用onRestart();(3)從本Activity切換到其他的應用,然後再從其他應用切換回來,會調用onRestart();

9.是否了SurfaceView,它是什麼?他的繼承方式是什麼?他與View的區別(從源碼角度,如加載,繪製等)

SurfaceView中採用了雙緩衝機制,保證了UI界面的流暢性,同時SurfaceView不在主線程中繪製,而是另開闢一個線程去繪製,所以它不妨礙UI線程;

SurfaceView繼承於View,他和View主要有以下三點區別:(1)View底層沒有雙緩衝機制,SurfaceView有;(2)view主要適用於主動更新,而SurfaceView適用與被動的更新,如頻繁的刷新(3)view會在主線程中去更新UI,而SurfaceView則在子線程中刷新;

SurfaceView的內容不在應用窗口上,所以不能使用變換(平移、縮放、旋轉等)。也難以放在ListView或者ScrollView中,不能使用UI控件的一些特性比如View.setAlpha()

View:顯示視圖,內置畫布,提供圖形繪製函數、觸屏事件、按鍵事件函數等;必須在UI主線程內更新畫面,速度較慢。

SurfaceView:基於view視圖進行拓展的視圖類,更適合2D遊戲的開發;是view的子類,類似使用雙緩機制,在新的線程中更新畫面所以刷新界面速度比view快,Camera預覽界面使用SurfaceView。

GLSurfaceView:基於SurfaceView視圖再次進行拓展的視圖類,專用於3D遊戲開發的視圖;是SurfaceView的子類,openGL專用。

10.如何實現進程保活

a: Service設置成START_STICKY kill後會被重啟(等待5秒左右),重傳Intent,保持與重啟前一樣b: 通過 startForeground將進程設置為前臺進程, 做前臺服務,優先級和前臺應用一個級別,除非在系統內存非常缺,否則此進程不會被killc: 雙進程Service: 讓2個進程互相保護對方,其中一個Service被清理後,另外沒被清理的進程可以立即重啟進程d: 用C編寫守護進程(即子進程) : Android系統中當前進程(Process)fork出來的子進程,被系統認為是兩個不同的進程。當父進程被殺死的時候,子進程仍然可以存活,並不受影響(Android5.0以上的版本不可行)聯繫廠商,加入白名單e.鎖屏狀態下,開啟一個一像素Activity

11.說下冷啟動與熱啟動是什麼,區別,如何優化,使用場景等。

app冷啟動:

當應用啟動時,後臺沒有該應用的進程,這時系統會重新創建一個新的進程分配給該應用, 這個啟動方式就叫做冷啟動(後臺不存在該應用進程)。冷啟動因為系統會重新創建一個新的進程分配給它,所以會先創建和初始化Application類,再創建和初始化MainActivity類(包括一系列的測量、佈局、繪製),最後顯示在界面上。

app熱啟動:

當應用已經被打開, 但是被按下返回鍵、Home鍵等按鍵時回到桌面或者是其他程序的時候,再重新打開該app時, 這個方式叫做熱啟動(後臺已經存在該應用進程)。熱啟動因為會從已有的進程中來啟動,所以熱啟動就不會走Application這步了,而是直接走MainActivity(包括一系列的測量、佈局、繪製),所以熱啟動的過程只需要創建和初始化一個MainActivity就行了,而不必創建和初始化Application冷啟動的流程當點擊app的啟動圖標時,安卓系統會從Zygote進程中fork創建出一個新的進程分配給該應用,之後會依次創建和初始化Application類、創建MainActivity類、加載主題樣式Theme中的windowBackground等屬性設置給MainActivity以及配置Activity層級上的一些屬性、再inflate佈局、當onCreate/onStart/onResume方法都走完了後最後才進行contentView的measure/layout/draw顯示在界面上

冷啟動的生命週期簡要流程:

Application構造方法 –>attachBaseContext()–>onCreate –>Activity構造方法–>onCreate() –> 配置主體中的背景等操作 –>onStart()–> onResume()–> 測量、佈局、繪製顯示

冷啟動的優化主要是視覺上的優化,解決白屏問題,提高用戶體驗,所以通過上面冷啟動的過程。能做的優化如下:1、減少onCreate()方法的工作量2、不要讓Application參與業務的操作3、不要在Application進行耗時操作4、不要以靜態變量的方式在Application保存數據5、減少佈局的複雜度和層級6、減少主線程耗時

12.為什麼冷啟動會有白屏黑屏問題?

原因在於加載主題樣式Theme中的windowBackground等屬性設置給MainActivity發生在inflate佈局當onCreate/onStart/onResume方法之前,而windowBackground背景被設置成了白色或者黑色,所以我們進入app的第一個界面的時候會造成先白屏或黑屏一下再進入界面。解決思路如下1.給他設置windowBackground背景跟啟動頁的背景相同,如果你的啟動頁是張圖片那麼可以直接給windowBackground這個屬性設置該圖片那麼就不會有一閃的效果了

<code> /<code>


分享到:


相關文章: