大多数应用不应打破 Activity 和任务的默认行为。如果您确定需要让 Activity 改变默认行为,请谨慎操作,并且务必要测试该 Activity 在以下情况下的可用性:启动期间以及您通过返回按钮从其他 Activity 和任务返回该 Activity 时。务必要测试是否存在可能与用户预期的行为冲突的导航行为。
四种启动模式
1.standard
默认值。系统在启动该 Activity 的任务中创建 Activity 的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。
standard模式的Activity可以有多个ActivityRecord加入不同的task,同一个task也可存在多个ActivityRecord,并且ActivityRecord还可相邻
假设Activity A启动Activity B,B的启动模式为standard模式
B的ActivityRecord默认会放在A的ActivityRecord所在的task里,即使B和A的taskAffinity不同也会如此,这也意味着如果B和A属于不同的应用,B的ActivityRecord也会放在A的ActivityRecord所在的task里。
但是下面两种情况不会将A和B的ActivityRecord放在同一个task里:
如果Activity A的启动模式为singleInstance,则会查找整个回退栈,直到找到和B相关的task,然后把B的ActivityRecord放到该task里,如果没有找到相关的task,则新建task,将B的ActivityRecord放到新task里。后面会介绍如何判断Activity和某个task相关。
如果Activity A的启动模式为singleTask,并且Activity A和Activity B的taskAffinity不一样,那么也会查找整个回退栈,直到找到和B相关的task,然后把B的ActivityRecord放到该task里。
2.singleTop
如果当前任务的顶部已存在 Activity 的实例,则系统会通过调用其 onNewIntent()
方法来将 intent 转送给该实例,而不是创建 Activity 的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。
3.singleTask
栈内复用模式, 只要Activity在一个栈中存在, 多次调用时, 都不会创建实例, 即单例模式,系统会回调 onNewIntent(),
假设Activity A的启动模式为singleTask,那么和Activity A的ActivityRecord放在同一个task里的ActivityRecord所对应的Activity,必须与Activity A的taskAffinity相同。也就是说,Activity A的ActivityRecord只会和同一应用的其它Activity的ActivityRecord放在同一个task里,并且这些同一应用的其它Activity不能设置特殊的taskAffinity。
启动SingleTask实例, 实例会置于栈顶, 并清除其上面实例, 具有clearTop的效果.
只要两个Activity的taskAffinity属性一致,即使其中有一个Activity的启动模式为singleTask,它们对应的ActivityRecord会放在同一个task里,不管是从某个Activity跳转到singleTask类型的Activity,还是从singleTask类型的Activity跳转到其他Activity都是如此,除非跳转的其他Activity的启动模式是singleInstance。
4.SingleInstance
单实例模式, 启动时, 系统会为其创造一个单独的任务栈, 以后每次使用, 都会使用这个单例, 直到其被销毁, 属于真正的单例模式.
该启动模式和singleTask类似,singleInstance模式的Activity在整个回退栈只可以有一个ActivityRecord,也就是说它只能属于某一个task,不可在多个task里存在ActivityRecord,并且它所在的task不可再有其它Activity的ActivityRecord,即使是同一个应用内的其它Activity,也不可有它们的AcvitityRecord。
使用 Intent 标记
FLAG_ACTIVITY_NEW_TASK = .singleTask
FLAG_ACTIVITY_SINGLE_TOP = singleTop
FLAG_ACTIVITY_CLEAR_TOP:如果要启动的 Activity 已经在当前任务中运行,则不会启动该 Activity 的新实例,而是会销毁位于它之上的所有其他 Activity,并通过 onNewIntent()
将此 intent 传送给它的已恢复实例(现在位于堆栈顶部),FLAG_ACTIVITY_CLEAR_TOP
最常与 FLAG_ACTIVITY_NEW_TASK
结合使用。将这两个标记结合使用,可以查找其他任务中的现有 Activity,并将其置于能够响应 intent 的位置
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的activity不会出现在历史列表中,等同于xml中的android:execludeFromRecents="true";
处理亲和性
“亲和性”表示 Activity 倾向于属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此具有亲和性。因此,在默认情况下,同一应用中的所有 Activity 都倾向于位于同一任务,如果一个Activity没有显式的指明该 Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果 Application也没有指明,那么该taskAffinity的值就等于包名
在不同应用中定义的 Activity 可以具有相同的亲和性,或者在同一应用中定义的 Activity 也可以被指定不同的任务亲和性,使用 <activity>
元素的 taskAffinity
属性修改任何给定 Activity 的亲和性
(1)当启动 Activity 的 intent 包含 FLAG_ACTIVITY_NEW_TASK
标记时。
默认情况下,新 Activity 会启动到调用 startActivity()
的 Activity 的任务中。它会被推送到调用方 Activity 所在的返回堆栈中。但是,如果传递给 startActivity()
的 intent 包含 FLAG_ACTIVITY_NEW_TASK
标记,则系统会寻找其他任务来容纳新 Activity。通常会是一个新任务,但也可能不是。如果已存在与新 Activity 具有相同亲和性的现有任务,则会将 Activity 启动到该任务中。如果不存在,则会启动一个新任务。
(2)如果该Activity的allowTaskReparenting设置为true,它进入后台,当一个和它有相同affinity的Task进入前台时,它会重新宿主,进入到该前台的task中。
清除返回堆栈
如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。
您可以使用一些 Activity 属性来修改此行为:
alwaysRetainTaskState
如果在任务的根 Activity 中将该属性设为 "true"
,则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity。
clearTaskOnLaunch
如果在任务的根 Activity 中将该属性设为 "true"
,那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与 alwaysRetainTaskState
正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。
finishOnTaskLaunch
该属性与 clearTaskOnLaunch
类似,但它只会作用于单个 Activity 而非整个任务。它还可导致任何 Activity 消失,包括根 Activity。如果将该属性设为 "true"
,则 Activity 仅在当前会话中归属于任务。如果用户离开任务再返回,则该任务将不再存在。
Activity,回退栈,Task之间的关系
Activity启动时ActivityManagerService会为其生成对应的ActivityRecord记录,并将其加入到回退栈(back stack)中,另外也会将ActivityRecord记录加入到某个Task中。请记住,ActivityRecord,backstack,Task都是ActivityManagerService的对象,由system_server进程负责维护,而不是由应用进程维护。
在回退栈里属于同一个task的ActivityRecord会放在一起,也会形成栈的结构,也就是说后启动的Activity对应的ActivityRecord会放在task的栈顶。
task其实是由ActivityRecord组成的栈,多个task以栈的形式组成了回退栈,ActivityManagerService移动回退栈里的ActivityRecord时以task为单位移动。
假设Activity的跳转顺序:A–>B–>C,A,B,C对应的ActivityRecord属于同一个Task,此时从C跳转至D,再跳转至E,C和D不属于同一个Task,D和E属于同一个Task,那现在的back stack结构如下所示:
现在A,B,C属于task1,C在task1的栈顶,D,E属于task2,E在task2的栈顶。也可以看出来task2位于整个回退栈的栈顶,也就是说task2在task1的上面。如果此时不断按回退键,看到的Activity的顺序会是E–>D–>C–>B–>A。
另外需注意,ActivityManagerService不仅会往回退栈里添加新的ActivityRecord,还会移动回退栈里的ActivityRecord,移动时以task为单位进行移动,而不会移动单个AcitivityRecord。还是针对上面的例子,假设此时按了Home键,那么会将Home应用程序(也叫做Launcher应用程序)的task移动至栈顶,那么此时回退栈如下所示:
可以看到Home应用程序的Activity H对应的Activity Record移动到了回退栈的栈顶。Home应用程序的Activity H对回退按键的响应做了特殊处理,如果此时按回退键,是看不到Activity E的。
如果此时通过Launcher程序再打开Activity A所在的应用,那么会显示Activity C,因为会将Activity A对应的Activity Record所在的task移动至回退栈的栈顶,此时回退栈如下所示:
此时如果按返回键,那么Activity的显示顺序是:C–>B–>A–>H,不会显示E
假设Activity A和Activity B的启动模式都是standard,二者taskAffinity属性值不一样,从Activity A跳转至Activity B,那么它们对应的ActivityRecord会属于同一个task。
假设Activity A的启动模式是standard,Activity B的启动模式singleTask,二者taskAffinity属性值一样,此时从Activity A跳转至Activity B,那么它们对应的ActivityRecord会属于同一个task。因为只要两个Activity的taskAffinity属性一致,即使其中有一个Activity的启动模式为singleTask,它们对应的ActivityRecord会放在同一个task里,不管是从某个Activity跳转到singleTask类型的Activity,还是从singleTask类型的Activity跳转到其他Activity都是如此,除非跳转的其他Activity的启动模式是singleInstance。
假设Activity A的启动模式是standard,Activity B的启动模式singleTask,二者taskAffinity属性值不 一样,此时从Activity A跳转至Activity B,那么它们对应的ActivityRecord会属于不同的Task。
注意
- 1) 从Launcher程序启动应用时,会先查找所有task,看是否有相关task,如果已有相关task,则会将相关task移动到回退栈的栈顶,然后显示栈顶Activity。查找相关task时,需看task是否和应用的入口Activity相关,入口Activity是指在AndroidManifest.xml里声明IntentFilter时,注明category为android.intent.category.LAUNCHER的Activity。如果入口Activity的启动模式为singleTask,不仅会将相关task移动到回退栈的栈顶,还会将该task里位于入口Activity之上的其它ActivityRecord全部清除掉
- 2) 通过最近应用程序,切换应用时,会直接将应用图标对应的task移动到回退栈的栈顶,这样即使task里有singleTask类型的ActivityRecord,在它之上的ActivityRecord也不会被清除
- 3) 可以通过adb shell dumpsys activity activties查看系统task情况