简介
taskAffinity 是用来指示 Activity 属于哪一个 Task 的,task可以理解为任务栈。默认情况下 Activity 的 taskAffinity 是包名。
使用方式
一般情况下 taskAffinity 的使用配合 Activity 的启动模式 LaunchMode 来一起使用。可以通过 getTaskId()方法 得到当前Activity属于哪一个task 的id值。
public class MyApplication extends Application {
private static final String TAG_APP = MyApplication.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG_APP,"MyApplication#onCreate"+Thread.currentThread().getName();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
//已过滤无用代码,只在onresume 读取相关数据
@Override
public void onActivityResumed(Activity activity) {
Log.d(TAG_APP,"onActivityResumed+"+activity.getClass().getSimpleName()+"####taskid = "+activity.getTaskId());
}
});
}
}
taskAffinity 的使用方式如下,
如上图所示,taskaffinity 可以单独对一个 activity 使用,代表该 activity 所想归属的 task;也能对application 使用,代表该 application 内声明的所有 activity 都归属于这个task。
如果 activity 组件没有声明 taskAffinity 的话,该 activity 的 taskAffinity 属性也是有默认值的。如果 application 指定了 taskAffinity 值,默认值就是 application 指定的 taskAffinity 值;如果 application 未指定的话,默认值就是 manifest 中声明的包名(package 对应的字符串)。
使用条件(即taskAffinity生效)
taskAffinity 单独使用并不会生效。要想其生效,需要配合其他属性使用,或者配合 Intent.FLAG_ACTIVITY_NEW_TASK,或者配合allowTaskReparenting 。下面将详细介绍这两个属性:
- 启动启动模式为singleTask的Activity
即manifest.xml中指定singleTask启动模式。或者Intent使用了FLAG_ACTIVITY_NEW_TASK标记都为singleTask启动模式。
首先,系统会去检查这个Activity的affinity是否与当前Task的affinity相同。如果相同的话就会把它放入到当前Task当中,如果不同则会先去检查是否已经有一个名字与该Activity的affinity相同的Task,如果有,这个Task将被调到前台,同时这个Activity将显示在这个Task的顶端;如果没有的话,系统将会尝试为这个Activity创建一个新的Task
- 根据affinity重新为Activity选择宿主task(与allowTaskReparenting属性配合使用)
对官网allowTaskReparenting概念的解释。
allowTaskReparenting用来标记Activity能否从启动的Task移动到taskAffinity指定的Task,当把Activity的allowTaskReparenting属性设置成true时,Activity就拥有了一个转移所在Task的能力。具体点来说,就是一个Activity现在是处于某个Task当中的,但是它与另外一个Task具有相同的affinity值,那么当另外这个任务切换到前台的时候,该Activity就可以转移到现在的这个任务当中。allowTaskReparenting默认是继承至application中的allowTaskReparenting=false,如果为true,则表示可以更换;false表示不可以。
举一个形象点的例子,比如有一个天气预报程序,它有一个用于显示天气信息的Activity,allowTaskReparenting属性设置成true,这个Activity和天气预报程序的所有其它Activity具体相同的affinity值。这个时候,你自己的应用程序通过Intent去启动了这个用于显示天气信息的Activity,那么此时这个Activity应该是和你的应用程序是在同一个任务当中的。但是当把天气预报程序切换到前台的时候,这个Activity会被转移到天气预报程序的任务当中,并显示出来。如果将你自己的应用切换到前台,发现你自己应用Task里的那个Activity消失了。
测试用例
下面开始我们的测试,测试结果为过滤后的log日志,并给出相应分析。
同一APP
case1: A、B 属同一App, intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性
D/MyApplication: onActivityResumed+MainActivity####taskid = 61
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 61
可以验证:一个Activity 归属的task 是由 启动它的 Activity 所决定的 (standard启动模式)
case2: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 指定 taskAffinity 属性,但与包名相同
D/MyApplication: onActivityResumed+MainActivity####taskid = 62
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 62
可以验证,一个 Activity 的默认 task 值就是 manifest 定义的包名。
case3: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 指定 taskAffinity 属性,但与包名不同
D/MyApplication: onActivityResumed+MainActivity####taskid = 63
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 63
可以验证:不指定 FLAG_ACTIVITY_NEW_TASK的话, 即使 taskAffinity 不同,一个Activity 归属的task 仍然是由 启动它的 Activity 所决定的。
case4: A、B 属同一App,intent 指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性
D/MyApplication: onActivityResumed+MainActivity####taskid = 64
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 64
可以验证:即使 使用了 FLAG_ACTIVITY_NEW_TASK,但由于两者的 taskAffinity 相同,所以仍然不会开启一个新的task。
case5: A、B 属同一App,intent 指定 FLAG_ACTIVITY_NEW_TASK,B 指定 taskAffinity 属性,且和包名不同
D/MyApplication: onActivityResumed+MainActivity####taskid = 65
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 66
可以验证:开启一个新task 的条件是 FLAG_ACTIVITY_NEW_TASK 和 taskAffinity 不同 缺一不可。
case6: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性,B启动模式为 singletask or singletop
D/MyApplication: onActivityResumed+MainActivity####taskid = 67
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 67
可以得出:未指定 FLAG_ACTIVITY_NEW_TASK 和 新的 taskAffinity 时,这两种启动模式对task 没有影响
case7: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性,B启动模式为 singleinstance
D/MyApplication: onActivityResumed+MainActivity####taskid = 70
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 71
可以得出:
singleinstance 启动模式本身就是会开启一个新的task 装载 这个Activity,且task 中只有这一个 Activity。
经判断得知,AMS 是先对 launchMode 做判断 再处理 FLAG_ACTIVITY_NEW_TASK 的,如果是 singleinstance ,则会直接开启一个task。
考虑跨进程调用的情况
我们可以通过隐式启动的方式启动B,B 仍然是标准启动模式。类似这样
//App1中 在Activity A 中我们这样定义跳转方法
public void onClickApp2(View v){
Intent intent = new Intent("xxx.lzq");//action 自己随便定义就行,但要保证 跟 B 的 intent-filter 是相同的。
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
}
//app2 中 manifest文件这样定义
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="xxx.lzq"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
上面七个 case 均是在同一个app 内的,现在考虑跨进程调用的情况,A在 app1,B在app2,此时 B 的默认 task 肯定是和 A 不同的。
我们可以通过隐式启动的方式启动B,B 仍然是标准启动模式。
类似这样。
case 8: intent 未指定 FLAG_ACTIVITY_NEW_TASK
D/MyApplication: onActivityResumed+MainActivity####taskid = 74
D/MyApplication2: onActivityResumed+Main2Activity####taskid = 74
可以得出:case 8 与 case 3 的本质是相同的,仅仅是 A 和 B 的taskA 属性不同,所以没有开启新的task。
我们还可以看出,task 是可以跨进程的,即一个 task 中的 Activities 是可以运行在不同的进程中的。(关于 A 和 B 不在同一个进程读者可自行验证)
case 9: intent 指定 FLAG_ACTIVITY_NEW_TASK
D/MyApplication: onActivityResumed+MainActivity####taskid = 76
D/MyApplication2: onActivityResumed+Main2Activity####taskid = 77
可以得出:case 9 与 case 5 的本质是相同的。
至此,Intent 的 FLAG_ACTIVITY_NEW_TASK 属性 应该算是讲解清楚了。
我们还可以得出一个有意思结论,那就是 AMS 分配的taskid 是线性递增的,每次开启一个新的task ,taskid 永远都是 +1 的操作。
allowTaskReparenting 相关
测试该属性的话,应该先把 FLAG_ACTIVITY_NEW_TASK 属性去掉。
allowTaskReparenting 这个属性指的是一个 Activity 运行时,可以重新选择自己所属的task。基本是在跨app 间调用时,我们在上面的case 8 的基础上,对 Activity 做如下修改
// 将 allowTaskReparenting 设置为 true
<activity android:name=".Main2Activity"
android:allowTaskReparenting="true">
<intent-filter>
<action android:name="xxx.lzq"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
case 10: taskReparenting 的时机:
当 A 启动 B 时,这时虽然是在两个进程中的,但其归属的task 是同一个,
adb shell dumpsys activity activities 通过栈结构信息发现,AB 在同一个栈里。比如说栈ID为83。栈里有两个实例 A和B
这时我们回到后台,在桌面点击 B 的应用图标,通过栈结构信息发现。
有一个新栈 ID 为84 ,栈内实例为B。ID为83的栈中。只有A。即B从83的栈中转移到了栈84.
打印 log 日志如下:
其中 MyApplication 代表 app1,MyApplication2代表 app2。
D/MyApplication: onActivityResumed+MainActivity####taskid = 83
D/MyApplication2: onActivityResumed+Main2Activity####taskid = 83
D/MyApplication2: onActivityResumed+Main2Activity####taskid = 84
case 11: 假如 app2 之前启动过,即app2 所归属的task 已经创建,这时我们再完全重复以上步骤,
log 日志如下:
D/MyApplication2: onActivityResumed+MainActivity####taskid = 86
D/MyApplication: onActivityResumed+MainActivity####taskid = 87
D/MyApplication2: onActivityResumed+Main2Activity####taskid = 87
D/MyApplication2: onActivityResumed+Main2Activity####taskid = 86
此 case 正好验证之前的解析规则,若 Activity taskAffinity指定的task 已经存在,是会复用之前的task,而不会再重新创建一个新的task。