Android 四大組件面試題

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

viewModel 做數據管理,activity 和 fragment 公用同個viewModel 實現數據傳遞

1.2 LaunchMode 的應用場景?

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

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

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

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

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

1.3 BroadcastReceiver 與 LocalBroadcastReceiver 有什么區別?

BroadcastReceiver 是跨應用廣播,利用Binder機制實現,支持動態和靜態兩種方式注冊方式。

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

1.4 對于 Context,你了解多少?

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

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

對于 startActivity操作

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

getApplication()和getApplicationContext() 區別?

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

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

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

IntentService是什么

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

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

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

IntentService與Service的區別

從何時使用,觸發方法,運行環境,何時停止四個方面分析。

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

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

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

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

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

1、生命周期上的區別

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

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

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

第一次執行bindService時,onCreate和onBind方法會被調用,但是多次執行bindService時,onCreate和onBind方法并不會被多次調用,即并不會多次創建服務和綁定服務。

2、調用者如何獲取綁定后的Service的方法

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

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

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

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

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

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

4、本地服務與遠程服務

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

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

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

1.7 Service如何進行保活?

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

1.8 簡單介紹下ContentProvider是如何實現數據共享的?

ContentProvider(內容提供者):對外提供了統一的訪問數據的接口。
ContentResolver(內容解析者):通過URI的不同來操作不同的ContentProvider中的數據。
ContentObserver(內容觀察者):觀察特定URI引起的數據庫的變化。通過ContentResolver進行注冊,觀察數據是否發生變化及時通知刷新頁面(通過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)

總結:沒有設置configChanges屬性Android 6.0 7.0 8.0系統手機 表現都是一樣的,當前的界面調用onSaveInstanceState走一遍流程,然后重啟調用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)

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

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

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

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

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

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

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

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

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

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

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

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

configChanges屬性

  1. orientation 屏幕在縱向和橫向間旋轉
  2. keyboardHidden 鍵盤顯示或隱藏
  3. screenSize 屏幕大小改變了
  4. fontScale 用戶變更了首選的字體大小
  5. locale 用戶選擇了不同的語言設定
  6. keyboard 鍵盤類型變更,例如手機從12鍵盤切換到全鍵盤
  7. touchscreen或navigation 鍵盤或導航方式變化,一般不會發生這樣的事件
    常用的包括:orientation keyboardHidden screenSize,設置這三項界面不會走Activity的生命周期,只會回調onConfigurationChanged方法。

screenOrientation屬性

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

1.10 Activity中onNewIntent方法的調用時機和使用場景?

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

且在下面情況下系統不會創建該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的棧頂.

上述情況滿足其一,則系統將不會創建該Activity的新實例.

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

1.11 Intent傳輸數據的大小有限制嗎?如何解決?

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

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

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

1.12 說說ContentProvider、ContentResolver、ContentObserver 之間的關系?

ContentProvider

內容提供者, 用于對外提供數據,比如聯系人應用中就是用了ContentProvider,
一個應用可以實現ContentProvider來提供給別的應用操作,通過ContentResolver來操作別的應用數據

ContentResolver

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

概括:
使用ContentResolver來獲取ContentProvider提供的數據,同時注冊ContentObserver監聽數據的變化

1.13說說Activity加載的流程?

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

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

推薦閱讀更多精彩內容