Activity启动模式(二)之 taskAffinity

taskAffinity说明

android:taskAffinity

affinity,名称( n.), 密切关系;吸引力;姻亲关系;类同

taskAffinity,即与 Activity 有着亲和关系的任务。从概念上讲,具有相同亲和关系的 Activity 归属同一任务(从用户的角度来看,则是归属同一“应用”)。 任务的亲和关系由其根 Activity 的亲和关系确定。 亲和关系确定两件事 - Activity 更改到的父项任务(请参阅 allowTaskReparenting 属性)和通过 FLAG_ACTIVITY_NEW_TASK 标志启动 Activity 时将用来容纳它的任务。

默认情况下,应用中的所有 Activity 都具有相同的亲和关系。您可以设置该属性来以不同方式组合它们,甚至可以将在不同应用中定义的 Activity 置于同一任务内。 要指定 Activity 与任何任务均无亲和关系,请将其设置为空字符串。

如果未设置该属性,则 Activity 继承为应用设置的亲和关系(即 元素的 taskAffinity 属性,该属性与activity中的taskAffinity 属性效果一致,不同的是application中的该属性作用于所有activity。若activity中单独设置了taskAffinity 属性,则使用activity中的该属性)。 应用默认亲和关系的名称是 元素设置的软件包名称。

taskAffinity与四大启动模式

四大启动模式的介绍请参考前文 Activity启动模式(一)之 launchMode
点击查看:affinity测试源码
从四大启动模式与Task的关系分析及taskAffinity的说明中,可以断定taskAffinity在standard、singleTop模式中是无效的;
而singleInstance模式本就是全局唯一的,taskAffinity虽然会改变singleInstance模式启动的activity的taskAffinity值,但不会改变singleInstance模式的特性,也可简单地将singleInstance也归为无效。 那就仅剩下singleTask模式了。下文将详细介绍singleTask与taskAffinity的关系。 任务栈中的affinity信息可以查看上文 Activity启动模式(一)之 launchMode 中提到的shell命令。

简化信息命令为:

adb shell dumpsys activity activities | sed -En -e ‘/Running activities/,/Run #0/p’

taskAffinity特点

singleTask与taskAffinity之间的关系,其实已经在taskAffinity说明中全部介绍了。 两者的要点有如下:

  1. 任务的亲和关系由其根 Activity 的亲和关系确定
  2. 亲和关系是处理allowTaskReparenting和FLAG_ACTIVITY_NEW_TASK(singleTask)的依据,taskAffinity也主要和这两者配合使用。
  3. 未设置该属性,则 Activity 继承为应用(application)设置的亲和关系;若应用未设置该属性,则默认使用包名作为该属性值;若application和Activity中均设置了该属性,则以Activity中的属性为准。
  4. 该属性可以将在不同应用中定义的 Activity 置于同一任务内
  5. 将该属性设置为空字符串,可使指定 Activity 与任何任务均无亲和关系。有点像singleInstance的模式。单独使用一个任务栈,且其内只有指定 Activity的一个实例。
  6. 该属性要么为空字符串,要么其值之中至少包含一个“.”符号,否则apk无法通过编译安装

可将测试代码中的清单文件AffinitySingleInstanceActivity、AffinitySingleTopAActivity的taskAffinity改为空字符串,验证第5条结论。

1
2
3
4
5
6
7
8
9
10
<activity
android:name=".activity.sample.lanchmode.advance.affinity.useless.AffinitySingleInstanceActivity"
android:label="启动模式-singleInstance affinity A"
android:launchMode="singleInstance"
android:taskAffinity="" />
<activity
android:name=".activity.sample.lanchmode.advance.affinity.useless.AffinitySingleTopAActivity"
android:label="启动模式-singleTop affinity A"
android:launchMode="singleTop"
android:taskAffinity="" />

singleTask的启动模式下流程如下:

singleTask流程图

而FLAG_ACTIVITY_NEW_TASK且指定了与包名不同的亲和性值后,有一条流程,结果有些特别。该流程如下所示:

是否存在需要的栈(Y)->栈内是否存在该Activity的实例(Y)->结果毫无反应

taskAffinity与allowTaskReparenting

当启动 Activity 的任务接下来转至前台时,Activity 是否能从该任务转移至与其有亲和关系的任务 —“true”表示它可以转移,“false”表示它仍须留在启动它的任务处。 如果未设置该属性,则对 Activity 应用由 元素的相应 allowTaskReparenting 属性设置的值。 默认值为“false”。

正常情况下,当 Activity 启动时,会与启动它的任务关联,并在其整个生命周期中一直留在该任务处。您可以利用该属性强制 Activity 在其当前任务不再显示时将其父项更改为与其有亲和关系的任务。该属性通常用于使应用的 Activity 转移至与该应用关联的主任务。


举例说明:
例如,如果电子邮件包含网页链接,则点击链接会调出可显示网页的 Activity。 该 Activity 由浏览器应用定义,但作为电子邮件任务的一部分启动。 如果将其父项更改为浏览器任务,它会在浏览器下一次转至前台时显示,当电子邮件任务再次转至前台时则会消失。

Activity 的亲和关系由 taskAffinity 属性定义。 任务的亲和关系通过读取其根 Activity 的亲和关系来确定。因此,按照定义,根 Activity 始终位于具有相同亲和关系的任务之中。 由于具有“singleTask”或“singleInstance”启动模式的 Activity 只能位于任务的根,因此更改父项仅限于“standard”和“singleTop”模式。 (另请参阅 launchMode属性。)

android:allowTaskReparenting
点击查看:allowTaskReparenting测试源码

allowTaskReparenting测试源码请查看两个application(LearnApplication、AppB),这两个app对应举例说明中的两个应用:电子邮件应用-LearnApplication,浏览器应用-AppB,其他对应关系为:电子邮件-MainActivity(LearnApplication),网页链接-MainActivity中的“allowTaskReparenting”条目,LearnApplication显示网页的 Activity-TestReparentActivity(AppB)。

具体操作流程为(前提:清除后台app、appb两个应用,确保流程正确)

  1. 点击LearnApplication应用logo,打开LearnApplication应用

  2. 点击“allowTaskReparenting”条目,LearnApplication应用会打开AppB的TestReparentActivity界面。

  3. 按home键

  4. 点击AppB应用,AppB应用会显示TestReparentActivity界面

  5. 点击返回键,TestReparentActivity销毁,并显示AppB应用的MainActivity界面

操作到第2步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Task id #1540
TaskRecord{16d20b3 #1540 A=com.nhtzj.learnapplication U=0 sz=2}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.learnapplication/.activity.main.MainActivity (has extras) }
Hist #1: ActivityRecord{78adae5 u0 com.nhtzj.appb/.sample.TestReparentActivity t1540}
Intent { cmp=com.nhtzj.appb/.sample.TestReparentActivity }
ProcessRecord{8dfa670 21197:com.nhtzj.appb/u0a267}
Hist #0: ActivityRecord{e19b029 u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1540}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.learnapplication/.activity.main.MainActivity bnds=[820,363][1002,545] (has extras) }
ProcessRecord{6902de9 21113:com.nhtzj.learnapplication/u0a59}

Running activities (most recent first):
TaskRecord{16d20b3 #1540 A=com.nhtzj.learnapplication U=0 sz=2}
Run #1: ActivityRecord{78adae5 u0 com.nhtzj.appb/.sample.TestReparentActivity t1540}
Run #0: ActivityRecord{e19b029 u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1540}

mResumedActivity: ActivityRecord{78adae5 u0 com.nhtzj.appb/.sample.TestReparentActivity t1540}

操作到第4步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Task id #1542
TaskRecord{468514d #1542 A=com.nhtzj.appb U=0 sz=2}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.appb/.sample.MainActivity (has extras) }
Hist #1: ActivityRecord{78adae5 u0 com.nhtzj.appb/.sample.TestReparentActivity t1542}
Intent { cmp=com.nhtzj.appb/.sample.TestReparentActivity }
ProcessRecord{8dfa670 21197:com.nhtzj.appb/u0a267}
Hist #0: ActivityRecord{3fc1c3e u0 com.nhtzj.appb/.sample.MainActivity t1542}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.appb/.sample.MainActivity bnds=[76,625][258,807] (has extras) }
Task id #1540
TaskRecord{16d20b3 #1540 A=com.nhtzj.learnapplication U=0 sz=1}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.learnapplication/.activity.main.MainActivity (has extras) }
Hist #0: ActivityRecord{e19b029 u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1540}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.learnapplication/.activity.main.MainActivity bnds=[820,363][1002,545] (has extras) }
ProcessRecord{6902de9 21113:com.nhtzj.learnapplication/u0a59}

Running activities (most recent first):
TaskRecord{468514d #1542 A=com.nhtzj.appb U=0 sz=2}
Run #1: ActivityRecord{78adae5 u0 com.nhtzj.appb/.sample.TestReparentActivity t1542}
TaskRecord{16d20b3 #1540 A=com.nhtzj.learnapplication U=0 sz=1}
Run #0: ActivityRecord{e19b029 u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1540}

mResumedActivity: ActivityRecord{78adae5 u0 com.nhtzj.appb/.sample.TestReparentActivity t1542}

操作到第5步

1
2
3
4
5
6
7
8
9
10
11
12
13
TaskRecord{16d20b3 #1540 A=com.nhtzj.learnapplication U=0 sz=1}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.learnapplication/.activity.main.MainActivity (has extras) }
Hist #0: ActivityRecord{e19b029 u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1540}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.nhtzj.learnapplication/.activity.main.MainActivity bnds=[820,363][1002,545] (has extras) }
ProcessRecord{6902de9 21113:com.nhtzj.learnapplication/u0a59}

Running activities (most recent first):
TaskRecord{468514d #1542 A=com.nhtzj.appb U=0 sz=1}
Run #1: ActivityRecord{3fc1c3e u0 com.nhtzj.appb/.sample.MainActivity t1542}
TaskRecord{16d20b3 #1540 A=com.nhtzj.learnapplication U=0 sz=1}
Run #0: ActivityRecord{e19b029 u0 com.nhtzj.learnapplication/.activity.main.MainActivity t1540}

mResumedActivity: ActivityRecord{3fc1c3e u0 com.nhtzj.appb/.sample.MainActivity t1542}

若第4步改为:点击LearnApplication应用,则显示LearnApplication应用主界面(LearnMainActivity)
新第5步:按home键
新第6步:点击AppB应用,则显示TestReparentActivity界面

坚持原创技术分享,您的支持是对我最大的鼓励!