一次沒有八股文的Android面試分享~
寫在前面
光陰似箭,不知不覺中,從事Android開發(fā)也有些年份了,回想自己的求職歷程,感觸頗多。求職離不開面試,想要在面試的時(shí)候應(yīng)答如流,還是需要提前準(zhǔn)備的,這里只針對(duì)Android技術(shù)面,做個(gè)總結(jié),把這些年我參加面試時(shí)遇到的所有問題做個(gè)匯總,讓我們一起玩轉(zhuǎn)技術(shù)面吧!

Dalvik和ART虛擬機(jī)的區(qū)別
Dalvik是運(yùn)行在Android平臺(tái)的Java虛擬機(jī),Android編譯后生成dex文件,Dalvik虛擬機(jī)下運(yùn)行Java時(shí),要將字節(jié)碼通過即時(shí)編譯器轉(zhuǎn)換為機(jī)器碼,這會(huì)拖慢應(yīng)用的運(yùn)行效率,所以Google開發(fā)了新的虛擬機(jī)ART(Android Runtime),應(yīng)用在第一次安裝的時(shí)候,字節(jié)碼就會(huì)預(yù)先編譯成機(jī)器碼,也就是預(yù)編譯,這樣首次啟動(dòng)應(yīng)用會(huì)變得更快。
進(jìn)程的劃分
前臺(tái)進(jìn)程:用戶正在使用的,比如某個(gè)進(jìn)程持有一個(gè)正在與用戶交互的Activity并且該Activity正處于Resume狀態(tài)。系統(tǒng)一般不會(huì)殺死前臺(tái)進(jìn)程,除非用戶強(qiáng)制停止應(yīng)用。
可見進(jìn)程:用戶正在使用,看得到摸不著,或者沒有覆蓋到整個(gè)屏幕,比如不在前臺(tái),但仍對(duì)用戶可見的Activity。系統(tǒng)一般也不會(huì)殺死可見進(jìn)程,除非在資源吃緊的情況下。
服務(wù)進(jìn)程:比如某個(gè)進(jìn)程中運(yùn)行著一個(gè)Service且該Service是通過startService()啟動(dòng),與用戶界面沒有直接關(guān)聯(lián)。在內(nèi)存不足的情況下,服務(wù)進(jìn)程會(huì)被殺死。
后臺(tái)進(jìn)程:比如在用戶按了Home鍵,程序本身看不到,但其實(shí)還在運(yùn)行,系統(tǒng)可能隨時(shí)終止,回收內(nèi)存。
空進(jìn)程:某個(gè)進(jìn)程不包含任何活躍組件時(shí)該進(jìn)程就會(huì)被置為空進(jìn)程,完全沒用,第一個(gè)干它。
Activity生命周期

各種情況的生命周期變化:
兩個(gè)Activity(A到B,B為正常Activity)切換的生命周期:onPause(A) – onCreate(B) – onStart(B) --onResume(B) – onStop(A),這時(shí)回退到A:onPause(B) – onRestart(A) – onStart(A) – onResume(A) – onStop(B) --onDestory(B)
兩個(gè)Activity(A到B,B為Dialog風(fēng)格的Activity)切換的生命周期:onPause(A) – onCreate(B) – onStart(B) – onResume(B),這時(shí)回退到A :onPause(B) – onResume(A) – onStop(B) – onDestory(B)
Activity啟動(dòng)后點(diǎn)擊Home鍵再回到應(yīng)用的生命周期:onPause – onStop – onRestart --onStart – onResume
橫豎屏切換(切換豎屏?xí)r會(huì)打印兩次相同的log):onPause – onStop – onDestory --onCreate – onStart – onResume。如果清單文件中配置了android:configChanges=“orientation|keyboardHidden|screenSize”,橫豎屏切換時(shí)不會(huì)重新創(chuàng)建Activity,只會(huì)調(diào)用onConfigurationChanged方法
電源鍵關(guān):onPause – onStop,重新開電源鍵:onRestart – onStart – onResume
Activity啟動(dòng)模式
在AndroidManifest的Activity中,launchMode屬性可以設(shè)置啟動(dòng)模式,默認(rèn)standard模式。
standard:每啟動(dòng)一個(gè)Activity,就會(huì)創(chuàng)建該Activity一個(gè)新實(shí)例。
singleTop:每啟動(dòng)一個(gè)Activity,都會(huì)檢查該Activity是否處于棧頂,如果處于棧頂則不創(chuàng)建新實(shí)例。適用于接受到消息后顯示的界面,例如QQ接受到消息后點(diǎn)擊彈出Activity,如果一次來10條消息,總不能一次彈10個(gè)Activity。
singleTask:每啟動(dòng)一個(gè)Activity,都會(huì)檢查該Activity是否存在返回棧中,如果存在,則直接跳轉(zhuǎn)至該Activity在返回棧中的位置并啟動(dòng),如果不存在則創(chuàng)建新實(shí)例。適合作為程序入口點(diǎn),例如手機(jī)系統(tǒng)瀏覽器的主界面,不管從多少個(gè)應(yīng)用啟動(dòng)系統(tǒng)瀏覽器,只會(huì)啟動(dòng)主界面一次,其余情況都會(huì)走onNewIntent,并且會(huì)清空主界面上其他的頁面,不再新建。
singleInstance:每啟動(dòng)一個(gè)Activity,都會(huì)為其創(chuàng)建一個(gè)獨(dú)立的返回棧。適用需要與程序分開的頁面,例如系統(tǒng)的通話界面,Activity同時(shí)接聽兩個(gè)電話。

onNewIntent與啟動(dòng)模式
當(dāng)Activity的launchMode為SingleTop時(shí),如果Activity在棧頂,且現(xiàn)在要啟動(dòng)該Activity,這時(shí)會(huì)調(diào)用onNewIntent方法 ,生命周期順序?yàn)椋簅nNewIntent — onResume。
當(dāng)Activity的launchMode為SingleInstance或SingleTask時(shí),如果Activity已經(jīng)在任務(wù)棧中,再次啟動(dòng)Activity,那么此時(shí)會(huì)調(diào)用onNewIntent方法,生命周期調(diào)用順序?yàn)椋簅nNewIntent — onRestart — onStart — onResume
Activity緩存

onSaveInstanceState
Activity中的onSaveInstanceState回調(diào)方法用于保存臨時(shí)數(shù)據(jù)和狀態(tài),這個(gè)方法會(huì)在Activity的onStop方法之前調(diào)用,在onSaveInstanceState方法中有個(gè)參數(shù)Bundle,可傳入需要保存的數(shù)據(jù)和狀態(tài)。
??override?fun?onSaveInstanceState(outState:?Bundle)?{
????????//注意:先保存數(shù)據(jù),然后調(diào)用父類方法
????????outState.putString("data",?"temp?data")
????????super.onSaveInstanceState(outState)
????}

onRestoreInstanceState
Activity中的onRestoreInstanceState回調(diào)方法用于恢復(fù)數(shù)據(jù)和狀態(tài),這個(gè)方法會(huì)在Activity的onResume之前調(diào)用。在onRestoreInstanceState方法中有個(gè)參數(shù)Bundle,它會(huì)傳遞到Activity的onCreate中,所以也可以在onCreate方法中做數(shù)據(jù)和狀態(tài)還原。
????override?fun?onCreate(savedInstanceState:?Bundle?)?{
????????super.onCreate(savedInstanceState)
????????setContentView(R.layout.activity_test)
????????//在activity的onCreate也可以恢復(fù)數(shù)據(jù),當(dāng)Acivity第一次被創(chuàng)建的時(shí)候?yàn)榭?所以我們需要先判斷一下
????????if?(savedInstanceState?!=?null)?{
????????????savedInstanceState.getString("data")
????????}
????}
????override?fun?onRestoreInstanceState(savedInstanceState:?Bundle)?{
????????super.onRestoreInstanceState(savedInstanceState)
????????//注意:先調(diào)用父類方法,再取出自己保存的數(shù)據(jù)
????????val?tempData?=?savedInstanceState.getString("data")
????}
當(dāng)某個(gè)Activity變得容易被系統(tǒng)銷毀時(shí),例如按下Home鍵,關(guān)閉電源鍵等,onSaveInstanceState就會(huì)被執(zhí)行。
onRestoreInstanceState執(zhí)行的前提是:Activity確實(shí)被系統(tǒng)銷毀了。
onSaveInstanceState和onRestoreInstanceState不一定成對(duì)調(diào)用的,例如當(dāng)正在顯示Activity的時(shí)候,用戶按下HOME鍵回到主界面,然后又返回到Activity,這種情況下Activity一般不會(huì)因?yàn)閮?nèi)存原因被系統(tǒng)銷毀,故Activity的onRestoreInstanceState方法不會(huì)被執(zhí)行。
Service的兩種啟動(dòng)方法的區(qū)別
生命周期:
startService:onCreate — onStartCommand — onDestroy
bindService:onCreate — onBind — onUnbind — onDestroy
重復(fù)調(diào)用:
startService:重復(fù)調(diào)用時(shí),onCreate方法只會(huì)調(diào)用一次,onStartCommand每次都會(huì)被調(diào)用
bindService:重復(fù)調(diào)用時(shí),onCreate與onBind都只會(huì)調(diào)用一次
與activity的關(guān)系:
startService:與activity之間沒有什么關(guān)系,對(duì)應(yīng)的activity被銷毀時(shí),不影響service
bindService:與activity綁定,對(duì)應(yīng)的activity銷毀時(shí),對(duì)應(yīng)的service也銷毀
應(yīng)用場(chǎng)景:
startService:只指定Service的操作,不需要service返回操作結(jié)果的場(chǎng)景
bindService:需要Service操作完成后,返回操作結(jié)果的場(chǎng)景
事件分發(fā)機(jī)制
事件分發(fā)的三個(gè)主要對(duì)象是:Activity,ViewGroup,View。一個(gè)事件產(chǎn)生之后,都是先傳給Activity,再傳給ViewGroup,最后傳給View。
三個(gè)重要的方法:dispatchTouchEvent(分發(fā)事件),onInterceptTouchEvent(判斷是否攔截某個(gè)事件,只存在于ViewGroup),onTouchEvent(處理事件)。
Activity和View是沒有onInterceptTouchEvent這個(gè)方法的,因?yàn)锳ctivity是處于分發(fā)機(jī)制的最頂端,如果一開始就把事件攔截了,那么會(huì)導(dǎo)致整個(gè)屏幕都無法響應(yīng)用戶的操作,而View處于事件分發(fā)的最末端,它不需要攔截,事件分發(fā)到View的時(shí)候,view能處理就處理,不處理就返回給他的父控件。
手機(jī)屏幕我們可以稱為一個(gè)窗口,也就是一個(gè)window,window是一個(gè)抽象類,它規(guī)定了一些管理窗口的方法,但是具體實(shí)現(xiàn)是由它的唯一實(shí)現(xiàn)類phonewindow去實(shí)現(xiàn)的,這樣phonewindow就是整個(gè)屏幕的實(shí)際掌控者,而phonewindow又是通過它的內(nèi)部類decorview去對(duì)View進(jìn)行管理。
以點(diǎn)擊事件為例:當(dāng)用戶點(diǎn)擊了屏幕,事件先傳遞到Activity中,Activity通過它的dispatchTouchEvent將事件分發(fā)到phoneWindow,phonewindow其內(nèi)部有個(gè)內(nèi)部類DecorView,DecorView會(huì)調(diào)用dispatchTouchEvent去進(jìn)行事件分發(fā),如果不攔截事件,就會(huì)下傳到rootview,rootview在dispatchTouchEvent內(nèi)部調(diào)用onInterceptTouchEvent去判斷是否攔截,不攔截就會(huì)把事件分發(fā)給下一個(gè)viewgroup,攔截就在onTouchEvent進(jìn)行處理并返回true,viewgroup中也是一樣,最后事件傳遞到view,view是最底層控件,不會(huì)有onInterceptTouchEvent,它的選擇就只有處理和不處理,處理就在onTouchEvent進(jìn)行處理并返回true,不處理的話事件也不會(huì)被銷毀,view這時(shí)會(huì)把事件回傳,經(jīng)過上述流程后回傳給activity,如果Activity還不處理,那么這個(gè)事件才會(huì)被銷毀,如下圖所示:

Binder原理
Binder IPC是基于內(nèi)存映射(mmap)來實(shí)現(xiàn)的
內(nèi)存映射就是將用戶空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間。映射關(guān)系建立后,用戶對(duì)這塊內(nèi)存區(qū)域的修改可以直接反應(yīng)到內(nèi)核空間,內(nèi)核空間對(duì)這段區(qū)域的修改也能直接反應(yīng)到用戶空間。內(nèi)存映射能減少數(shù)據(jù)拷貝次數(shù),實(shí)現(xiàn)用戶空間和內(nèi)核空間的高效互動(dòng)。
首先Binder驅(qū)動(dòng)在內(nèi)核空間創(chuàng)建一個(gè)數(shù)據(jù)接收緩存區(qū);
接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū),建立內(nèi)核緩存區(qū)和數(shù)據(jù)接收緩存區(qū)之間的映射關(guān)系,以及數(shù)據(jù)接收緩存區(qū)和接收進(jìn)程用戶空間地址的映射關(guān)系;
發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用copy_from_user()將數(shù)據(jù)copy到內(nèi)核中的內(nèi)核緩存區(qū),由于內(nèi)核緩存區(qū)和數(shù)據(jù)接收緩存區(qū),接收進(jìn)程的用戶空間存在內(nèi)存映射,因此也就相當(dāng)于把數(shù)據(jù)發(fā)送到了接收進(jìn)程的用戶空間,這樣便完成了一次進(jìn)程間的通信。

SurfaceView和View的區(qū)別
View主要適用于主動(dòng)更新的情況,而surfaceView主要適用于被動(dòng)更新,例如頻繁的刷新。
View在主線程中對(duì)畫面進(jìn)行刷新,而surfaceView通常會(huì)通過一個(gè)子線程來進(jìn)行頁面的刷新。
View在繪圖時(shí)沒有使用雙緩沖機(jī)制,而surfaceView在底層實(shí)現(xiàn)了雙緩沖機(jī)制。
雙緩沖技術(shù)是把要處理的圖片在內(nèi)存中處理好之后,再將其顯示在屏幕上。雙緩沖主要是為了解決反復(fù)局部刷屏帶來的閃爍。把要畫的東西先畫到一個(gè)內(nèi)存區(qū)域里,然后整體的一次性畫出來。
HashMap機(jī)制
HashMap是一種 “數(shù)組+鏈表” 數(shù)據(jù)結(jié)構(gòu),我們是通過put和get方法存取對(duì)象的,當(dāng)我們將鍵值對(duì)傳遞給put方法時(shí),它調(diào)用鍵對(duì)象的hashCode()方法來計(jì)算hashcode,然后找到相應(yīng)的位置來儲(chǔ)存值對(duì)象。當(dāng)獲取對(duì)象時(shí),通過鍵對(duì)象的equals方法找到正確的鍵值對(duì),然后返回值對(duì)象。HashMap使用鏈表來解決碰撞問題,當(dāng)發(fā)生碰撞了,對(duì)象將會(huì)儲(chǔ)存在鏈表的下一個(gè)節(jié)點(diǎn)中,也就是說,當(dāng)兩個(gè)不同的鍵對(duì)象的hashcode相同時(shí),它們會(huì)儲(chǔ)存在同一個(gè)數(shù)組位置的鏈表中。

HashMap在什么情況下會(huì)擴(kuò)容,怎么擴(kuò)容?
執(zhí)行put方法時(shí),如果當(dāng)前的數(shù)組里的元素個(gè)數(shù)大于閾值(閾值=數(shù)組長(zhǎng)度 x 負(fù)荷因子),就會(huì)執(zhí)行resize()方法擴(kuò)容,擴(kuò)容時(shí)創(chuàng)建一個(gè)比原來數(shù)組容量大兩倍的新數(shù)組,遍歷原來的數(shù)組,把原來數(shù)組上的元素重新放到新數(shù)組上。
總結(jié):HashMap由數(shù)組+鏈表組成的,數(shù)組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的。因?yàn)榧t黑樹的節(jié)點(diǎn)大小是鏈表節(jié)點(diǎn)的兩倍,所以只有在節(jié)點(diǎn)比較多的時(shí)候才使用紅黑樹。只有鏈表長(zhǎng)度大于8,數(shù)組長(zhǎng)度大于等于64的時(shí)候才會(huì)轉(zhuǎn)成紅黑樹。
ClassLoader雙親委托機(jī)制

ClassLoader使用雙親委派機(jī)制來加載class文件的:
當(dāng)AppClassLoader加載一個(gè)class時(shí),首先不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給父類加載器ExtClassLoader去完成。
當(dāng)ExtClassLoader加載一個(gè)class時(shí),首先也不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給BootStrapClassLoader去完成。
如果BootStrapClassLoader加載失敗,會(huì)使用ExtClassLoader來嘗試加載。
若ExtClassLoader也加載失敗,則會(huì)使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會(huì)報(bào)出異常ClassNotFoundException。

雙親委派機(jī)制為什么安全
一些來源的class文件是不可靠的,比如我可以自定義一個(gè)Integer類來覆蓋jdk中默認(rèn)的Integer類,然后執(zhí)行破壞程序正常執(zhí)行的一些操作,如果使用雙親委派機(jī)制的話該Integer類永遠(yuǎn)不會(huì)被調(diào)用,因?yàn)槲蠦ootStrapClassLoader加載后會(huì)加載JDK中的Integer類而不會(huì)加載自定義的這個(gè)。