引用上文生命周期和launchMode介紹, Activity的生命周期實際上比我們想象的復(fù)雜得多.
本文主要通過實例, 來探索下Activity的啟動Intent Flag以及taskAffinity對生命周期和Task/Back Stack的影響. 算是對生命周期和launchMode的一個補充, 以便我們在開發(fā)過程中靈活組合運用.
照例, 我們先從一些官方解釋開始:
1, 相關(guān)概念
-
對生命周期和Task/Back Stack有影響的Intent Flag主要有:
- FLAG_ACTIVITY_NEW_TASK
- FLAG_ACTIVITY_CLEAR_TOP
- FLAG_ACTIVITY_SINGLE_TOP
-
FLAG_ACTIVITY_NEW_TASK
- 會產(chǎn)生與 "singleTask" launchMode 值相同的行為.
- 在新任務(wù)中啟動Activity. 如果已有包含該Activity的任務(wù),則該任務(wù)會轉(zhuǎn)到前臺并恢復(fù)其最后狀態(tài)圈盔,同時該Activity會在onNewIntent()中收到新Intent.
-
FLAG_ACTIVITY_SINGLE_TOP
- 會產(chǎn)生與 "singleTop" launchMode 值相同的行為.
- 如果正在啟動的Activity是當前Activity(位于返回棧的頂部), 則現(xiàn)有實例會接收對 onNewIntent()的調(diào)用拐袜,而不是創(chuàng)建 Activity 的新實例.
-
FLAG_ACTIVITY_CLEAR_TOP
- 如果正在啟動的 Activity 已在當前任務(wù)中運行胁镐,則會銷毀當前任務(wù)頂部的所有 Activity甜无,并通過 onNewIntent() 將此 Intent 傳遞給 Activity 已恢復(fù)的實例(現(xiàn)在位于頂部)汁针,而不是啟動該 Activity 的新實例.
- 如果指定 Activity 的啟動模式為 "standard"答朋,則該 Activity 也會從堆棧中刪除碗旅,并在其位置啟動一個新實例渡处,以便處理傳入的 Intent。 這是因為當啟動模式為 "standard" 時祟辟,將始終為新 Intent 創(chuàng)建新實例.
以上為官方文檔解釋.
在探索Activity之launchMode一文中我們也提到了實際上文檔由于"年久失修"沒有跟上, 有些解釋是不合理的.
我們可以跟隨實例一起看下.
2, 開始探索
借用上次探索生命周期的Demo程序.
Github源碼地址
通過AActivity, BActivity, CActivity這三個Activity之間的跳轉(zhuǎn)來進行intent flag的探索.
如果沒有特別之處, 默認A, B, C三個Activity的launchMode都是默認的standard模式.
2.1, FLAG_ACTIVITY_NEW_TASK
2.1.1, 執(zhí)行B -> A, B啟動A時加FLAG_ACTIVITY_NEW_TASK
實驗?zāi)康氖强聪? 在當前系統(tǒng)沒有A實例時, 用FLAG_ACTIVITY_NEW_TASK來啟動A會不會將A創(chuàng)建在單獨的任務(wù)中.
BActivity.java中:
startActivity(new Intent(BActivity.this, AActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=3:
Task id #35
* TaskRecord{42b60ae0 #35 A=com.anly.samples U=0 sz=3}
numActivities=3 rootWasReset=false userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
affinity=com.anly.samples
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
realActivity=com.anly.samples/.MainActivity
Activities=[ActivityRecord{4285c1b0 u0 com.anly.samples/.MainActivity t35},
ActivityRecord{42decc00 u0 com.anly.samples/.activity.BActivity t35},
ActivityRecord{4372b9e8 u0 com.anly.samples/.activity.AActivity t35}]
可以看到:
B以FLAG_ACTIVITY_NEW_TASK啟動A, A仍然和B處在同一個Task中.
2.1.2 執(zhí)行A -> B -> A, B啟動A時加FLAG_ACTIVITY_NEW_TASK
實驗?zāi)康氖窍腧炞C下官方文檔對FLAG_ACTIVITY_NEW_TASK的解釋, 在A實例已經(jīng)存在的情況下, 以FLAG_ACTIVITY_NEW_TASK啟動A會發(fā)生什么.
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=2:
Task id #34
* TaskRecord{42bfb088 #34 A=com.anly.samples U=0 sz=4}
numActivities=4 rootWasReset=false userId=0 mTaskType=0 numFullscreen=4 mOnTopOfHome=true
affinity=com.anly.samples
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
realActivity=com.anly.samples/.MainActivity
Activities=[ActivityRecord{42568318 u0 com.anly.samples/.MainActivity t34},
ActivityRecord{42725050 u0 com.anly.samples/.activity.AActivity t34},
ActivityRecord{42dab240 u0 com.anly.samples/.activity.BActivity t34},
ActivityRecord{42e293f8 u0 com.anly.samples/.activity.AActivity t34}]
可以看到:
1, 在B啟動A之前, Task #34中本來就有A, 但是B加FLAG_ACTIVITY_NEW_TASK啟動A時, A并未重用, 而是在本Task #34中在此創(chuàng)建了一個A的實例. (這點和文檔描述不一致)
2, 此時Task #34中的Activity實例為ABA.
3, 但是如果A的lunchMode是singleTask的話, 如lunchMode一文2.2.3所示, 此時應(yīng)該銷毀A以上的實例, Task中只剩下A.
4, 綜上, FLAG_ACTIVITY_NEW_TASK并不等同與singleTask. 且FLAG_ACTIVITY_NEW_TASK感覺并為起作用(在A已經(jīng)存在一個實例的情況下).
2.2, FLAG_ACTIVITY_SINGLE_TOP
2.2.1, 執(zhí)行A -> B -> B, 其中B啟動B時加FLAG_ACTIVITY_SINGLE_TOP
BActivity啟動B時加:
startActivity(new Intent(BActivity.this, BActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=6:
Task id #38
* TaskRecord{43665a30 #38 A=com.anly.samples U=0 sz=3}
numActivities=3 rootWasReset=true userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
affinity=com.anly.samples
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.anly.samples/.MainActivity}
realActivity=com.anly.samples/.MainActivity
Activities=[ActivityRecord{42bbfea0 u0 com.anly.samples/.MainActivity t38},
ActivityRecord{433b6130 u0 com.anly.samples/.activity.AActivity t38},
ActivityRecord{4324ef18 u0 com.anly.samples/.activity.BActivity t38}]
可以看到:
1, B復(fù)用了, 通過onNewIntent, 走onResume流程, 復(fù)用之前的B實例.
2, 此時Task #38中的Activity實例為AB.
2.3, FLAG_ACTIVITY_CLEAR_TOP
2.3.1, 執(zhí)行A -> B -> A, 啟動B啟動A時加FLAG_ACTIVITY_CLEAR_TOP
實驗?zāi)康氖菫榱丝聪翧會不會重用, 且B會不會被Clear.
BActivity啟動A的代碼:
startActivity(new Intent(BActivity.this, AActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=7:
Task id #39
* TaskRecord{4274e020 #39 A=com.anly.samples U=0 sz=2}
numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=true
affinity=com.anly.samples
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
realActivity=com.anly.samples/.MainActivity
Activities=[ActivityRecord{42742598 u0 com.anly.samples/.MainActivity t39},
ActivityRecord{4274eb28 u0 com.anly.samples/.activity.AActivity t39}]
可以看到:
1, A并沒有重用, 而是新建了一個實例. 這個和文檔是一致的, 參見FLAG_ACTIVITY_CLEAR_TOP概念第二點.
2, B被銷毀了(Clear Top).
3, 此時Task #39中只有A(一個新的A).
2.4, 組合使用
以上是簡單使用, 然后實際場景中會有很多組合使用Intent Flag以及Intent Flag與taskAffinity結(jié)合使用的情況. 其中官方文檔就提到了:
FLAG_ACTIVITY_CLEAR_TOP 通常與 FLAG_ACTIVITY_NEW_TASK 結(jié)合使用医瘫。一起使用時,通過這些標志旧困,可以找到其他任務(wù)中的現(xiàn)有 Activity醇份,并將其放入可從中響應(yīng) Intent 的位置。
下面我們來實驗下這種組合:
2.4.1, FLAG_ACTIVITY_CLEAR_TOP + FLAG_ACTIVITY_NEW_TASK
執(zhí)行A -> B -> A, 其中B啟動A時, Intent flag加FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK
B啟動A的代碼:
startActivity(new Intent(BActivity.this, AActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK));
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=8:
Task id #40
* TaskRecord{429c96b0 #40 A=com.anly.samples U=0 sz=2}
numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=true
affinity=com.anly.samples
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
realActivity=com.anly.samples/.MainActivity
Activities=[ActivityRecord{427907d0 u0 com.anly.samples/.MainActivity t40},
ActivityRecord{42dd24b8 u0 com.anly.samples/.activity.AActivity t40}]
可以看到:
1, 結(jié)果和2.3.1單獨使用Intent.FLAG_ACTIVITY_CLEAR_TOP是一樣的.
2.5, taskAffinity補充實驗
有2.1.1, 2.1.2以及2.4.1這三個包含Intent.FLAG_ACTIVITY_NEW_TASK的實驗, 可以看到, 字面上Intent.FLAG_ACTIVITY_NEW_TASK的意思是在新的task啟動Activity, 然而事實上, 新Activity還是在原來的task上創(chuàng)建的.
這里有必要提出官網(wǎng)關(guān)于taskAffinity的解釋了:
taskAffinity指示Activity優(yōu)先屬于哪個task. 默認情況下, 同一應(yīng)用中的所有 Activity 彼此關(guān)聯(lián). 因此, 默認情況下, 同一應(yīng)用中的所有 Activity 優(yōu)先位于相同任務(wù)中.
taskAffinity在兩種情況下會起作用:
--- 啟動 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 標志.
--- Activity 將其 allowTaskReparenting 屬性設(shè)置為 "true".
讓我們來結(jié)合taskAffinity做下實驗:
2.5.1, taskAffinity + FLAG_ACTIVITY_NEW_TASK
設(shè)置A和B不同的taskAffinity, 執(zhí)行Main -> A -> B -> A -> B -> A, 其中B啟動A使用FLAG_ACTIVITY_NEW_TASK
為什么要執(zhí)行兩次B -> A? 我們跟隨實驗結(jié)果, 稍后來看.
設(shè)置A的taskAffinity為com.anly.aactivity, B默認(包名).
<activity
android:name=".activity.AActivity"
android:label="A-Activity"
android:taskAffinity="com.anly.aactivity"
>
</activity>
<activity
android:name=".activity.BActivity"
android:label="B-Activity"
>
</activity>
B啟動A:
startActivity(new Intent(BActivity.this, AActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=10:
Task id #44
* TaskRecord{43085768 #44 A=com.anly.aactivity U=0 sz=2}
numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=false
affinity=com.anly.aactivity
intent={flg=0x10000000 cmp=com.anly.samples/.activity.AActivity}
realActivity=com.anly.samples/.activity.AActivity
Activities=[ActivityRecord{4303fe00 u0 com.anly.samples/.activity.AActivity t44}, ActivityRecord{4324bb10 u0 com.anly.samples/.activity.BActivity t44}]
Task id #43
* TaskRecord{426d0a78 #43 A=com.anly.samples U=0 sz=3}
numActivities=3 rootWasReset=true userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
affinity=com.anly.samples
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.anly.samples/.MainActivity}
realActivity=com.anly.samples/.MainActivity
Activities=[ActivityRecord{4256ae80 u0 com.anly.samples/.MainActivity t43},
ActivityRecord{4372db08 u0 com.anly.samples/.activity.AActivity t43},
ActivityRecord{4273f478 u0 com.anly.samples/.activity.BActivity t43}]
可以看到:
1, Stack #1中有兩個Task, #43(affinity=com.anly.samples)和#44(affinity=com.anly.aactivity).
2, 第一輪Main -> A -> B時, 再Task #43中產(chǎn)生了Main,A,B三個Activity實例.
3, 接著B -> A時, A在一個新的Task #44中創(chuàng)建了新的A實例.
4, 然后A -> B, 因為B不加任何參數(shù)(啟動模式, affinity, flag等), B會創(chuàng)建在啟動他的Activity也就是A所在的Task.
5, 此時Task #44中就有了A, B.
6, 再次在B中點擊啟動A(攜帶Intent.FLAG_ACTIVITY_NEW_TASK)時. 并沒有任何反應(yīng).
為什么會出現(xiàn)第6點描述的這樣的問題呢?
我理解:
此時B啟動A, 因為攜帶Intent.FLAG_ACTIVITY_NEW_TASK, 且A的taskAffnity為"com.anly.aactivity". 系統(tǒng)會在affinity=com.anly.aactivity的Task中找有沒有已經(jīng)存在的A的實例, 發(fā)現(xiàn)Task #44中有. 于是乎, 想重用A. 然而并沒有能銷毀B, 讓A彈出來接收新的Intent.
所以說, 這種情況下, Intent.FLAG_ACTIVITY_NEW_TASK必須結(jié)合Intent.FLAG_ACTIVITY_CLEAR_TOP來一起用.
讓我們再做個實驗驗證下想法.
2.5.1, taskAffinity + FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP
設(shè)置A和B不同的taskAffinity, 執(zhí)行Main -> A -> B -> A -> B -> A, 其中B啟動A使用FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP
A的affinity還是設(shè)置成com.anly.aactivity, B默認.
B啟動A的代碼:
startActivity(new Intent(BActivity.this, AActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP));
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=11:
Task id #46
* TaskRecord{4338bc08 #46 A=com.anly.aactivity U=0 sz=1}
numActivities=1 rootWasReset=false userId=0 mTaskType=0 numFullscreen=1 mOnTopOfHome=false
affinity=com.anly.aactivity
intent={flg=0x14000000 cmp=com.anly.samples/.activity.AActivity}
realActivity=com.anly.samples/.activity.AActivity
Activities=[ActivityRecord{42d88890 u0 com.anly.samples/.activity.AActivity t46}]
Task id #45
* TaskRecord{42eee4d0 #45 A=com.anly.samples U=0 sz=3}
numActivities=3 rootWasReset=false userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
affinity=com.anly.samples
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
realActivity=com.anly.samples/.MainActivity
Activities=[ActivityRecord{42ed9690 u0 com.anly.samples/.MainActivity t45}, ActivityRecord{42e507b8 u0 com.anly.samples/.activity.AActivity t45}, ActivityRecord{42714cd0 u0 com.anly.samples/.activity.BActivity t45}]
可以看到:
果然, 此時第二次B -> A, 有效果了, 跳轉(zhuǎn)到A了.
然而, 我們發(fā)現(xiàn), 雖然Task #46中只有一個A(B被clear top,銷毀了). 然而A并不是重用的, 而是先銷毀了然來的A實例, 重建了一個A實例.
參見相關(guān)概念I(lǐng)ntent.FLAG_ACTIVITY_CLEAR_TOP的第二點解釋, 的確是這樣, 因為A是默認的standard模式, 所以必須新創(chuàng)建實例.
3, 結(jié)論
至此, 我們關(guān)于Intent flag和taskAffinity的實驗結(jié)束, 我們來看下相關(guān)結(jié)論:
FLAG_ACTIVITY_NEW_TASK并不像官方文檔所說的等同與singleTask.
在沒有任何其他flag組合和taskAffinity設(shè)置的情況下, 同一應(yīng)用內(nèi)FLAG_ACTIVITY_NEW_TASK啟動另外一個Activity, 不會在新的Task中創(chuàng)建實例, 也不會有實例復(fù)用.
FLAG_ACTIVITY_SINGLE_TOP作用等同與singleTop, 當Task的top Activity是該Activity時, Activity復(fù)用.
FLAG_ACTIVITY_CLEAR_TOP會clear top, 也就是說如果Task中有ABCD, 在D中啟動B, 會clear掉B以上的CD. CD銷毀.
注意, FLAG_ACTIVITY_CLEAR_TOP并不意味著重用, 默認Activity為standard模式的話, 只是會clear其top的其他Activity實例, 該Activity并不會重用, 而是也會銷毀, 然后創(chuàng)建一個新的該Activity實例來響應(yīng)此Intent.
在沒有設(shè)置taskAffinity的情況下, 同一應(yīng)用內(nèi)FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP組合啟動另外一個Activity, 作用和單獨使用FLAG_ACTIVITY_CLEAR_TOP是一樣.(此點類同與第二點)
如taskAffinity解釋的一樣, 在我們沒有引入taskAffinity的2.1, 2.2, 2.3, 2.4的相關(guān)實驗中, 同一個應(yīng)用中, 使用各種Intent flag都并不會創(chuàng)建新的Task.
taskAffinity需結(jié)合FLAG_ACTIVITY_NEW_TASK使用, 此時會再新的Task中尋找/創(chuàng)建待啟動的Activity實例.
強烈建議FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP結(jié)合使用, 單獨使用FLAG_ACTIVITY_NEW_TASK可能會遇到2.5.1那樣的問題.
Intent Flag并不能代替launchMode, 至少在想重用Activity的情況下, 你需要做的是考慮launchMode而非Intent Flag.
個人理解, Intent Flag更多是傾向于用來做Task中的Activity組織. 而launchMode兼顧Task組織和Activity實例的重用.