前言
雖然了解activity的四種啟動(dòng)模式念逞,但是在一些復(fù)雜場(chǎng)景下候衍,各種啟動(dòng)模式會(huì)出現(xiàn)的現(xiàn)象蛇尚,以及現(xiàn)象的原因并不清楚摆碉,再加上個(gè)taskAffinity launchMode clearTaskOnLaunch 這些參數(shù)會(huì)使得更加懵逼塘匣。所以根據(jù)在實(shí)際應(yīng)用中遇到的問題總結(jié)一下。
主要內(nèi)容
要講啟動(dòng)模式需要從Task ,taskAffinity 以及l(fā)aunchMode巷帝,還有標(biāo)簽四個(gè)方面入手忌卤,看這四個(gè)之前的關(guān)聯(lián)以及影響。Task
task跟activity的啟動(dòng)息息相關(guān)楞泼,因?yàn)閍ctivity啟動(dòng)后都是放在task里面進(jìn)行管理的,task的數(shù)據(jù)結(jié)構(gòu)是stack的驰徊,先進(jìn)后出,新創(chuàng)建的activity放在task的頂部堕阔,如下圖打開ActivityA->activityB->activityC:task的特點(diǎn):
- activity的集合
- 以棧的形式對(duì)activity進(jìn)行管理(back stack)
- task里面至少包含一個(gè)activity
- 新創(chuàng)建的activity放在棧頂辣垒。
- 每一個(gè)task都有稱為Affinity的name。
TaskAffinity
taskAffinity是activity可以在manifest文件里面設(shè)置的屬性.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.coroutinescopedemo">
<application android:allowBackup="true">
<activity
android:name=".MainActivity"
android:taskAffinity="hanking.edu">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
復(fù)制代碼
用來確定啟動(dòng)的activity屬于哪個(gè)task印蔬,或者確定task的名稱勋桶。具體的功能如下:
用來決定持有activity的task是哪個(gè)。
默認(rèn)情況下一個(gè)app里面的activity都有相同的affinity值(package name)
task的affinity值由觸發(fā)創(chuàng)建task的activity的affinity值決定侥猬。(也被稱為root activity)
taskAffinity用來確定activity所在棧的名字例驹,是不是任何時(shí)候都會(huì)生效?看下默認(rèn)情況下的兩個(gè)activity設(shè)置不同的affinity會(huì)發(fā)生什么情況退唠。
1鹃锈、給activity設(shè)置task affinity 如下創(chuàng)建了activityA和activityB,其中給activityB設(shè)置了task Affinity值為com.something.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myApp">
<application
android:allowBackup="true"
android:theme="@style/AppTheme">
<activity android:name=".ActivityA">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ActivityB"
android:taskAffinity="com.something" />
</application>
</manifest>
復(fù)制代碼
流程:打開activityA 從activityA跳轉(zhuǎn)到ActivityB瞧预,然后打印task的情況屎债。 通過adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'打印情況如下
adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'
Running activities (most recent first):
TaskRecord{5938ae7 #1669 A=com.example.coroutinescopedemo U=0 StackId=287 sz=2}
Run #1: ActivityRecord{5d93c09 u0 com.myApp/.ActivityB t1669}
Run #0: ActivityRecord{5ce5f59 u0 com.myApp/.ActivityA t1669}
復(fù)制代碼
有上面流程可以知道仅政,activity默認(rèn)啟動(dòng)情況下加task affinity屬性并不會(huì)新建task,也不會(huì)改變task名稱盆驹,task的名稱和taskRoot的activity中設(shè)置的task affinity值一致圆丹,如果沒設(shè)置默認(rèn)就是包名,這里taskRoot Activity是activityA。
2躯喇、添加FLAG_ACTIVITY_NEW_TASK 由上面可見辫封,默認(rèn)模式下就算給activity添加了taskAffinity屬性也不會(huì)多創(chuàng)建一個(gè)task,原因是這個(gè)taskAffinity應(yīng)該和FLAG_ACTIVITY_NEW_TASK一起使用才會(huì)創(chuàng)建新task廉丽。
val i = Intent(this, ActivityB::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
復(fù)制代碼
在ActivityA中啟動(dòng)ActivityB的時(shí)候加上Intent.FLAG_ACTIVITY_NEW_TASK的flag,再嘗試從ActivityA打開ActivityB倦微。
Running activities (most recent first):
TaskRecord{59b097b #1675 A=com.something U=0 StackId=293 sz=1}
Run #0: ActivityRecord{5d93041 u0 com.example.myApp/.ActivityB t1675}
Running activities (most recent first):
TaskRecord{59b09a0 #1674 A=com.example.myApp U=0 StackId=292 sz=1}
Run #0: ActivityRecord{5d2de41 u0 com.example.myApp/.ActivityA t1674}
復(fù)制代碼
由上面的信息可以看到有兩個(gè)task,ActivityB所在的task 名稱是com.something, stackId=293, ActivityA所在的task名稱是com.example.myApp stackId=292 思考:加上Intent.FLAG_ACTIVITY_NEW_TASK的tag后由于啟動(dòng)了一個(gè)新的task正压,這時(shí)候退到任務(wù)管理器可以看到activityA和activityB所在的task欣福,如果這個(gè)時(shí)候點(diǎn)開activityB再點(diǎn)擊返回還會(huì)返回到activityA嗎? 答案是不會(huì)焦履?因?yàn)閺腶ctivityA打開activityB后再切到后臺(tái)劣欢,這個(gè)時(shí)候這兩個(gè)activity的task都屬于background狀態(tài),再打開activityB的task裁良,activityB的task屬于foreground task,返回會(huì)直接返回到桌面凿将。
activity啟動(dòng)模式
activity的啟動(dòng)模式一般分為以下四種,四種模式的特點(diǎn)如下:Standard:
activity默認(rèn)的啟動(dòng)模式价脾,在standard模式下每次打開一個(gè)activity的時(shí)候都會(huì)生成一個(gè)新的實(shí)例牧抵。 如下已經(jīng)有了A,B,C,D在stack中,再啟動(dòng)B侨把,B是standard模式犀变。 A →B→ C→D 啟動(dòng)B, A → B → C→D→ B 可以看到會(huì)再次生成B的實(shí)例秋柄,并放到棧頂获枝。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myApp">
<application
android:allowBackup="true"
android:theme="@style/AppTheme">
<activity android:name=".ActivityA">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ActivityB"
android:taskAffinity="com.something" />
</application>
</manifest>
復(fù)制代碼
val i = Intent(this, ActivityB::class.java)
startActivity(i)
復(fù)制代碼
在標(biāo)準(zhǔn)模式下啟動(dòng)ActivityA->ActivityB->activityB如下圖: standard模式看起來非常簡(jiǎn)單,每次生成activity實(shí)例并放在棧頂骇笔,但是當(dāng)standard模式和flag一起使用的時(shí)候又會(huì)產(chǎn)生很多不一樣的效果省店。 1、standard+Intent.FLAG_ACTIVITY_NEW_TASK 按照下面的方式啟動(dòng)activity
啟動(dòng) Activity A
ActivityA 啟動(dòng) ActivityB (no flags)
ActivityB 啟動(dòng) Activity A
ActivityA 啟動(dòng) Activity B (with flag NEW_TASK)
ActivityB 啟動(dòng) ActivityA
復(fù)制代碼
由上圖可知當(dāng)activityA啟動(dòng)activityB笨触,并且此時(shí)intent加上NEW_TASK標(biāo)簽時(shí)懦傍,會(huì)生成一個(gè)新的task com.something,并且activityB作為taskRootActivity,此時(shí)activityB再啟動(dòng)activityA芦劣,activityA也會(huì)在com.something的棧上生成實(shí)例粗俱。
SingleTop
如果需要打開的activity的實(shí)例已經(jīng)處于當(dāng)前棧頂,那么會(huì)復(fù)用當(dāng)前棧頂?shù)腶ctivity虚吟,不會(huì)重新創(chuàng)建activity寸认,但是會(huì)通過調(diào)用onNewIntent().所以如果需要刷新頁面數(shù)據(jù)签财,就要在onNewIntent().進(jìn)行處理。如果棧頂?shù)腶ctivity和需要打開的activity不相同,那么會(huì)重新創(chuàng)建一個(gè)activity,并進(jìn)棧终吼。 假設(shè)棧里面已經(jīng)有A ,B,C幾個(gè)activity, . A →B →C 如果需要再打開activity B那么如下: A →B →C →B 如果這個(gè)時(shí)候再調(diào)用打開activity B會(huì)直接復(fù)用棧頂?shù)腂油宜,并且調(diào)用B的onNewIntent()方法掂碱,棧如下 A →B →C →B
Step 1: Launch A -> A
Step 2: Launch B -> A-B
Step 3: Launch C -> A-B-C
Step 4: Launch B -> A-B-C-B
Step 5: Launch B -> A-B-C-B
復(fù)制代碼
singleTop總結(jié)一句:就是復(fù)用棧頂activity怜姿。
SingleTask
如果棧中存在這個(gè)Activity的實(shí)例就會(huì)復(fù)用這個(gè)Activity,不管它是否位于棧頂疼燥,復(fù)用時(shí)沧卢,會(huì)將它上面的Activity全部出棧,并且會(huì)回調(diào)該實(shí)例的onNewIntent方法醉者。其實(shí)這個(gè)過程還存在一個(gè)任務(wù)棧的匹配但狭,因?yàn)檫@個(gè)模式啟動(dòng)時(shí),會(huì)在自己需要的任務(wù)棧中尋找實(shí)例撬即,這個(gè)任務(wù)棧就是通過taskAffinity屬性指定立磁。如果這個(gè)任務(wù)棧不存在,則會(huì)創(chuàng)建這個(gè)任務(wù)棧剥槐。
Step 1: Launch A -> A
Step 2: Launch B -> A-B
Step 3: Launch C -> A-B-C
Step 4: Launch B -> A-B
Step 5: Launch B -> A-B*
復(fù)制代碼
如上當(dāng)棧中有A-B-C此時(shí)再啟動(dòng)B唱歧,會(huì)遍歷棧,然后找到B粒竖,將B上面的C出棧颅崩。
singleInstance
singleInstance模式下的Activity會(huì)單獨(dú)占用一個(gè)Task棧,具有全局唯一性蕊苗,即整個(gè)系統(tǒng)中就這么一個(gè)實(shí)例沿后,由于棧內(nèi)復(fù)用的特性,后續(xù)的請(qǐng)求均不會(huì)創(chuàng)建新的Activity實(shí)例朽砰,除非這個(gè)特殊的任務(wù)棧被銷毀了尖滚。以singleInstance模式啟動(dòng)的Activity在整個(gè)系統(tǒng)中是單例的,如果在啟動(dòng)這樣的Activiyt時(shí)瞧柔,已經(jīng)存在了一個(gè)實(shí)例熔掺,那么會(huì)把它所在的任務(wù)調(diào)度到前臺(tái),重用這個(gè)實(shí)例非剃。 假設(shè)有三個(gè)activity:A,B,C置逻,B是singleInstance的,
Step 1: Launch A -> Task 1: A
Step 2: Launch B -> Task 1: A
Task 2: B // Visible to the user
Step 3: Launch C -> Task 1: A-C // Visible to the user
Task 2: B
Step 4: Launch B -> Task 1: A-C
Task 2: B* // Visible to the user
復(fù)制代碼
FLAG_ACTIVITY_NEW_TASK
Start the activity in a new task. If a task is already running for the activity you are now starting, that task is brought to the foreground with its last state restored and the activity receives the new intent in onNewIntent().
復(fù)雜場(chǎng)景分析
上面都是簡(jiǎn)單的場(chǎng)景备绽,如果選擇更加復(fù)雜的場(chǎng)景券坞,又會(huì)出現(xiàn)意想不到的現(xiàn)象鬓催。1、task切換后打不開activity
- 啟動(dòng) ActivityA
- Activity A 啟動(dòng) Activity B
- Activity B 啟動(dòng) Activity C with FLAG_NEW_TASK
- Activity C 啟動(dòng) Activity D
- 用戶切換到 com.myApp
- Activity B 啟動(dòng) Activity C with FLAG_NEW_TASK
上面有個(gè)比較奇怪的現(xiàn)象activityB啟動(dòng)activityC恨锚,activityC 啟動(dòng)activityD宇驾,都是new_task方式,C猴伶,D课舍,都是在com.something的task里面,這里正常他挎,但是activityB筝尾,再次調(diào)用activityC的時(shí)候卻沒有啟動(dòng)activityC。 2办桨、task切換后打開多個(gè)activity
1\. 啟動(dòng) Activity A
2\. Activity A 啟動(dòng) Activity B
3\. Activity B 啟動(dòng) Activity C with FLAG_NEW_TASK
4\. Activity C 啟動(dòng) Activity D
5\. User switches to com.myApp
6\. Activity B 啟動(dòng) Activity D with FLAG_NEW_TASK
復(fù)制代碼
上面流程啟動(dòng)activity的筹淫,task是什么情況? 看上圖呢撞,最后當(dāng)activityB其次啟動(dòng)activityD的時(shí)候又創(chuàng)建了一個(gè)activityD损姜,
當(dāng)activityB啟動(dòng)activityD的時(shí)候?yàn)槭裁磿?huì)新創(chuàng)建一個(gè)activityD的實(shí)例? 這里重新創(chuàng)建activityD的原因是:通過activityC創(chuàng)建activityD的intent和通過activityB創(chuàng)建activityD的intent的不一樣導(dǎo)致的殊霞。也就是說只有當(dāng)intent的一樣時(shí)才不會(huì)創(chuàng)建多個(gè)實(shí)例摧阅。
startActivityForResult 異常
activityA是singleInstance,activityB是standard模式,當(dāng)activityA調(diào)用startActivityForResult打開activityB時(shí)绷蹲,activityA收到的回調(diào)函數(shù)onActivityResult能接受activityB的返回結(jié)果嗎棒卷? 正常情況下activityA通過startActivityForResult打開activityB時(shí)的流程如下:
- activityA 重寫onActivityResult接受activityB中的返回值。
- activityB通過setResult確定返回值瘸右。
3.當(dāng)activityB返回時(shí)activityA的onActivityResult才會(huì)被回調(diào)娇跟。
但是當(dāng)activityA定義為singleInstance時(shí)通過startActivityForResult打開activityB時(shí)如下:因?yàn)閍ctivityA是singleInstance的所以獨(dú)享一個(gè)task,當(dāng)activityA打開activityB時(shí)太颤,也創(chuàng)建一個(gè)新的task苞俘,但是onActivityResult是立馬就回調(diào),不是activityB finish的時(shí)候回調(diào)的龄章。這是什么原因吃谣? 注意:當(dāng)一個(gè)activity 調(diào)用startActivityForResult打開另一個(gè)activity當(dāng)時(shí)另一個(gè)activity在另一個(gè)創(chuàng)建的task里面的時(shí)候,onActivityResult就會(huì)立刻回調(diào)做裙。