taskAffinity
我们可以在 AndroidManifest.xml 中通过 android:taskAffinity
属性为 Activity 指定 taskAffinity。
那么什么是 taskAffinity 呢?
taskAffinity 可以为 Activity 指定归属于那个 Task。
一般情况下,如果应用中的 Activity 都没有特殊的显式指定 taskAffinity,那么它的这个属性就等于 Application 指明的 taskAffinity,如果 Application 也没有指明,那么该 taskAffinity 的值就等于应用的包名。
taskAffinity 可以为任意字符串,但是非空的话必须至少包含一个“.”,否则会编译报错。但是你可以指定taskAffinity为空字符串,这时它就不属于任何 Task。
那么是不是我指定了一个 Activity 的 taskAffinity 值(跟包名不同),运行该 Activity 时,是否就会新开这个 task栈呢?
答案是否定的,一个 Activity 运行时所归属的task,是由 StackId 来决定的,默认是启动它的那个Activity 所在的 task。即使有时我们配置了 taskAffinity,但是它是在特定情况下才会生效的。
taskAffinit 单独使用并不会生效,要想其生效,需要配合其他属性使用,或者在 standard 模式下配合 Intent.FLAG_ACTIVITY_NEW_TASK,或者配合
allowTaskReparenting。或者配合 singleTask 使用。
LaunchMode
standard
标准模式。如果不显式的指定 launchMode,Activity都会按照此种模式启动。这种模式下的 Activity 可以有多个实例,不同任务中可以有不同的 Activity 实例,同一个任务中也可以有多个 Activity 实例。这种模式下启动Activity总是会重新创建Activity,对应调用 onCreate()
方法。
Android L(Android 5.0)前后对这种启动模式的处理方式是不同的:
- Android L之前:每次以该模式启动的 Activity 都会被压入当前任务的顶部,启动 N 次,在当前任务就会出现 N 个 Activity 的实例,每次Back键就会销毁一个,直到按了 N 次Back键。
- Android L之后:如果要以该模式启动 Activity 都是来自同一应用,那么还是会像之前一样,压入当前任务的顶部; 如果是来自不同应用,那么将会创建一个新的任务,然后将 Activity 的实例压入新的任务中。Android L做的优化主要是针对多任务的显示。
singleTop
栈顶复用模式。这种模式下栈顶仅有一个 Activity 实例,也就是说不会有多个相同的 Activity 叠加在栈顶。如果当前任务的顶部就是待启动的 Activity 实例,那么并不会再创建一个新的 Activity 实例,而是仅仅调用已有实例的 onNewIntent()
方法,所以对于要以 singleTop 启动的Activity,需要处理 onCreate()
和 onNewIntent()
这两种情况下的启动参数。
但是如果任务中已经有这个 Activity 实例,但是不在栈顶,还是会重新创建一个新的 Activity 实例。
singleTask
栈内复用模式,也是单实例模式。该模式下该 Activity 实例仅有一个 Task 存在。但是该 Task 中可能会有多个 Activity。
系统在该类型的 Task 不存在时创建一个新的 Task,并将该 Activity 放入 Task 底部。具体启动 singleTask 会不会新建一个 Task,则和 TaskAffinity 有关。在我们不设置 android:taskAffinity
的情况下,同一个应用的 Activity 默认具有相同的 TaskAffinity,除非你自己设置了 Activity 的 android:taskAffinity
。
以 A 启动 B(singleTask)为例,下面我们分集中情况来介绍:
- A 和 B 的 TaskAffinity 相同,该 Task 里面没有 B:那么就直接在 A 的 Task 里面启动 B。
- A 和 B 的 TaskAffinity 相同,该 Task 里面存在 B:清除该 Activity 上面的所有对象,把该 Activity 推到前台。
- A 和 B 的 TaskAffinity 不同,且 B 不存在:则创建一个新的 Task,并将该 Activity 放入 Task 底部。
- A 和 B 的 TaskAffinity 不同,且 B 存在:在该 Task 中启动该 Activity,并把该 Task 拉回前台,如果该 Activity 上面有其他的对象,则清除该 Activity 上面的所有对象,把该 Activity 推到前台。
也就是说,启动模式为 singleTask 的 Activity 在系统中只会存在一个实例。
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent()
)。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。
但是要注意,系统可能会随时杀掉后台运行的 Activity,如果这一切发生,那么系统就会调用 onCreate()
方法,而不调用 onNewIntent()
方法。,因此,如果有需要在 Activity
启动时处理的方法最好在 onCreate()
和 onNewIntent()
方法中都要调用一下。
我们其实也注意到,Intent Flag 中 有 FLAG_ACTIVITY_NEW_TASK,singleTask 的作用相当于 FLAG_ACTIVITY_CLEAR_TOP + FLAG_ACTIVITY_NEW_TASK。
singleInstance
单实例模式,也是增强型 singleTask 模式。该模式下,系统中只允许有一个存在该 Activity 的 Task 存在,而且该 Task 中只允许有该 Activity 一个实例存在。
- Activity 不存在:总是新建一个 Task,并且创建一个 Activity。
- Activity 存在:则会重用该 Activity。
这里可能会有个疑问,如果 A 和 B (singleInstance)的 TaskAffinity 相同,A 启动 B 时的情况是怎么样的呢?我们来做一个测试:
看一下 adb shell dumpsys activity
的打印:
启动 B 前:
1 | Task id #356 |
该 Task 里面有 MainActivity 和 A。
A 启动 B:
1 | Task id #361 |
可以看到,新建了一个 Task 用来管理 ActivityB。印证了上面的总结,Activity 不存在时总是新建一个 Task,并且创建一个 Activity。
区别
- standard 和 singleTop:这两种模式下 Activity 在系统中可以有多个实例。如果栈顶已经存在待启动 Activity 的实例,singleTop 会利用已有的实例,而 standard 仍然会新建一个。 然而,singleTop 只对当前应用启动有效,对于跨应用启动的情况,singleTop 与 standard 模式没有区别。在跨应用启动这种情况下,Intent 并不会去寻找已有任务的 Activity,而是直接创建一个新的 Activity 实例:在 Android L 之前,将 Activity 实例置于发起者的任务顶,在 Android L 之后,将 Activity 实例置于新任务的根部。
- singleTask 和 singleInstance 模式的 Activity 在系统中都只有一个实例(当然也就只有一个该 Activity 的Task),但是 singleTask 模式的 Task 中允许有别的 Activity 存在,而 singleInstance 模式的 Task 中只有该 Activity 一个实例,不允许有其他 Activity 存在。
LaunchMode 和 taskAffinity 的关系
standard 模式下指定 taskAffinity
A (MainActivity) 以 standard 模式启动 B(OtherTestActivity),为 B 指定一个不同于 A 的 taskAffinity。
1 | Task id #21330 |
可以看到,他们仍然在同一个 Task 中,这个 Task 为 A Activity 所在的 Task。
因此在 standard 模式下设置 taskAffinity 没什么作用,正如上面介绍,可以配合 FLAG_ACTIVITY_NEW_TASK 使用。
singleTask 模式下指定 taskAffinity
这种模式下启动和 taskAffinity 的关系上面已经介绍了一些普通的情况,这里不再介绍。
下面来看一个不同的 Activity 指定同一个 taskAffinity 的情况。
我们先来看一下同一个应用中的情况。首先,这两个 Activity 没有指定特殊进程,他们运行在一个进程中。
在 A(MainActivity) 中以 singleTask 启动同一个 taskAffinity (但不同于A)的 B(OtherTestActivity) 和 C(EventActivity) 。
1 | Task id #21332 |
可以看到,这种情况下,B 和 C 在同一个 Task中,而且在同一个进程中。
不在同一个进程的情况这里就不再测试了, B 和 C 这时候仍在 在同一个 Task 中。也就是说 Task 和 进程没有联系。
下面来说明一种情况,就是 A 应用和 B 应用依赖了SDK包,他们以 singleTask 模式启动 SDK 中的同一个 Activity C,这又该什么情况呢?
其实这种情况只要你理解了下面的事实,那么 LaunchMode 、taskAffinity 以及 Task 的关系就很容易理解了。
也就是虽然 SDK 中的 C 从代码层面上讲是同一个,但是对于A 应用和 B 应用来说,调用的确实两个不同的 Activity ,因为它们的包名不同,比如:
com.hq.test.sdkdemo/com.hq.sdk.TestActivity
com.example.heqiang.testsomething/com.hq.sdk.TestActivity
可以下面的信息:
1 | Task id #21313 |
在 com.hq.test Task中,有两个 TestActivity
,他们属于不同进程。
Intent Flag
FLAG_ACTIVITY_NEW_TASK
设置 FLAG_ACTIVITY_NEW_TASK 标志位时,当有 task 运行了你要运行的 activity,这个task会到前台并,并且这个activity会回调 onNewIntent(),而不是创建新的。 如果没有就会去创建新的 activity。
设置 FLAG_ACTIVITY_NEW_TASK 标志位是,具体是新建 stask 启动还是在当前 stask 中启动 activity,就和 activity 的 taskAffinity 有关系了。
FLAG_ACTIVITY_NEW_TASK 只会去关心该 Activity
的 Task 的个数,如果不存在就新建,存在就直接推到前台。而不去关心 Activity
的个数(和启动模式有关),也就是说该 Task 中可以有多个 Activity 存在,这个要和 singleTask
做区别。
下来来介绍一下需要注意的几个点:
第一种情况:OtherTestActivity(默认 taskAffinity),ActivityA(设置新的taskAffinity)、ActivityB(默认 taskAffinity)。OtherTestActivity 启动ActivityA,ActivityA启动ActivityB时都加上 FLAG_ACTIVITY_NEW_TASK。
来看一下栈的情况:
1 | Stack #1571: type=standard mode=fullscreen |
可以看到,OtherTestActivity 启动ActivityA时,在新的栈上创建 ActivityA,ActivityA启动ActivityB时会在 OtherTestActivity 所在的栈中添加 ActivityB。
再看另外一种情况:
OtherTestActivity(默认 taskAffinity),ActivityA(设置新的taskAffinity)、ActivityB(默认 taskAffinity)。OtherTestActivity 启动ActivityA时加上 FLAG_ACTIVITY_NEW_TASK,ActivityA启动ActivityB时不加。
1 | Stack #1578: type=standard mode=fullscreen |
可以看到,OtherTestActivity 启动ActivityA时,在新的栈上创建 ActivityA,ActivityA启动 ActivityB时会在 ActivityA 所在的栈中添加 ActivityB。
这是我们进行下面一步测试,从多任务中打开 OtherTestActivity,从新点击按钮启动 ActivityA:
1 | Stack #1578: type=standard mode=fullscreen |
可以看到,这时并没有新建 ActivityA,而是把 ActivityA 和 ActivityB所在的栈推到了前台,ActivityB 仍然在 ActivityA 上面。
基于此再做一个测试:从ActivityB再启动ActivityA。
发现,在这种情况下,栈的情况都没有变化,和上面的情况是一样的。因为当前栈本来就在前台。要注意,从两个 activity 情况下启动同一个activiyt只是推栈到前台,必须要保证启动的 Intent 一样才行。
FLAG_ACTIVITY_CLEAR_TOP
启动模式不同,具体操作也不一样。但是都会清除该 Activity 上面的所有对象。
standard 模式:系统收到新的 Intent 时,如果 Task 中存在该 Activity 则弹出 Activity 和 Activity 上面的所有对象,并重新初始化一个新的 Activity 对象放入栈顶,以处理 Intent 请求。因为 standard 总是初始化新的 Activity 来处理 Intent请求。
其他模式:则会将 Activity 上面的对象弹出,使该 Activity 在栈顶部,以处理 Intent请求。
FLAG_ACTIVITY_CLEAR_TASK
启动 Activity 时如果加入了这个标志,那么如果存放该 Activity 的 Task 已经存在,会先把该 Task 里面所有的 Activity 清空,然后再启动该 Activity。那么该 Activity 就成了该 Task 里面唯一的一个 Activity。
应用场景:如果在同一 Task 里面 A 启动 B,B 启动 C,但是在 C 里面后退的话会默认返回 B,然后再返回 A,如果不想返回 B 和 A,那么就可以用该 Flag,启动 C 时把 A 和 B 清除掉,比如在登陆界面跳转到到主界面。从主界面后退肯定不想返回登陆界面的。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
通过这个 Flag 启动的 Activity 将不会添加到最近使用应用列表中,我们从任务管理器中无法找到这个 Activity。
FLAG_ACTIVITY_NO_HISTORY
通过这个 Flag 启动的 Activity 一旦退出后,就不会存在栈中。比如 A 添加这个 Flag 启动 B,B启动 C,这是栈中就只有 A、C。
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
当从手机任务管理器启动的Activity Intent 的 Flag 会带有这个属性,我们可以用它来区分是否从任务管理器里面启动的。
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
只有当新启动一个任务栈或任务栈被切换到前台时才有用,也就意味着该标记只会加在任务栈的根 Activity 上,用这个标记来表明需要重置任务栈的状态。
一般与FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET结合使用。
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
设置此标签相当于在任务栈中标记一个位置,当任务栈被重置时,从该标记位置到栈顶的 Activity 全部被清除。所谓的任务栈重置,是指当程序从后台进入前台时(通常是用户从主屏重新点击启动图标,或者从 recent apps 重新进入程序),如果栈顶的 Activity 设置了 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 标记,那么从设置 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标记到栈顶的 Activity 会全部被清除掉。
举例:该标签可用于这种情况,用户在浏览邮件时,通过另外一个 Activity 来打开图片附件,这时用户可能会按 Home 键返回主屏,进行其它操作,一段时间后重新点击邮件的启动图标进入程序,这时应该显示邮件的内容而不是图片,因为用户可能是很长时间后返回邮件 App 的,也许已经忘记之前的操作,如果用户打开邮件后看到的是一张图片,可能会觉得很困惑。而设置 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标记后,用户再次打开程序时,任务栈就会把图片 Activity 清除掉,显示邮件内容。