Android 開發藝術探索筆記之一 -- Android 的生命週期和啟動模式

weixin_33866037發表於2018-07-26

學習內容:

  • Activity 的生命週期和啟動模式以及 IntentFilter 的匹配規則分析
    • 異常情況下的生命週期
    • Activity 的啟動模式以及 Flags
    • 隱式啟動下的 Intent 匹配

Activity 的生命週期全面分析

我的另一篇文章:詳解 Android&Fragment 的生命週期
此處只是記錄一下缺失的知識點,加以擴充。
建議本文與上文配合閱讀。

前置

生命週期分為兩類:

  1. 典型情況下的生命週期
  2. 另一部分是異常情況下的生命週期。

典型情況:指有使用者參與的情況下,Activity 所經過的生命週期的改變
異常情況:指 Activity 被系統回收或者由於當前裝置的 configuration 發生改變從而導致 Activity 被銷燬重建

典型情況下的生命週期

書中的兩個問題:

  1. 問題一:onStart 和 onResume、onPause 和 onStop 有什麼實質性的不同?
    答:意義不同:onStart 和 onStop 是從Activity 是否可見這個角度來回撥;onResume 和 onPause 是從 Activity 是否處在前臺這個角度來回撥

  2. 問題二:當前 Activity 為 A,此時開啟新的 Activity B,那麼 B 的 onResume 和 A 的 onPause 哪個先執行?
    答:舊的Activity(A)執行完 onPause 之後,新的 Activity(B)的 onResume 才能執行。
    這個問題在我原來寫的那篇文章中也提到過,只不過我得出這個結論是寫了個 Demo,測試得來的。而主席這裡,從原始碼分析得到結論,之後再通過例項測試驗證結論。可以說,我只是知道了這個結論就是這樣,但是卻不知道為什麼,原理是什麼。

異常情況下的生命週期

書中的兩種情況:

  1. 情況1:資源相關的系統配置發生改變導致 Activity 被殺死並重新建立。
    • 系統配置發生改變後,Activity 會被銷燬,onPause、onStop、onDestroy 正常呼叫。
    • Activity 是異常情況下終止,因而會呼叫 onSaveInstanceState 方法儲存狀態。
    • 異常情況下終止,需要重建時,系統會預設為我們儲存當前 Activity 的檢視結構。
    • 儲存和恢復 View 層級結構的工作流程(以儲存為例,恢復過程類似):委託思想
      1. 首先 Activity 意外終止時,呼叫 onSavedInstanceState 儲存資料
      2. 然後 Activity 委託 Window 儲存資料。
      3. 接著 WIndow 再委託它上面的頂級容器儲存資料(頂級容易是一個 ViewGroup 一般是 DecorView)
      4. 最後頂級容器一一通知它的子元素來儲存資料。
  2. 情況2:資源記憶體不足導致低優先順序的 Activity 被殺死
    • 優先順序排序
      • 前臺 > 可見但非前臺 -> 後臺
    • 後臺任務保活:將後臺任務放入 Service 從而保證程式有一定的優先順序,這樣不會輕易被殺死。

如何在系統配置發生改變後,避免 Activity 重建?

答:通過為 Activity 指定 configChanges 屬性,以此在特定情況下不讓系統重建 Activity,此時系統不會儲存恢復資料,而是呼叫了 onConfigurationChanged 方法,在此方法中我們可以做一些自己的特殊處理。
常用的屬性為 locale、orientation、keyboardHidden 這三個:locale 指裝置本地設定發生改變,一般是系統語言;orientation 指螢幕方向發生改變;keyboardHidden 指鍵盤的可訪問性發生了改變,比如用於調出了鍵盤。


Activity 的啟動模式

我的另一篇文章:深入理解 Android 的啟動模式
此處只是記錄一下缺失的知識點,加以擴充。
建議本文與上文配合閱讀。

Activity 的 LaunchMode

四種模式:

  1. standard:標準模式
  2. singleTop:棧頂複用模式
  3. singleTask:棧內複用模式
  4. singleInstance:單例項模式

singleTask 下的特殊情況:

假設目前有 2 個任務棧,前臺任務棧為 AB,後臺任務棧為 CD,且 CD 的啟動模式都是 singleTask:

  1. 如果此時啟動 D:整個後臺任務棧都被切換到前臺,即前臺此時為 ABCD。
  2. 如果此時啟動 C:那麼 D 會因為 singleTask 模式預設具備 clearTop 的效果而出棧,此時前臺任務棧為 ABC。

TaskAffinity 基礎

  • TaskAffinity 標識 Activit有所需要的任務棧的名字。
  • 該屬性主要與 singleTask 啟動模式或者 allowTaskReparenting 屬性配對使用。

TaskAffinity + singleTask

待啟動的 Activity 會執行在名字和 TaskAffinity 相同的任務棧中

TaskAffinity + allowTaskReparenting

當一個應用 A 啟動了應用 B 的某個 Activity 後,如果這個 Activity 的 allowTaskReparenting 屬性為 true 的話,那麼當應用 B 被啟動後,此 Activity 會直接從應用 A 的任務棧轉移到應用 B 的任務棧中。

Activity 的 Flags

見我的另一篇文章:深入理解 Android 的啟動模式


IntentFilter 的匹配規則

啟動 Activity 分為兩種:顯式呼叫和隱式呼叫。

  1. 顯式呼叫需要明確指定被啟動物件的元件資訊,包括包名和類名
  2. 隱式呼叫不需要明確指定元件資訊。
  3. 如果二者共存,以顯式呼叫為主。

隱式呼叫的匹配規則

隱式呼叫需要 Intent 能夠匹配目標元件的 IntentFilter 中所設定的過濾資訊,如果不匹配將無法啟動目標 Activity。

IntentFilter 中的過濾資訊有 action、category、data。

  1. 當三者都匹配時,IntentFilter 才匹配成功,才能啟動目標 Activity。
  2. 一個 Activity 可以有多個 intent-filter,一個 Intent 只要匹配任何一組 intent-filter 即可成功啟動對應的 Activity

1.action 的匹配規則

action 的匹配規則是 Intent 中的 action 必須能夠和過濾規則中的 action 匹配,這裡匹配指 action 的字串值完全一致。
一個過濾規則中可能有多個 action,只要 Intent 中的 action 能夠和過濾規則中的任何一個 action 相同都可匹配成功。反之,任何一個都不相同,則匹配失敗。

需要注意:action 區分大小寫。

2.category 的匹配規則

Intent 可以沒有 category,但是如果一旦有 category,那麼不管有幾個,每個都要能和過濾規則中的任何一個 category 相同。
startActivity 或者 startActivityForResult 方法啟動 Activity 時,預設會為 Intent 加上 android.intent.category.DEFAULT 這個 category。

3.data 的匹配規則

和 action 的匹配規則類似,Intent 必須含有 data 資料,並且 data 資料能夠完全匹配過濾規則中的某一個 data,這裡的完全匹配是指過濾規則中出現的 data 部分也出現在了 Intent 中的 data。

先介紹下data 的結構:

<data android:scheme="string"
           android:host="string"
           android:port="string"
           android:path="string"
           android:pathPattern="string"
           android:pathPrefix="string"
           android:mimeType="string"

data 由兩部分組成:mimeType 和 URI。mime Type 指媒體型別,比如 image/jpeg、audio/mpeg4-generic 和 video/* 等,可以表示圖片、文字、視訊等不同的媒體格式;而 URI 包含的資料很多,URI 的結構如下:

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

//例項
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
  • Scheme:URI 的模式,如 http、file、content等,如果未指定,則 該URI無效。
  • Host:URI 的主機名,比如 www.baidu.com,如果未指定,則 該URI無效
  • Port:URI 的埠號,比如 80。
  • Path、pathPattern 和 pathPrefix:三者表述路徑資訊,其中 path 表示完整路徑資訊;pathPattern 也表示完整的路徑資訊,但是可以包含萬用字元 "*";pathPrefix 表示路徑的字首資訊。

下面說一下 data 的指定:

  1. 指定完整的 data,必須呼叫 setDataAndType 方法
  2. setData 和 setType 互相沖突,會彼此消除對方的值。

避免隱式呼叫出錯

當隱式啟動一個 Activity 的時候,如果未加判斷,找不到匹配的 Activity,那麼會丟擲 android.content.ActivityNotFoundException 異常,解決方案:

  1. PackageManager 的 resolveActivity 方法或者 Intent 的 resolveActivity 方法:返回最佳匹配的 Activity 資訊,如果找不到匹配的 Activity 就會返回 null
  2. PackageManager 的 queryIntentActivities 方法:返回 所有成功匹配的 Activity 資訊。

補充說明:

  1. 不含 DEFAULT 這個 category 的 Activity 無法接收隱式 Intent
  2. 對於 Service,儘量使用顯式呼叫方式來啟動服務
  3. 通過以下設定表明入口 Activity,並且出現在系統的應用列表:
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    

相關文章