Android 四大組件面試題

1.1 Activity 與 Fragment 之間常見的幾種通信方式?

viewModel 做數(shù)據(jù)管理,activity 和 fragment 公用同個viewModel 實現(xiàn)數(shù)據(jù)傳遞

1.2 LaunchMode 的應用場景?

LaunchMode 有四種,分別為 Standard,SingleTop,SingleTask 和 SingleInstance,每種模式的實現(xiàn)原理一樓都做了較詳細說明,下面說一下具體使用場景:

  • Standard
    Standard 模式是系統(tǒng)默認的啟動模式,一般我們 app中大部分頁面都是由該模式的頁面構(gòu)成的,比較常見的場景是:社交應用中,點擊查看用戶A信息->查看用戶A粉絲->在粉絲中挑選查看用戶B信息->查看用戶A粉絲...
    這種情況下一般我們需要保留用戶操作 Activity 棧的頁面所有執(zhí)行順序。

  • SingleTop
    SingleTop 模式一般常見于社交應用中的通知欄行為功能,例如:App 用戶收到幾條好友請求的推送消息,需要用戶點擊推送通知進入到請求者個人信息頁,將信息頁設置為 SingleTop 模式就可以增強復用性。

  • SingleTask
    SingleTask 模式一般用作應用的首頁,例如瀏覽器主頁,用戶可能從多個應用啟動瀏覽器,但主界面僅僅啟動一次,其余情況都會走onNewIntent,并且會清空主界面上面的其他頁面。

  • SingleInstance
    SingleInstance 模式常應用于獨立棧操作的應用,如鬧鐘的提醒頁面,當你在A應用中看視頻時,鬧鐘響了,你點擊鬧鐘提醒通知后進入提醒詳情頁面,然后點擊返回就再次回到A的視頻頁面,這樣就不會過多干擾到用戶先前的操作了。

1.3 BroadcastReceiver 與 LocalBroadcastReceiver 有什么區(qū)別?

BroadcastReceiver 是跨應用廣播,利用Binder機制實現(xiàn),支持動態(tài)和靜態(tài)兩種方式注冊方式。

LocalBroadcastReceiver 是應用內(nèi)廣播,利用Handler實現(xiàn),利用了IntentFilter的match功能,提供消息的發(fā)布與接收功能,實現(xiàn)應用內(nèi)通信,效率和安全性比較高,僅支持動態(tài)注冊。

1.4 對于 Context,你了解多少?

Context 也叫上下文,是有關(guān)應用程序環(huán)境的全局信息的接口。這是一個抽象類, 它允許訪問特定于應用程序的資源和類,以及對應用程序級操作的調(diào)用,比如啟動活動,發(fā)送廣播和接收意圖等;

Activity, Service, Application 都是 Context 的子類。Context 的具體實現(xiàn)類是 ContextImpl, 還有一個包裝類ContextWrapper, ContextWrapper 的子類有 Service,
Application,ContextThemeWrapper, Activity 又是ContextThemeWrapper 的子類,
ContextThemeWrapper 也可以叫 UI Context,跟UI 操作相關(guān)的最好使用此類 Context。
ContextWrapper 中有個 mBase,這個 mBase 其實是ContextImpl,它是在Activity, Service, Application 創(chuàng)建時通過 attachBaseContext() 方法將各自對對應ContextImpl 賦值的。對 context 的操作,最終實現(xiàn)都是在 ContextImpl。

對于 startActivity操作

  • 當為Activity Context則可直接使用
  • 當為其他Context, 則必須帶上
    FLAG_ACTIVITY_NEW_TASK flags才能使用,因為非 Activity context 啟動 Activity 沒有 Activity 棧,則無法啟動,因此需要加開啟新的棧;
  • 另外UI相關(guān)要Activity中使用

getApplication()和getApplicationContext() 區(qū)別?

  1. 對于Activity/Service來說, getApplication()和getApplicationContext()的返回值完全相同; 除非廠商修改過接口;
  2. BroadcastReceiver在onReceive的過程, 能使用getBaseContext().getApplicationContext獲取所在Application, 而無法使用getApplication;
  3. ContentProvider能使用
    getContext().getApplicationContext()獲取所在Application. 絕大多數(shù)情況下沒有問題, 但是有可能會出現(xiàn)空指針的問題, 情況如下:

當同一個進程有多個apk的情況下, 對于第二個apk是由provider方式拉起的, 前面介紹過provider創(chuàng)建過程并不會初始化所在application, 此時執(zhí)行g(shù)etContext().getApplicationContext()返回的結(jié)果便是NULL. 所以對于這種情況要做好判空.

1.5 IntentFilter是什么?有哪些使用場景?

IntentService是什么

IntentService是Service的子類,繼承與Service類,用于處理需要異步請求。用戶通過調(diào)用Context.StartService(Intent)發(fā)送請求,服務根據(jù)需要啟動,使用工作線程依次處理每個Intent,并在處理完所有工作后自身停止服務。

使用時,擴展IntentService并實現(xiàn)onHandleIntent(android.content.Intent)。IntentService接收Intent,啟動工作線程,并在適當時機停止服務。

所有的請求都在同一個工作線程上處理,一次處理一個請求,所以處理完所以的請求可能會花費很長的時間,但由于IntentService是另外了線程來工作,所以保證不會阻止App的主線程。

IntentService與Service的區(qū)別

從何時使用,觸發(fā)方法,運行環(huán)境,何時停止四個方面分析。

何時使用
Service用于沒有UI工作的任務,但不能執(zhí)行長任務(長時間的任務),如果需要Service來執(zhí)行長時間的任務,則必須手動開店一個線程來執(zhí)行該Service。
IntentService可用于執(zhí)行不與主線程溝通的長任務。

觸發(fā)方法
Service通過調(diào)用 startService() 方法來觸發(fā)。而IntentService通過Intent來觸發(fā),開啟一個新的工作線程,并在線程上調(diào)用 onHandleIntent() 方法。

運行環(huán)境
Service 在App主線程上運行,沒有與用戶交互,即在后臺運行,如果執(zhí)行長時間的請求任務會阻止主線程工作。
IntentService在自己單獨開啟的工作線程上運行,即使執(zhí)行長時間的請求任務也不會阻止主線程工作。

何時停止
如果執(zhí)行了Service,我們是有責任在其請求任務完成后關(guān)閉服務,通過調(diào)用 stopSelf() 或 stopService()來結(jié)束服務。
IntentService會在執(zhí)行完所有的請求任務后自行關(guān)閉服務,所以我們不必額外調(diào)用 stopSelf() 去關(guān)閉它。

1.6 談一談startService和bindService的區(qū)別,生命周期以及使用場景?

1、生命周期上的區(qū)別

執(zhí)行startService時,Service會經(jīng)歷onCreate->onStartCommand。當執(zhí)行stopService時,直接調(diào)用onDestroy方法。調(diào)用者如果沒有stopService,Service
會一直在后臺運行,下次調(diào)用者再起來仍然可以stopService。

執(zhí)行bindService時,Service會經(jīng)歷onCreate->onBind。這個時候調(diào)用者和Service綁定在一起。調(diào)用者調(diào)用unbindService方法或者調(diào)用者Context不存在了(如Activity被finish了),Service就會調(diào)用onUnbind->onDestroy。這里所謂的綁定在一起就是說兩者共存亡了。

多次調(diào)用startService,該Service只能被創(chuàng)建一次,即該Service的onCreate方法只會被調(diào)用一次。但是每次調(diào)用startService,onStartCommand方法都會被調(diào)用。Service的onStart方法在API 5時被廢棄,替代它的是onStartCommand方法。

第一次執(zhí)行bindService時,onCreate和onBind方法會被調(diào)用,但是多次執(zhí)行bindService時,onCreate和onBind方法并不會被多次調(diào)用,即并不會多次創(chuàng)建服務和綁定服務。

2、調(diào)用者如何獲取綁定后的Service的方法

onBind回調(diào)方法將返回給客戶端一個IBinder接口實例,IBinder允許客戶端回調(diào)服務的方法,比如得到Service運行的狀態(tài)或其他操作。我們需要IBinder對象返回具體的Service對象才能操作,所以說具體的Service對象必須首先實現(xiàn)Binder對象。

3、既使用startService又使用bindService的情況

如果一個Service又被啟動又被綁定,則該Service會一直在后臺運行。首先不管如何調(diào)用,onCreate始終只會調(diào)用一次。對應startService調(diào)用多少次,Service的onStart
方法便會調(diào)用多少次。Service的終止,需要unbindService和stopService同時調(diào)用才行。不管startService與bindService的調(diào)用順序,如果先調(diào)用unbindService,此時服務不會自動終止,再調(diào)用stopService之后,服務才會終止;如果先調(diào)用stopService,此時服務也不會終止,而再調(diào)用unbindService或者之前調(diào)用bindService的Context不存在了(如Activity被finish的時候)之后,服務才會自動停止。

那么,什么情況下既使用startService,又使用bindService呢?

如果你只是想要啟動一個后臺服務長期進行某項任務,那么使用startService便可以了。如果你還想要與正在運行的Service取得聯(lián)系,那么有兩種方法:一種是使用broadcast,另一種是使用bindService。前者的缺點是如果交流較為頻繁,容易造成性能上的問題,而后者則沒有這些問題。因此,這種情況就需要startService和bindService一起使用了。

另外,如果你的服務只是公開一個遠程接口,供連接上的客戶端(Android的Service是C/S架構(gòu))遠程調(diào)用執(zhí)行方法,這個時候你可以不讓服務一開始就運行,而只是bindService,這樣在第一次bindService的時候才會創(chuàng)建服務的實例運行它,這會節(jié)約很多系統(tǒng)資源,特別是如果你的服務是遠程服務,那么效果會越明顯(當然Servcie創(chuàng)建是會花去一定時間,這點需要注意)。

4、本地服務與遠程服務

本地服務依附在主進程上,在一定程度上節(jié)約了資源。本地服務因為是在同一進程,因此不需要IPC,也不需要AIDL。相應bindService會方便很多。缺點是主進程被kill后,服務變會終止。

遠程服務是獨立的進程,對應進程名格式為所在包名加上你指定的android:process字符串。由于是獨立的進程,因此在Activity所在進程被kill的是偶,該服務依然在運行。缺點是該服務是獨立的進程,會占用一定資源,并且使用AIDL進行IPC稍微麻煩一點。

對于startService來說,不管是本地服務還是遠程服務,我們需要做的工作都一樣簡單。

1.7 Service如何進行?;??

  • 利用系統(tǒng)廣播拉活
  • 利用系統(tǒng)service拉活
  • 利用Native進程拉活<Android5.0以后失效> fork進行監(jiān)控
  • 主進程,利用native拉活
  • 利用JobScheduler機制拉活<Android5.0以后>
  • 利用賬號同步機制拉活

1.8 簡單介紹下ContentProvider是如何實現(xiàn)數(shù)據(jù)共享的?

ContentProvider(內(nèi)容提供者):對外提供了統(tǒng)一的訪問數(shù)據(jù)的接口。
ContentResolver(內(nèi)容解析者):通過URI的不同來操作不同的ContentProvider中的數(shù)據(jù)。
ContentObserver(內(nèi)容觀察者):觀察特定URI引起的數(shù)據(jù)庫的變化。通過ContentResolver進行注冊,觀察數(shù)據(jù)是否發(fā)生變化及時通知刷新頁面(通過Handler通知主線程更新UI)。

1.9 說下切換橫豎屏時Activity的生命周期?

1.AndroidManifest沒有設置configChanges屬性豎屏啟動:

onCreate -->onStart-->onResume

切換橫屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 6.0 Android 7.0 Android 8.0)

橫屏啟動:
onCreate -->onStart-->onResume

切換豎屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 6.0 Android 7.0 Android 8.0)

總結(jié):沒有設置configChanges屬性Android 6.0 7.0 8.0系統(tǒng)手機 表現(xiàn)都是一樣的,當前的界面調(diào)用onSaveInstanceState走一遍流程,然后重啟調(diào)用onRestoreInstanceState再走一遍完整流程,最終destory。

2.AndroidManifest設置了android:configChanges="orientation"

豎屏啟動:
onCreate -->onStart-->onResume

切換橫屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 6.0)

onConfigurationChanged-->onPause -->onSaveInstanceState -->onStop ->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 7.0)

onConfigurationChanged
(Android 8.0)

橫屏啟動:
onCreate -->onStart-->onResume

切換豎屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState--> onResume -->onPause -->onStop -->onDestroy
(Android 6.0 )

onConfigurationChanged-->onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->onRestoreInstanceState-->onResume -->onPause -->onStop -->onDestroy
(Android 7.0)

onConfigurationChanged
(Android 8.0)

總結(jié):設置了configChanges屬性為orientation之后,Android6.0 同沒有設置configChanges情況相同,完整的走完了兩個生命周期,調(diào)用了onSaveInstanceState和onRestoreInstanceState方法;Android 7.0則會先回調(diào)onConfigurationChanged方法,剩下的流程跟Android6.0 保持一致;Android 8.0 系統(tǒng)更是簡單,只是回調(diào)了onConfigurationChanged方法,并沒有走Activity的生命周期方法。

3.AndroidManifest設置了android:configChanges="orientation|keyboardHidden|screenSize"

豎(橫)屏啟動:onCreate -->onStart-->onResume

切換橫(豎)屏:onConfigurationChanged (Android 6.0
Android 7.0 Android 8.0)

總結(jié):設置android:configChanges="orientation|keyboardHidden|screenSize" 則都不會調(diào)用Activity的其他生命周期方法,只會調(diào)用onConfigurationChanged方法。

4.AndroidManifest設置了android:configChanges="orientation|screenSize"

豎(橫)屏啟動:onCreate -->onStart-->onResume

切換橫(豎)屏:onConfigurationChanged (Android 6.0Android 7.0 Android 8.0)

總結(jié):沒有了keyboardHidden跟3是相同的,orientation
代表橫豎屏切換 screenSize代表屏幕大小發(fā)生了改變,設置了這兩項就不會回調(diào)Activity的生命周期的方法,只會回調(diào)onConfigurationChanged 。

5.AndroidManifest設置了android:configChanges="orientation|keyboardHidden"

總結(jié):跟只設置了orientation屬性相同,Android6.0Android7.0會回調(diào)生命周期的方法,Android8.0則只回調(diào)onConfigurationChanged。說明如果設置了orientation
和 screenSize 都不會走生命周期的方法,keyboardHidden不影響。

  1. 不設置configChanges屬性不會回調(diào)onConfigurationChanged,且切屏的時候會回調(diào)生命周期方法。
  2. 只有設置了orientation 和 screenSize 才會保證都不會走生命周期,且切屏只回調(diào)onConfigurationChanged。
  3. 設置orientation,沒有設置screenSize,切屏會回調(diào)onConfigurationChanged,但是還會走生命周期方法。
    注:這里只選擇了Android部分系統(tǒng)的手機做測試,由于不同系統(tǒng)的手機品牌也不相同,可能略微會有區(qū)別。
    另:代碼動態(tài)設置橫豎屏狀態(tài)(onConfigurationChanged當屏幕發(fā)生變化的時候回調(diào))
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    獲取屏幕狀態(tài)(int ORIENTATION_PORTRAIT = 1; 豎屏 int ORIENTATION_LANDSCAPE = 2; 橫屏)
    int screenNum = getResources().getConfiguration().orientation;

configChanges屬性

  1. orientation 屏幕在縱向和橫向間旋轉(zhuǎn)
  2. keyboardHidden 鍵盤顯示或隱藏
  3. screenSize 屏幕大小改變了
  4. fontScale 用戶變更了首選的字體大小
  5. locale 用戶選擇了不同的語言設定
  6. keyboard 鍵盤類型變更,例如手機從12鍵盤切換到全鍵盤
  7. touchscreen或navigation 鍵盤或?qū)Ш椒绞阶兓话悴粫l(fā)生這樣的事件
    常用的包括:orientation keyboardHidden screenSize,設置這三項界面不會走Activity的生命周期,只會回調(diào)onConfigurationChanged方法。

screenOrientation屬性

  1. unspecified 默認值,由系統(tǒng)判斷狀態(tài)自動切換
  2. landscape 橫屏
  3. portrait 豎屏
  4. user 用戶當前設置的orientation值
  5. behind 下一個要顯示的Activity的orientation值
  6. sensor 使用傳感器 傳感器的方向
  7. nosensor 不使用傳感器 基本等同于unspecified僅landscape和portrait常用,代表界面默認是橫屏或者豎屏,還可以再代碼中更改。

1.10 Activity中onNewIntent方法的調(diào)用時機和使用場景?

Activity 的 onNewIntent方法的調(diào)用可總結(jié)如下:
在該Activity的實例已經(jīng)存在于Task和Back stack中(或者通俗的說可以通過按返回鍵返回到該Activity )時,當使用intent來再次啟動該Activity的時候,如果此次啟動不創(chuàng)建該Activity的新實例,則系統(tǒng)會調(diào)用原有實例的onNewIntent()方法來處理此intent.

且在下面情況下系統(tǒng)不會創(chuàng)建該Activity的新實例:
1,如果該Activity在Manifest中的android:launchMode定義為singleTask或者singleInstance.
2,如果該Activity在Manifest中的android:launchMode定義為singleTop且該實例位于Backstack的棧頂.
3,如果該Activity在Manifest中的android:launchMode定義為singleTop,且上述intent包含Intent.FLAG_ACTIVITY_CLEAR_TOP標志.
4,如果上述intent中包含Intent.FLAG_ACTIVITY_CLEAR_TOP 標志和且包含Intent.FLAG_ACTIVITY_SINGLE_TOP 標志.
5,如果上述intent中包含Intent.FLAG_ACTIVITY_SINGLE_TOP 標志且該實例位于Back stack的棧頂.

上述情況滿足其一,則系統(tǒng)將不會創(chuàng)建該Activity的新實例.

根據(jù)現(xiàn)有實例所處的狀態(tài)不同onNewIntent()方法的調(diào)用時機也不同,總的說如果系統(tǒng)調(diào)用onNewIntent()方法則系統(tǒng)會在onResume()方法執(zhí)行之前調(diào)用它.這也是官方API為什么只說"you can count on onResume() being called after this method",而不具體說明調(diào)用時機的原因.

1.11 Intent傳輸數(shù)據(jù)的大小有限制嗎?如何解決?

Intent 中的 Bundle 是使用 Binder 機制進行數(shù)據(jù)傳送的,數(shù)據(jù)會寫到內(nèi)核空間, Binder 緩沖區(qū)域;
Binder 的緩沖區(qū)是有大小限制的, 有些 ROM 是 1M, 有些ROM 是 2M;
這個限制定義在frameworks/native/libs/binder/processState.cpp 類中,如果超過這個限制, 系統(tǒng)就會報錯;

#define BINDER_VM_SIZE ((1*1024*1024) - (4096*2)) ;

因為 Binder 本身就是為了進程間頻繁-靈活的通信所設計的, 并不是為了拷貝大量數(shù)據(jù);
如果非 ipc 就很簡單了, static 變量, eventBus 之類的都可以;
如果是 ipc, 一定要一次性傳大文件, 可以用 file 或者 socket;

1.12 說說ContentProvider、ContentResolver、ContentObserver 之間的關(guān)系?

ContentProvider

內(nèi)容提供者, 用于對外提供數(shù)據(jù),比如聯(lián)系人應用中就是用了ContentProvider,
一個應用可以實現(xiàn)ContentProvider來提供給別的應用操作,通過ContentResolver來操作別的應用數(shù)據(jù)

ContentResolver

內(nèi)容解析者, 用于獲取內(nèi)容提供者提供的數(shù)據(jù)
ContentResolver.notifyChange(uri)發(fā)出消息ContentObserver
內(nèi)容監(jiān)聽者,可以監(jiān)聽數(shù)據(jù)的改變狀態(tài)
觀察(捕捉)特定的Uri引起的數(shù)據(jù)庫的變化
ContentResolver.registerContentObserver()監(jiān)聽消息

概括:
使用ContentResolver來獲取ContentProvider提供的數(shù)據(jù),同時注冊ContentObserver監(jiān)聽數(shù)據(jù)的變化

1.13說說Activity加載的流程?

App 啟動流程(基于Android8.0)

  • 點擊桌面 App 圖標,Launcher 進程采用 Binder IPC(具體為ActivityManager.getService 獲取 AMS 實例)向 system_server 的 AMS 發(fā)起startActivity 請求
  • system_server 進程收到請求后,向 Zygote 進程發(fā)送創(chuàng)建進程的請求;
  • Zygote 進程 fork 出新的子進程,即 App 進程
  • App 進程創(chuàng)建即初始化 ActivityThread,然后通過Binder IPC 向 system_server 進程的 AMS 發(fā)起 attachApplication 請求
  • system_server 進程的 AMS 在收到 attachApplication請求后,做一系列操作后,通知 ApplicationThread bindApplication,然后發(fā)送 H.BIND_APPLICATION 消
  • 主線程收到 H.BIND_APPLICATION 消息,調(diào)用handleBindApplication 處理后做一系列的初始化操作,初始化 Application 等
  • system_server 進程的 AMS 在 bindApplication 后,會調(diào)用ActivityStackSupervisor.attachApplicationLocked,之后經(jīng)過一系列操作,在 realStartActivityLocked 方法通過 Binder IPC 向 App 進程發(fā)送 scheduleLaunchActivity 請求;
  • App進程的 binder 線程(ApplicationThread)在收到請求后,通過 handler 向主線程發(fā)送 LAUNCH_ACTIVITY 消息;
  • 主線程收到 message 后經(jīng)過 handleLaunchActivity,performLaunchActivity 方法,然后通過反射機制創(chuàng)建目標 Activity;
  • 通過 Activity attach 方法創(chuàng)建 window 并且和 Activity關(guān)聯(lián),然后設置 WindowManager 用來管理 window,然后通知 Activity 已創(chuàng)建,即調(diào)用 onCreate 然后調(diào)用 handleResumeActivity,Activity 可見
    補充:
  • ActivityManagerService 是一個注冊到 SystemServer 進程并實現(xiàn)了 IActivityManager 的 Binder,可以通過 ActivityManager 的 getService 方法獲取 AMS 的代理對象,進而調(diào)用 AMS 方法
  • ApplicationThread 是 ActivityThread 的內(nèi)部類,是一個實現(xiàn)了 IApplicationThread 的 Binder。AMS通過Binder IPC 經(jīng) ApplicationThread 對應用進行控制
  • 普通的 Activity 啟動和本流程差不多,至少不需要再創(chuàng)建 App 進程了
  • Activity A 啟動 Activity B,A 先 pause 然后 B 才能resume,因此在 onPause 中不能做耗時操作,不然會影響下一個 Activity 的啟動
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容