前言
上一篇聊到了Activity在啟動過程中創(chuàng)建的相關(guān)對象,知道在AMS等系統(tǒng)服務(wù)中管理的Activity對象實際上是ActivityRecord對象,內(nèi)部包含了一些列的跟Activity相關(guān)的成員屬性,在應(yīng)用開發(fā)中,我們經(jīng)常也會使用到launchMode屬性,這個屬性就會關(guān)聯(lián)Activity任務(wù)棧的概念,當(dāng)配置不同的啟動模式,會影響Activity回退的頁面順序,那么接下來就聊一下什么是任務(wù)棧,任務(wù)棧和啟動模式之間的關(guān)系
Task
我們知道當(dāng)執(zhí)行startActivity()方法打開一個新的Activiy頁面,就會伴隨實例化一個ActivityRecord對象指代當(dāng)前的Activity,在上一篇的ActivityRecord數(shù)據(jù)結(jié)構(gòu)中,持有了一個Task類型的成員屬性,可以看出一個ActivityRecord就對應(yīng)了一個Task,接下來先從Task的數(shù)據(jù)結(jié)構(gòu)開始看
class Task extends WindowContainer<WindowContainer> {
String affinity; //Task的別名,在manifest內(nèi)部的activity標(biāo)簽可以自定義配置
final int mTaskId; //Task有一個對應(yīng)的任務(wù)ID
final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();//Task中使用ArrayList保存內(nèi)部的Activity
final ActivityStackSupervisor mStackSupervisor;
void addChild(ActivityRecord r) {
addChild(r, Integer.MAX_VALUE /* add on top */);
}
void removeChild(WindowContainer child) {
removeChild(child, "removeChild");
}
}
可以看到Task對象內(nèi)部通過一個ArrayList持有了一組的ActivityRecord,并且里面提供了一系列的對列表元素操作的方法,所以Task可以理解為執(zhí)行一組特定的任務(wù)的Activity集合,在不配置啟動模式的情況下,startActivity后啟動的ActivityRecord是默認(rèn)添加在集合尾部,而當(dāng)頁面退出就會將其移出,這種先入后出的方式跟棧很類似,所以我們經(jīng)常會把Task叫做Activity的回退棧
通過adb shell dumpsys activity activities | grep packagename命令可以查看當(dāng)前系統(tǒng)運行的應(yīng)用進(jìn)程的任務(wù)棧情況
//簡單的測試頁面,內(nèi)部一個按鈕跳轉(zhuǎn)到一個A頁面
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.button).setOnClickListener {
startActivity(Intent(this, AActivity::class.java))
}
}
}
//>>> adb shell dumpsys activity activities | grep test.taskapp
//執(zhí)行跳轉(zhuǎn)前
* Task{5ba46f8 #292 visible=true type=standard mode=fullscreen translucent=true A=10446:test.taskapp U=0 StackId=292 sz=1}
mLastOrientationSource=ActivityRecord{7534e5b u0 test.taskapp/.MainActivity t292}
bounds=[0,0][1080,2400]
* ActivityRecord{7534e5b u0 test.taskapp/.MainActivity t292}
//執(zhí)行跳轉(zhuǎn)后
* Task{5ba46f8 #292 visible=true type=standard mode=fullscreen translucent=true A=10446:test.taskapp U=0 StackId=292 sz=2}
mLastOrientationSource=ActivityRecord{221e302 u0 test.taskapp/.AActivity t292}
bounds=[0,0][1080,2400]
* ActivityRecord{221e302 u0 test.taskapp/.AActivity t292}
* ActivityRecord{7534e5b u0 test.taskapp/.MainActivity t292}
從上面可以更加直觀的看出Task內(nèi)部維護(hù)了一個ActivityRecord列表,方便管理Activity的頁面回退彈出操作
Task管理
通常來說,一個應(yīng)用進(jìn)程是由一個一個的Activity交互頁面構(gòu)成的,這些完成一組操作的Activity又構(gòu)成了一個Task對象,就可以簡單的理解一個app就是一個Task,Task也就是用來做后臺應(yīng)用切換的單位,當(dāng)我們一般通過上滑懸停操作,可以進(jìn)入操作系統(tǒng)的后臺管理頁面,這個管理頁面其實管理的就是Task,Task可以用來進(jìn)行任務(wù)切換

因為通常情況下一個應(yīng)用進(jìn)程就對應(yīng)一個Task任務(wù)棧,所以也就經(jīng)常理解為后臺管理頁面管理的是應(yīng)用進(jìn)程
Affinity
那么不通常情況下,一個app進(jìn)程可以維護(hù)多個Task任務(wù)棧嗎?答案當(dāng)然是可以的,當(dāng)我們想在一個不同的任務(wù)棧去啟動一個Activity,可以通過manifest配置文件中給Activity設(shè)置一個taskAffinity屬性,這個屬性就是Task任務(wù)棧的別名,默認(rèn)在不配置的情況下,Task的Affinity值就是應(yīng)用的包名,設(shè)置了別名后,還得在startActivity()啟動參數(shù)Intent中設(shè)置FLAG_ACTIVITY_NEW_TASK屬性就可以了,直接看代碼
//AndroidManifest.xml
<activity android:name=".AActivity" />
<activity
android:name=".BActivity"
android:taskAffinity=".BTask" />
<activity
android:name=".CActivity"
android:taskAffinity=".CTask" />
配置了兩個頁面B和C,都設(shè)置了自定義taskAffinity屬性
class AActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_a)
findViewById<Button>(R.id.button).setOnClickListener {
//B頁面沒有添加FLAG_ACTIVITY_NEW_TASK
startActivity(Intent(this, BActivity::class.java))
}
//A頁面添加FLAG_ACTIVITY_NEW_TASK
findViewById<Button>(R.id.button2).setOnClickListener {
startActivity(Intent(this, CActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
}
}
}
當(dāng)從A->B頁面的時候,通過查看任務(wù)棧
* Task{5ba46f8 #292 visible=true type=standard mode=fullscreen translucent=true A=10446:test.taskapp U=0 StackId=292 sz=3}
mLastOrientationSource=ActivityRecord{5ebb40f u0 test.taskapp/.BActivity t292}
bounds=[0,0][1080,2400]
* ActivityRecord{5ebb40f u0 test.taskapp/.BActivity t292}
* ActivityRecord{221e302 u0 test.taskapp/.AActivity t292}
* ActivityRecord{7534e5b u0 test.taskapp/.MainActivity t292}
可以看到,B頁面的ActivityRecord跟前面的頁面在同一個Task內(nèi)部

* Task{b3bb3f4 #293 visible=true type=standard mode=fullscreen translucent=true A=10446:.CTask U=0 StackId=293 sz=1}
mLastOrientationSource=ActivityRecord{c8aa1c7 u0 test.taskapp/.CActivity t293}
bounds=[0,0][1080,2400]
* ActivityRecord{c8aa1c7 u0 test.taskapp/.CActivity t293}
* Task{5ba46f8 #292 visible=true type=standard mode=fullscreen translucent=true A=10446:test.taskapp U=0 StackId=292 sz=2}
mLastOrientationSource=ActivityRecord{221e302 u0 test.taskapp/.AActivity t292}
bounds=[0,0][1080,2400]
* ActivityRecord{221e302 u0 test.taskapp/.AActivity t292}
* ActivityRecord{7534e5b u0 test.taskapp/.MainActivity t292}
當(dāng)跳轉(zhuǎn)參數(shù)設(shè)置了FLAG_ACTIVITY_NEW_TASK標(biāo)志位后,新生成的CActivity就在一個新的Task任務(wù)棧中,同時觀察后臺的Task列表可以看出,我們的應(yīng)用進(jìn)程就有兩個不同的窗體

跨進(jìn)程Activity
同一個應(yīng)用進(jìn)程可以管理多個不同的Task任務(wù)棧,那么不同的進(jìn)程的Activity可以在用一個任務(wù)棧么,接著從代碼中取看一下
//新增一個D頁面,并且配置其process屬性
<activity
android:name=".DActivity"
android:process=":TaskDProcess" />
//繼續(xù)從A頁面跳轉(zhuǎn)到D頁面后查看任務(wù)棧
* Task{5ba46f8 #292 visible=true type=standard mode=fullscreen translucent=true A=10446:test.taskapp U=0 StackId=292 sz=3}
mLastOrientationSource=ActivityRecord{e6a638f u0 test.taskapp/.DActivity t399}
bounds=[0,0][1080,2400]
* ActivityRecord{e6a638f u0 test.taskapp/.DActivity t292}
* ActivityRecord{221e302 u0 test.taskapp/.AActivity t292}
* ActivityRecord{7534e5b u0 test.taskapp/.MainActivity t292}
可以看到不同的應(yīng)用進(jìn)程的Activity也可以使用同一個任務(wù)棧進(jìn)行管理,ActivityRecord對象就是用于AMS去管理Activity,而AMS本身就是跨進(jìn)程通信的系統(tǒng)服務(wù),所以Task能關(guān)聯(lián)不同進(jìn)程的ActivityRecord也就能理解
小結(jié)
通過一系列的分析,應(yīng)該對Task有了一個更全面的認(rèn)知,在平時開發(fā)中可能很少使用taskAffinity屬性去新開一個Task管理頁面棧,但是如果對回退棧有更精細(xì)的操作管理,可以使用這個方式去做不同的任務(wù)棧的隔離,而且Task也可以管理跨進(jìn)程的頁面
launchMode
跟Task相關(guān)的概念還有一個啟動模式,這個在平時應(yīng)用開發(fā)中還是比較常用,可以直接在AndroidManifest.xml文件中進(jìn)行配置

從圖中可以看到,啟動模式可以配置5種,不同的啟動模式會影響Task內(nèi)部的ActivityRecord列表順序
standard:這個是Android的默認(rèn)啟動模式,每打開一個Activity頁面就會在當(dāng)前Task內(nèi)部創(chuàng)建一個對應(yīng)的ActiviyRecord實例,同一個Activity可以存在多個實例
singleTask:從英文意思看就能知道是在Task中只能有一個對應(yīng)類型的Activity實例,如果當(dāng)前類型的Activity已經(jīng)在Task棧內(nèi)已經(jīng)有對應(yīng)的ActivityRecord,就會將其上面的Activity出棧清理掉,如果沒有就正常的加入Task內(nèi),需要注意的是,如果配置了taskAffnity,在不使用FLAG_ACTIVITY_NEW_TASK標(biāo)志位的情況下,如果沒有對應(yīng)taskAffnity命名的任務(wù)棧,也會新實例化一個對應(yīng)taskAffnity的任務(wù)棧,然后將對應(yīng)的ActivityRecord加入進(jìn)去
singleInstance:不同于singleTask,設(shè)置了這個模式,整個系統(tǒng)中只能存在這個類型的Activity實例,在不同的Task中也不能重復(fù)存在
singleInstancePerTask:這個是Android12新增的啟動模式,類似于singleTask,也是在一個Task內(nèi)部保持唯一類型的Activity對象,不同的在于,singeTask需要配置taskAffnity屬性,就會在一個不同的任務(wù)棧中啟動對應(yīng)的Activity,singleInstancePerTask不需要配置taskAffnity,直接調(diào)用start就會默認(rèn)新開一個Task去管理,但是這個默認(rèn)新建Task的行為只執(zhí)行一次,當(dāng)后續(xù)再次啟動對應(yīng)的Activity,如果不額外配置,就會在新的Task任務(wù)棧內(nèi)執(zhí)行singleTask邏輯,直接將對應(yīng)的Activity彈到棧頂,如果配置了Intent.FLAG_ACTIVITY_MULTIPLE_TASK或Intent.FLAG_ACTIVITY_NEW_DOCUMENT屬性,就會每次都創(chuàng)建一個新的Task去管理調(diào)用棧
singleTop:從英文意思能知道是棧頂唯一,那就是如果當(dāng)需要啟動的Activity類型已經(jīng)在棧頂,那么當(dāng)再次啟動就不會去創(chuàng)建新的ActivityRecord對象,但是如果對應(yīng)的類型沒有在棧頂,那么調(diào)用棧就會出現(xiàn)兩個ActivityRecord都是對應(yīng)的Activity類型
啟動模式舉例寫demo太麻煩了,這個平時開發(fā)中都有用過,舉一些簡單的例子說明一下
舉個??:比如一個新聞列表頁A,進(jìn)入新聞詳情頁B,底部又有推薦新聞可以點擊跳轉(zhuǎn)跳轉(zhuǎn)新聞詳情頁,如果不配置啟動模式,以standard的行為模式執(zhí)行startActivity(),整個任務(wù)棧內(nèi)可能就成了A->B->B->B->B...這樣想回到列表頁A繼續(xù)操作,就得點擊多次返回按鈕,這個時候可以給B頁面配置singleTop模式,當(dāng)B在棧頂去再次打開B頁面的時候的,就不產(chǎn)生重復(fù)的實例,最后的調(diào)用棧就是A->B,這樣只需要一次返回就能到達(dá)A頁面
再舉個??:一個應(yīng)用從首頁A跳轉(zhuǎn)到登錄頁面B,然后繼續(xù)去跳轉(zhuǎn)注冊頁面C,當(dāng)注冊完成后,直接返回A頁面,如果使用默認(rèn)的跳轉(zhuǎn)方式,任務(wù)棧就會成了A->B->C->A,如果這個時候雙擊想要退出應(yīng)用,就會發(fā)現(xiàn)直接跳轉(zhuǎn)到了C頁面,這個時候給A配置singleTask,那么當(dāng)從C跳轉(zhuǎn)到A后,A彈到棧頂,B和C頁面就會被彈出清理掉,最終Task內(nèi)部就只有A一個實例
ActivityStack
從其他的博文中可以了解到,在之前的版本中,ActivityStack是用來管理不同的Task做前后臺切換的,而我目前看的Android11的源碼內(nèi),ActivityStack就是一個繼承自Task的類,這樣命名更加符合我們理解的Activity啟動過程中先入后出的棧的概念
/**
* State and management of a single stack of activities.
*/
class ActivityStack extends Task {}
總結(jié)
通過上面的一些列分析,我們對ActivityRecord,Task還有ActivityStack有了認(rèn)知,Task和ActivityStack是繼承關(guān)系,都是用來管理ActivityRecord的,Task內(nèi)部維護(hù)了ActivityRecord列表,并且通過不同的launchMode對這個列表進(jìn)程添加/移除操作
有了這些概念基礎(chǔ),下一篇就繼續(xù)回到startActiviy()的源碼流程中,看看再啟動的時候去怎么判斷創(chuàng)建Task,判斷l(xiāng)aunchMode標(biāo)志,添加ActivityRecord實例的