前言

很多人面試之前,可能沒有在互聯網公司工作過或者說工作過但年頭較短,不知道互聯網公司技術面試都會問哪些問題? 再加上可能自己準備也不充分,去面試沒幾個回合就被面試官幾個問題打蒙了,最後以慘敗收場。

下述是我整理的Android面試題匯總,由於篇幅原因,在這隻把常規知識點部分的題目列舉出來,後續還會更新其餘面試題內容,大家可以關注一下我,及時知曉我更新的知識點,同時這份面試集錦的整理也花費了我很多時間,有需要的朋友可以幫忙轉發分享下,點個贊~

可以關注一下專欄Android高級開發架構(記得看專欄簡介哦~)每天更新各種Android開發趣事、技術乾貨、進階架構知識、面試經驗分享等。如有好的文章也歡迎投稿。

1、 Android類載入器

在Android開發中,不管是插件化還是組件化,都是基於Android系統的類載入器ClassLoader來設計的。只不過Android平臺上虛擬機運行的是Dex位元組碼,一種對class文件優化的產物,傳統Class文件是一個Java源碼文件會生成一個.class文件,而Android是把所有Class文件進行合併、優化,然後再生成一個最終的class.dex,目的是把不同class文件重複的東西只需保留一份,在早期的Android應用開發中,如果不對Android應用進行分dex處理,那麼最後一個應用的apk只會有一個dex文件。

Android中常用的類載入器有兩種,DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。區別在於調用父類構造器時,DexClassLoader多傳了一個optimizedDirectory參數,這個目錄必須是內部存儲路徑,用來緩存系統創建的Dex文件。而PathClassLoader該參數為null,只能載入內部存儲目錄的Dex文件。所以我們可以用DexClassLoader去載入外部的apk文件,這也是很多插件化技術的基礎。

學習&提升&進階直通車:有任何問題,歡迎你來一起交流,互相學習

2、 Service

理解Android的Service,可以從以下幾個方面來理解:

  • Service是在main Thread中執行,Service中不能執行耗時操作(網路請求,拷貝資料庫,大文件)。
  • 可以在xml中設置Service所在的進程,讓Service在另外的進程中執行。
  • Service執行的操作最多是20s,BroadcastReceiver是10s,Activity是5s。
  • Activity通過bindService(Intent,ServiceConnection,flag)與Service綁定。
  • Activity可以通過startService和bindService啟動Service。

IntentService

IntentService是一個抽象類,繼承自Service,內部存在一個ServiceHandler(Handler)和HandlerThread(Thread)。IntentService是處理非同步請求的一個類,在IntentService中有一個工作線程(HandlerThread)來處理耗時操作,啟動IntentService的方式和普通的一樣,不過當執行完任務之後,IntentService會自動停止。另外可以多次啟動IntentService,每一個耗時操作都會以工作隊列的形式在IntentService的onHandleIntent回調中執行,並且每次執行一個工作線程。IntentService的本質是:封裝了一個HandlerThread和Handler的非同步框架。

2.1、生命週期示意圖

Service 作為 Android四大組件之一,應用非常廣泛。和Activity一樣,Service 也有一系列的生命週期回調函數,具體如下圖。

通常,啟動Service有兩種方式,startService和bindService方式。

2.2、startService生命週期

當我們通過調用了Context的startService方法後,我們便啟動了Service,通過startService方法啟動的Service會一直無限期地運行下去,只有在外部調用Context的stopService或Service內部調用Service的stopSelf方法時,該Service才會停止運行並銷毀。

onCreate

onCreate: 執行startService方法時,如果Service沒有運行的時候會創建該Service並執行Service的onCreate回調方法;如果Service已經處於運行中,那麼執行startService方法不會執行Service的onCreate方法。也就是說如果多次執行了Context的startService方法啟動Service,Service方法的onCreate方法只會在第一次創建Service的時候調用一次,以後均不會再次調用。我們可以在onCreate方法中完成一些Service初始化相關的操作。

onStartCommand

onStartCommand: 在執行了startService方法之後,有可能會調用Service的onCreate方法,在這之後一定會執行Service的onStartCommand回調方法。也就是說,如果多次執行了Context的startService方法,那麼Service的onStartCommand方法也會相應的多次調用。onStartCommand方法很重要,我們在該方法中根據傳入的Intent參數進行實際的操作,比如會在此處創建一個線程用於下載數據或播放音樂等。

public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
}

當Android面臨內存匱乏的時候,可能會銷毀掉你當前運行的Service,然後待內存充足的時候可以重新創建Service,Service被Android系統強制銷毀並再次重建的行為依賴於Service中onStartCommand方法的返回值。我們常用的返回值有三種值,START_NOT_STICKYSTART_STICKYSTART_REDELIVER_INTENT,這三個值都是Service中的靜態常量。

START_NOT_STICKY

如果返回START_NOT_STICKY,表示當Service運行的進程被Android系統強制殺掉之後,不會重新創建該Service,當然如果在其被殺掉之後一段時間又調用了startService,那麼該Service又將被實例化。那什麼情境下返回該值比較恰當呢?如果我們某個Service執行的工作被中斷幾次無關緊要或者對Android內存緊張的情況下需要被殺掉且不會立即重新創建這種行為也可接受,那麼我們便可將 onStartCommand的返回值設置為START_NOT_STICKY。舉個例子,某個Service需要定時從伺服器獲取最新數據:通過一個定時器每隔指定的N分鐘讓定時器啟動Service去獲取服務端的最新數據。當執行到Service的onStartCommand時,在該方法內再規劃一個N分鐘後的定時器用於再次啟動該Service並開闢一個新的線程去執行網路操作。假設Service在從伺服器獲取最新數據的過程中被Android系統強制殺掉,Service不會再重新創建,這也沒關係,因為再過N分鐘定時器就會再次啟動該Service並重新獲取數據。

START_STICKY

如果返回START_STICKY,表示Service運行的進程被Android系統強制殺掉之後,Android系統會將該Service依然設置為started狀態(即運行狀態),但是不再保存onStartCommand方法傳入的intent對象,然後Android系統會嘗試再次重新創建該Service,並執行onStartCommand回調方法,但是onStartCommand回調方法的Intent參數為null,也就是onStartCommand方法雖然會執行但是獲取不到intent信息。如果你的Service可以在任意時刻運行或結束都沒什麼問題,而且不需要intent信息,那麼就可以在onStartCommand方法中返回START_STICKY,比如一個用來播放背景音樂功能的Service就適合返回該值。

START_REDELIVER_INTENT

如果返回START_REDELIVER_INTENT,表示Service運行的進程被Android系統強制殺掉之後,與返回START_STICKY的情況類似,Android系統會將再次重新創建該Service,並執行onStartCommand回調方法,但是不同的是,Android系統會再次將Service在被殺掉之前最後一次傳入onStartCommand方法中的Intent再次保留下來並再次傳入到重新創建後的Service的onStartCommand方法中,這樣我們就能讀取到intent參數。只要返回START_REDELIVER_INTENT,那麼onStartCommand重的intent一定不是null。如果我們的Service需要依賴具體的Intent才能運行(需要從Intent中讀取相關數據信息等),並且在強制銷毀後有必要重新創建運行,那麼這樣的Service就適合返回START_REDELIVER_INTENT。

onBind

Service中的onBind方法是抽象方法,所以Service類本身就是抽象類,也就是onBind方法是必須重寫的,即使我們用不到。在通過startService使用Service時,我們在重寫onBind方法時,只需要將其返回null即可。onBind方法主要是用於給bindService方法調用Service時才會使用到。

onDestroy

onDestroy: 通過startService方法啟動的Service會無限期運行,只有當調用了Context的stopService或在Service內部調用stopSelf方法時,Service才會停止運行並銷毀,在銷毀的時候會執行Service回調函數。

2.3、bindService生命週期

bindService方式啟動Service主要有以下幾個生命週期函數:

onCreate():

首次創建服務時,系統將調用此方法。如果服務已在運行,則不會調用此方法,該方法只調用一次。

onStartCommand():

當另一個組件通過調用startService()請求啟動服務時,系統將調用此方法。

onDestroy():

當服務不再使用且將被銷毀時,系統將調用此方法。

onBind():

當另一個組件通過調用bindService()與服務綁定時,系統將調用此方法。

onUnbind():

當另一個組件通過調用unbindService()與服務解綁時,系統將調用此方法。

onRebind():

當舊的組件與服務解綁後,另一個新的組件與服務綁定,onUnbind()返回true時,系統將調用此方法。

學習&提升&進階直通車:有任何問題,歡迎你來一起交流,互相學習

3、fragemnt

3.1、創建方式

(1)靜態創建

首先我們需要創建一個xml文件,然後創建與之對應的java文件,通過onCreatView()的返回方法進行關聯,最後我們需要在Activity中進行配置相關參數即在Activity的xml文件中放上fragment的位置。

<fragment
android:name="xxx.BlankFragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
</fragment>

(2)動態創建

動態創建Fragment主要有以下幾個步驟:

  1. 創建待添加的fragment實例。
  2. 獲取FragmentManager,在Activity中可以直接通過調用 getSupportFragmentManager()方法得到。
  3. 開啟一個事務,通過調用beginTransaction()方法開啟。
  4. 向容器內添加或替換fragment,一般使用repalce()方法實現,需要傳入容器的id和待添加的fragment實例。
  5. 提交事務,調用commit()方法來完成。

3.2、Adapter對比

FragmnetPageAdapter在每次切換頁面時,只是將Fragment進行分離,適合頁面較少的Fragment使用以保存一些內存,對系統內存不會多大影響。

FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的內存

3.3、Activity生命週期

Activity的生命週期如下圖:

(1)動態載入:

動態載入時,Activity的onCreate()調用完,才開始載入fragment並調用其生命週期方法,所以在第一個生命週期方法onAttach()中便能獲取Activity以及Activity的佈局的組件;

(2)靜態載入:

1.靜態載入時,Activity的onCreate()調用過程中,fragment也在載入,所以fragment無法獲取到Activity的佈局中的組件,但為什麼能獲取到Activity呢?

2.原來在fragment調用onAttach()之前其實還調用了一個方法onInflate(),該方法被調用時fragment已經是和Activity相互結合了,所以可以獲取到對方,但是Activity的onCreate()調用還未完成,故無法獲取Activity的組件;

3.Activity的onCreate()調用完成是,fragment會調用onActivityCreated()生命週期方法,因此在這兒開始便能獲取到Activity的佈局的組件;

3.4、與Activity通信

fragment不通過構造函數進行傳值的原因是因為橫屏切換的時候獲取不到值。

Activity向Fragment傳值:

Activity向Fragment傳值,要傳的值放到bundle對象裏; 在Activity中創建該Fragment的對象fragment,通過調用setArguments()傳遞到fragment中; 在該Fragment中通過調用getArguments()得到bundle對象,就能得到裡面的值。

Fragment向Activity傳值:

第一種:

在Activity中調用getFragmentManager()得到fragmentManager,,調用findFragmentByTag(tag)或者通過findFragmentById(id),例如:

FragmentManager fragmentManager = getFragmentManager();

Fragment fragment = fragmentManager.findFragmentByTag(tag);

第二種:

通過回調的方式,定義一個介面(可以在Fragment類中定義),介面中有一個空的方法,在fragment中需要的時候調用介面的方法,值可以作為參數放在這個方法中,然後讓Activity實現這個介面,必然會重寫這個方法,這樣值就傳到了Activity中

Fragment與Fragment之間是如何傳值的:

第一種:

通過findFragmentByTag得到另一個的Fragment的對象,這樣就可以調用另一個的方法了。

第二種:

通過介面回調的方式。

第三種:

通過setArguments,getArguments的方式。

3.5、api區別

add

一種是add方式來進行show和add,這種方式你切換fragment不會讓fragment重新刷新,只會調用onHiddenChanged(boolean isHidden)。

replace

而用replace方式會使fragment重新刷新,因為add方式是將fragment隱藏了而不是銷毀再創建,replace方式每次都是重新創建。

commit/commitAllowingStateLoss

兩者都可以提交fragment的操作,唯一的不同是第二種方法,允許丟失一些界面的狀態和信息,幾乎所有的開發者都遇到過這樣的錯誤:無法在activity調用了onSaveInstanceState之後再執行commit(),這種異常時可以理解的,界面被系統回收(界面已經不存在),為了在下次打開的時候恢復原來的樣子,系統為我們保存界面的所有狀態,這個時候我們再去修改界面理論上肯定是不允許的,所以為了避免這種異常,要使用第二種方法。

3.懶載入

我們經常在使用fragment時,常常會結合著viewpager使用,那麼我們就會遇到一個問題,就是初始化fragment的時候,會連同我們寫的網路請求一起執行,這樣非常消耗性能,最理想的方式是,只有用戶點開或滑動到當前fragment時,才進行請求網路的操作。因此,我們就產生了懶載入這樣一個說法。

Viewpager配合fragment使用,默認載入前兩個fragment。很容易造成網路丟包、阻塞等問題。

在Fragment中有一個setUserVisibleHint這個方法,而且這個方法是優於onCreate()方法的,它會通過isVisibleToUser告訴我們當前Fragment我們是否可見,我們可以在可見的時候再進行網路載入。

從log上看setUserVisibleHint()的調用早於onCreateView,所以如果在setUserVisibleHint()要實現懶載入的話,就必須要確保View以及其他變數都已經初始化結束,避免空指針。

使用步驟:

申明一個變數isPrepare=false,isVisible=false,標明當前頁面是否被創建了 在onViewCreated週期內設置isPrepare=true 在setUserVisibleHint(boolean isVisible)判斷是否顯示,設置isVisible=true 判斷isPrepare和isVisible,都為true開始載入數據,然後恢復isPrepare和isVisible為false,防止重複載入。

學習&提升&進階直通車:有任何問題,歡迎你來一起交流,互相學習

4、Activity

4.1、 Activity啟動流程

用戶從Launcher程序點擊應用圖標可啟動應用的入口Activity,Activity啟動時需要多個進程之間的交互,Android系統中有一個zygote進程專用於孵化Android框架層和應用層程序的進程。還有一個system_server進程,該進程裏運行了很多binder service。例如ActivityManagerService,PackageManagerService,WindowManagerService,這些binder service分別運行在不同的線程中,其中ActivityManagerService負責管理Activity棧,應用進程,task。

點擊Launcher圖標來啟動Activity

用戶在Launcher程序裏點擊應用圖標時,會通知ActivityManagerService啟動應用的入口Activity,ActivityManagerService發現這個應用還未啟動,則會通知Zygote進程孵化出應用進程,然後在這個dalvik應用進程裏執行ActivityThread的main方法。應用進程接下來通知ActivityManagerService應用進程已啟動,ActivityManagerService保存應用進程的一個代理對象,這樣ActivityManagerService可以通過這個代理對象控制應用進程,然後ActivityManagerService通知應用進程創建入口Activity的實例,並執行它的生命週期方法。

4.2、Activity生命週期

(1)Activity的形態

Active/Running:

Activity處於活動狀態,此時Activity處於棧頂,是可見狀態,可與用戶進行交互。

Paused:

當Activity失去焦點時,或被一個新的非全屏的Activity,或被一個透明的Activity放置在棧頂時,Activity就轉化為Paused狀態。但我們需要明白,此時Activity只是失去了與用戶交互的能力,其所有的狀態信息及其成員變數都還存在,只有在系統內存緊張的情況下,纔有可能被系統回收掉。

Stopped:

當一個Activity被另一個Activity完全覆蓋時,被覆蓋的Activity就會進入Stopped狀態,此時它不再可見,但是跟Paused狀態一樣保持著其所有狀態信息及其成員變數。

Killed:

當Activity被系統回收掉時,Activity就處於Killed狀態。

Activity會在以上四種形態中相互切換,至於如何切換,這因用戶的操作不同而異。瞭解了Activity的4種形態後,我們就來聊聊Activity的生命週期。

Activity的生命週期

所謂的典型的生命週期就是在有用戶參與的情況下,Activity經歷從創建,運行,停止,銷毀等正常的生命週期過程。

onCreate

該方法是在Activity被創建時回調,它是生命週期第一個調用的方法,我們在創建Activity時一般都需要重寫該方法,然後在該方法中做一些初始化的操作,如通過setContentView設置界面佈局的資源,初始化所需要的組件信息等。

onStart

此方法被回調時表示Activity正在啟動,此時Activity已處於可見狀態,只是還沒有在前臺顯示,因此無法與用戶進行交互。可以簡單理解為Activity已顯示而我們無法看見擺了。

onResume

當此方法回調時,則說明Activity已在前臺可見,可與用戶交互了(處於前面所說的Active/Running形態),onResume方法與onStart的相同點是兩者都表示Activity可見,只不過onStart回調時Activity還是後臺無法與用戶交互,而onResume則已顯示在前臺,可與用戶交互。當然從流程圖,我們也可以看出當Activity停止後(onPause方法和onStop方法被調用),重新回到前臺時也會調用onResume方法,因此我們也可以在onResume方法中初始化一些資源,比如重新初始化在onPause或者onStop方法中釋放的資源。

onPause

此方法被回調時則表示Activity正在停止(Paused形態),一般情況下onStop方法會緊接著被回調。但通過流程圖我們還可以看到一種情況是onPause方法執行後直接執行了onResume方法,這屬於比較極端的現象了,這可能是用戶操作使當前Activity退居後臺後又迅速地再回到到當前的Activity,此時onResume方法就會被回調。當然,在onPause方法中我們可以做一些數據存儲或者動畫停止或者資源回收的操作,但是不能太耗時,因為這可能會影響到新的Activity的顯示——onPause方法執行完成後,新Activity的onResume方法才會被執行。

onStop

一般在onPause方法執行完成直接執行,表示Activity即將停止或者完全被覆蓋(Stopped形態),此時Activity不可見,僅在後臺運行。同樣地,在onStop方法可以做一些資源釋放的操作(不能太耗時)。

onRestart

表示Activity正在重新啟動,當Activity由不可見變為可見狀態時,該方法被回調。這種情況一般是用戶打開了一個新的Activity時,當前的Activity就會被暫停(onPause和onStop被執行了),接著又回到當前Activity頁面時,onRestart方法就會被回調。

onDestroy

此時Activity正在被銷毀,也是生命週期最後一個執行的方法,一般我們可以在此方法中做一些回收工作和最終的資源釋放。

小結

到這裡我們來個小結,當Activity啟動時,依次會調用onCreate(),onStart(),onResume(),而當Activity退居後臺時(不可見,點擊Home或者被新的Activity完全覆蓋),onPause()和onStop()會依次被調用。當Activity重新回到前臺(從桌面回到原Activity或者被覆蓋後又回到原Activity)時,onRestart(),onStart(),onResume()會依次被調用。當Activity退出銷毀時(點擊back鍵),onPause(),onStop(),onDestroy()會依次被調用,到此Activity的整個生命週期方法回調完成。現在我們再回頭看看之前的流程圖,應該是相當清晰了吧。嗯,這就是Activity整個典型的生命週期過程。

以上就是Android常規知識點部分的面試題目,後續還會更新其餘面試題內容,大家可以關注一下我,及時知曉我更新的知識點,同時這份面試集錦的整理也花費了我很多時間,有需要的朋友可以點這裡:有任何問題,歡迎你來一起交流,互相學習

最後

漫漫Android開發之路,你我只是其中的一小部分……

只有不斷的學習、進階,纔是我們的出路!纔跟得上時代的進步!為此我也是找了很多的資料。

這邊把我整理的一些資料拿出來分享給大家,一方面是希望能夠幫助大家提高,一方面也是警醒自己,要不斷學習、不斷提升,進階纔是王道!

分享給大家的資料包括高級架構技術進階腦圖Android開發面試專題資料,還有高級進階架構資料包括但不限於【高級UI、性能優化、移動架構師、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術】希望能幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也是可以分享給身邊好友一起學習的!

學習提升進階直通車:有任何問題,歡迎你來一起交流,互相學習

關注一下專欄Android高級開發架構(記得看專欄簡介哦~)每天更新各種Android開發趣事、技術乾貨、進階架構知識、面試經驗分享等。


推薦閱讀:
相關文章