Activity的生命週期全面分析
Activity的生命週期可分爲兩部分,一種是在正常的執行過程中的生命週期,另一種則是在執行過程中發生異常情況的生命週期,這兩種情況在實際開發中都有着的極爲重要的作用,因此弄清楚這裏面的關係就顯得十分必要。
典型情況下的生命週期
- 由於正常情況下的生命週期已經是一個老生常談的問題,這裏也就不再多費口舌,給出一張Google官方的流程圖便能看出其調用順序。
-
額外補充的說明:
- 針對一個特定的Activity,第一次啓動,回調如下:
onCreate
->onStart
->onResume
- 當用戶打開新的Activity或者切換到桌面的時候,回調如下:
onPause
->onStop
,特殊情況:新的Activity採用了透明主題,那麼當前Activity不會調用onStop
- 當用戶再次回到原Activity時,回調如下:
onRestart
->onStart
->onResume
- 當用戶按back鍵回退時,回調如下:
onPause
->onStop
->onDestroy
- 當Activity被系統回收後再次打開,此時生命週期任然是和第一次啓動一樣,不過其過程多了參數的保存和恢復,後面會集體講到
- 各個生命週期都存在着一一配對的關係,可以看成是對稱的,正因爲有這個特性,很多時候在對稱的回調裏做註冊和反註冊
- 針對一個特定的Activity,第一次啓動,回調如下:
-
onStart
對應onStop
,onResume
對應onPause
,使用中,二者極其類似,其之所以這樣存在,是應爲出發的角度不同。前者從Activity是否可見的角度來回調,後者從Activity是否位於前臺來回調。 -
一個有趣的問題:當前 Activity 爲 A,在此時打開 Activity B,那麼 B 的
onResume
先執行還是 A 的onPause
先執行?回答這個問題並不難,這裏就是採用 閱讀源碼 + 實驗驗證 的方式,在源碼中新的Activity啓動之前,棧頂的Activity需要先
onPause
後,新的 Activity 才能啓動。同時在 Activity 的生命週期的回調中輸出日誌,驗證正確性。最後的結論就是 A 的 Activity 先onPause
,然後 B 的 Activity 再啓動。在onPause
與onStop
中都不可執行耗時操作,尤其是在onPause
中。 -
另外,在Android的說明文檔中還有着這樣一張圖,看圖說話
異常情況下的生命週期
情況1:資源相關的系統配置發生改變導致 Activity 被殺死並重新創建
-
一個簡單的例子,在橫豎屏切換的時候,在默認配置的情況下,Activity 就會銷燬並重建,那麼在這個過程中發生的事情自然就是數據的保存和恢復了。
-
這裏依舊是調用了之前圖片中的
onSaveInstanceState
,用以保存數據,這個調用發生在onStop
之前,但其順序與onPause
沒有關係,可能在之前也可能在之後。而當 Activity 被重建之時,會調用onRestoreInstanceState
來恢復數據,其調用時機在onStart
之後,值得注意的是,這個狀態值Bundle
會同時傳遞給onCreate
和onRestoreInstanceState
,也就是說在沒有數據保存的時候,onCreate
的形參Bundle
爲null
,而當有數據保存的時候則不爲null
。 -
在系統調用
onSaveInstanceState
與onRestoreInstanceState
的時候,會默認爲我們保存當前 Activity 的視圖結構以及恢復數據。這其中使用的是委託思想,一級一級委託上層保存元素,在一級一級通知下層恢復數據。這種思想在view
繪製和事件分發中都有着應用。
情況2:資源內存不足導致低優先級的 Activity 被殺死
-
Activity有着優先級一說,優先級越高,越不容易被系統殺死。按照其高低可分爲以下三種
- 前臺 Activity——正在和用戶交互的 Activity,這個 Activity 幾乎不可能被系統殺死,殺死的話會嚴重影響用戶體驗
- 可見但非前臺 Activity——比如 Activity 中彈出一個對話框,導致 Activity 可見但是位於後臺無法直接和用戶直接交互,這種 Activity 也不容易被殺死
- 後臺 Activity ——已經被暫停的 Activity,比如執行了
onStop
,優先級最低,在內存不足的時候會優先回收此類 Activity
-
另外說明一點,在沒有四大組件執行的時候,進程就會很快被殺死。
禁止重建
- 就針對以上 Activity 的重建來說,能否在某些內容發生改變時不重建 Activity 呢?答案是肯定的,這時候就需要在
AndroidManifest.xml
中配置android:configChanges
了,其目的是告訴系統不要重建在指定的變化時不要重建 Activity,其配置值有很多,但常用的只有screenSize
(屏幕尺寸信息發生改變,例如屏幕旋轉),smallestScreenSize
(物理屏幕尺寸發生改變,例如外接屏幕)和keyboardHidden
(鍵盤的可訪問性發生改變,例如調出虛擬鍵盤)
Activity的啓動模式
啓動模式的必要性:爲了更好的對任務棧進行管理
Activity的LaunchMode
standard
:標準模式,系統默認的啓動模式,每次伴隨 Activity 的啓動而創建一個新的實例,不管這個實例是否已經存在。在這種啓動模式下,注意兩點,第一:每次 Activity 的啓動都會調用完整的 Activity 生命週期流程。第二:誰啓動了這個 Activity,那麼這個 Activity 就會運行在啓動它的那個 Activity 所在的棧中,用一個例子來說明,Activity A 啓動了 Activity B(標準模式),那麼 B 就會進入 A 所在的棧中。這裏引申出一個問題:用 ApplicationContext 啓動標準模式的 Activity 會報錯,這是因爲非 Activity 類型的 Context 是不存在任務棧一說的,所以就會報錯,當添加了 FLAG_ACTIVITY_NEW_TASK 時,便不會有問題,這是因爲當存在這個Flag的時候,啓動時就會創建出一個新的任務棧,而這時候啓動的 Activity 實際上是以 singleTask 模式啓動的。singleTop
:棧頂複用模式。這種模式下,新的 Activity 已經位於棧頂,那麼此 Activity 實例將不再創建,並且回調onNewIntent
方法。但如果新的 Activity 不在棧頂,不管其是否存在都會新建 Activity 的實例,並將新建的實例置於棧頂。singleTask
:棧內複用模式。簡單來說,這就是 Activity 實例的單例模式,只要棧中存在 Activity 實例,就不會創建這個 Activity 的新實例,同時會回調onNewIntent
。對於Activity A,當棧中不存在 A 需要的任務棧,那麼便會創建一個新的任務棧,同時將 A 放入新的任務棧中;如果存在 A 需要的任務棧,此時若 A 的實例存在,那麼 A 會被調到棧頂(會清空 A 之上的實例),並且執行onNewIntent
,如果 A 的實例不存在,那麼便會創建 A 的實例,並將其放入棧中。singleInstance
:單實例模式。加強的singleTask模式,擁有其全部特性。具有此模式的 Activity 只能單獨位於一個任務棧,並且只有這麼一個實例存在。
默認情況下所有 Activity 所需的任務棧的名字爲應用的包名,而任務棧可以通過TaskAffinity
標識,TaskAffinity
屬性主要和singleTask
啓動模式或者allowTaskReparenting
屬性配對使用,在其他情況下沒有意義。任務棧分爲前臺任務棧與後天任務棧,後臺任務棧中的 Activity 位於暫停狀態,用戶可以通過切換將後臺任務棧再次調到前臺。
當TaskAffinity
和singleTask
啓動模式配對使用時,他是具有該模式的 Activity 的目前任務棧的名字,待啓動的 Activity 就會運行在名字和TaskAffinity
相同的任務棧中。
當TaskAffinity
和allowTaskReparenting
結合使用的時候,當一個應用 A 啓動了應用 B 的某個 Activity 後,如果這個 Activity 的allowTaskReparenting
屬性爲true
的話,那麼應用 B 被啓動後,此 Activity 會直接從應用 A 的任務棧轉移到應用 B 的任務棧中。
指定 Activity 的啓動模式主要有兩種方式,一種是在AndroidManifest.xml
中指定,另外一種是通過Intent
的標誌位來指定。
<activity
android:name="com.cj5785.demo.MainActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"
android:label="@string/app_name" />
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
二者的區別在於:
1. 優先級不同,代碼添加的優先級高於XML中添加,同時存在時,以代碼爲準
2. 範圍不同,XML方式無法設置`FLAG_ACTIVITY_CLEAR_TOP`,代碼無法設置`singleInstance`
查看任務棧信息:adb shell dumpsys activity
關於任務棧,當前臺任務棧都出棧時,回將後臺任務棧調至前臺。
Activity的Flags
Activity 的 Flags 有很多,除了幾個常用的,其餘可以在用到的時候去文檔查詢
FLAG_ACTIVITY_NEW_TASK
爲 Activity 指定singleTask
啓動模式,效果和在XML中指定該啓動模式相同
FLAG_ACTIVITY_SINGLE_TOP
爲 Activity 指定singleTop
啓動模式,效果和在XML中指定該啓動模式相同
FLAG_ACTIVITY_CLEAR_TOP
具有此標記的的 Activity,當其啓動時,在同一個任務棧中所有位於它上面的 Activity 都要出棧
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有這個標記的 Activity 不會出現在歷史 Activity 列表中,等同於XML中的android:excludeFromRecents="true"
IntentFilter的匹配規則
Activity 的啓動分爲兩種,隱式調用和顯式調用,而 IntentFilter 正是用來匹配隱式調用的。IntentFilter 中的過濾信息有action,category,data。只有一個 Intent 同時匹配一組action,category,data纔算完全匹配,也只有完全匹配才能啓動目標 Activity,一個 Activity 可以包含多個intent-filter
,而intent-filter
中正是包含了三個信息的組合。
action的匹配規則
action 是一個字符串,系統預定義了一些 action,也可以自定義 action。action的匹配要求字符串完全一致(區分大小寫),一個intent-filter
可以存在多個action,只要能夠與其中任意一個完全一致則視爲匹配成功。也就是說,action 的匹配要求 Intent 中的 action 存在且必須和過濾規則中的其中一個 action 相同。
category的匹配規則
category 是一個字符串,系統預定義了一些 category,也可以自定義 category。category 匹配要求 Intent 中如果有 category,那麼所有的 category 都必須和過濾規則中的其中一個 category 相同。如果不寫,那麼在startActivity的時候會默認加上android.intent.category.DEFAULT
,爲了能夠接受隱式調用,也必須加上android.intent.category.DEFAULT
。
data的匹配規則
data 的匹配規則與 action 類似,如果過濾規則中定義了 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。
URI的默認值爲content和file,也就是說如果指定了,那麼就必須是content或者file
<data android:mimeType="image/*" />
intent.setDataAndType(Uri.parse("file://abc"), "image/*");
intent的setData
和setType
會使得後調用的清除先調用的值
隱式調用啓動 Activity,如果沒有能夠匹配的項,就會報錯ActivityNotFoundException
,這種情況一般使用PackageManager
的resolveActivity
或者Intent
的resolveActivity
方法先做判斷,找不到匹配項則會返回null
,其參數第二位爲標誌爲,一般設置爲MATCH_DEFAULT_ONLY
,以防止匹配到不含 default category 的 Activity,而那些 Activity 是無法通過隱式調用啓動的。