任务和返回栈
标签(空格分隔): Android开发 AndroidAPI指南
任务和返回栈 google api
基础总结篇之二:Activity的四种launchMode
基础总结篇之三:Activity的task相关
“任务”和“返回栈”
任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。
当用户触摸应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务将出现在前台。 如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。
返回栈以“后进先出”对象结构运行。当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:
Activity 和任务的默认行为总结如下:
- 当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。 如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。
- 用户通过按“主页”按钮离开任务时,当前 Activity 将停止且其任务会进入后台。 系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。
- 如果用户按“返回”按钮,则当前 Activity 会从堆栈弹出并被销毁。 堆栈中的前一个 Activity 恢复执行。销毁 Activity 时,系统不会保留该 Activity 的状态。
- 即使来自其他任务,Activity 也可以多次实例化。
管理任务
定义启动模式
启动模式允许您定义 Activity的新实例如何与当前任务关联。您可以通过两种方法定义不同的启动模式:
使用清单文件
在清单文件中声明 Activity 时,您可以指定 Activity 在启动时应该如何与任务关联。使用 Intent 标志
调用 startActivity() 时,可以在 Intent 中加入一个标志,用于声明新 Activity 如何(或是否)与当前任务关联。
使用清单文件
在清单文件中声明 Activity 时,您可以使用 <activity>
元素的 launchMode 属性指定 Activity 应该如何与任务关联。
launchMode 属性指定有关应如何将 Activity 启动到任务中的指令。您可以分配给 launchMode 属性的启动模式共有四种:
“standard”(默认模式)
默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。“singleTop”
如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C 和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D 具有默认的 “standard” 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 “singleTop”,则 D 的现有实例会通过 onNewIntent() 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。但是,如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 “singleTop” 也是如此。
“singleTask”
系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。“singleInstance”
与 “singleTask” 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。
使用 Intent 标志
启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity 与其任务的默认关联方式。可用于修改默认行为的标志包括:
FLAG_ACTIVITY_NEW_TASK
在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 onNewIntent() 中收到新 Intent。
正如前文所述,这会产生与 “singleTask”launchMode 值相同的行为。FLAG_ACTIVITY_SINGLE_TOP
如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则 现有实例会接收对 onNewIntent() 的调用,而不是创建 Activity 的新实例。
正如前文所述,这会产生与 “singleTop”launchMode 值相同的行为。FLAG_ACTIVITY_CLEAR_TOP
如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。
产生这种行为的 launchMode 属性没有值。FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。
注:如果指定 Activity 的启动模式为 “standard”,则该 Activity 也会从堆栈中移除,并在其位置启动一个新实例,以便处理传入的 Intent。其它三种模式则不会创建新实例。 这是因为当启动模式为 “standard” 时,将始终为新 Intent 创建新实例。
FLAG_ACTIVITY_NEW_DOCUMENT
要插入逻辑换行符以便系统将Activity视为新任务显示在概览屏幕(近期任务)中,可在启动 Activity 的 Intent 的 addFlags() 方法中传递FLAG_ACTIVITY_NEW_DOCUMENT 标志。
与documentLaunchMode=”intoExisting”行为相同。注:FLAG_ACTIVITY_NEW_DOCUMENT 标志取代了 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标志,后者自 Android 5.0(API 级别 21)起已弃用。使用 FLAG_ACTIVITY_NEW_DOCUMENT 标志启动的 Activity 必须具有在清单文件中设置的 android:launchMode=”standard” 属性值(默认)。
FLAG_ACTIVITY_MULTIPLE_TASK
如果在创建新文档时设置FLAG_ACTIVITY_MULTIPLE_TASK标志,则系统始终会以目标 Activity 作为根创建新任务(显示在概览屏幕中),即便文档已打开也是如此。。此设置允许同一文档在多个任务中打开。
FLAG_ACTIVITY_MULTIPLE_TASK通常和FLAG_ACTIVITY_NEW_DOCUMENT配套使用(启动的 Activity 必须具有在清单文件中设置的android:launchMode=”standard”属性值(默认))。与documentLaunchMode=”always”行为相同。
处理关联
“关联”指示 Activity 优先属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此关联。 因此,默认情况下,同一应用中的所有 Activity 优先位于相同任务中。 不过,您可以修改 Activity 的默认关联。 在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。
可以使用 <activity>
元素的 taskAffinity 属性修改任何给定 Activity 的关联。
taskAffinity 属性取字符串值,该值必须不同于在 <manifest>
元素中声明的默认软件包名称,因为系统使用该名称标识应用的默认任务关联。
在两种情况下,关联会起作用:
1. 根据affinity重新为Activity选择任务(与allowTaskReparenting属性配合工作);
2. 启动一个Activity过程中Intent使用了FLAG_ACTIVITY_NEW_TASK标记,根据affinity查找或创建一个新的具有对应affinity的任务。
activity的task相关属性
在<activity>
中定义了几个常见的task相关属性,它们分别代表了task内部不同的行为特征。
android:allowTaskReparenting
当启动 Activity的任务接下来转至前台时,Activity是否能从该任务转移至与其有亲和关系的任务—“true”表示它可以转移,“false”表示它仍须留在启动它的任务处。 默认值为“false”。例如,如果电子邮件包含网页链接,则点击链接会调出可显示网页的Activity。 该 Activity 由浏览器应用定义,但作为电子邮件任务的一部分启动。 如果将其父项更改为浏览器任务,它会在浏览器下一次转至前台时显示,当电子邮件任务再次转至前台时则会消失。
Activity 的亲和关系由taskAffinity属性定义。任务的亲和关系通过读取其根 Activity 的亲和关系来确定。因此,按照定义,根Activity始终位于具有相同亲和关系的任务之中。 由于具有“singleTask”或“singleInstance”启动模式的 Activity 只能位于任务的根,因此更改父项仅限于“standard”和“singleTop”模式。
android:alwaysRetainTaskState
系统是否始终保持 Activity 所在任务的状态 —“true”表示保持,“false”表示允许系统在特定情况下将任务重置到其初始状态。 默认值为“false”。该属性只对任务的根 Activity 有意义;对于所有其他 Activity,均忽略该属性。
正常情况下,当用户从主屏幕重新选择某个任务时,系统会在特定情况下清除该任务(从根 Activity 之上的堆栈中移除所有 Activity)。 系统通常会在用户一段时间(如 30 分钟)内未访问任务时执行此操作。不过,如果该属性的值是“true”,则无论用户如何到达任务,将始终返回到最后状态的任务。 例如,在网络浏览器这类存在大量用户不愿失去的状态(如多个打开的标签)的应用中,该属性会很有用。
android:clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 “true”,则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。android:finishOnTaskLaunch
这个属性和android:allowReparenting属性相似,不同之处在于allowReparenting属性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch属性是销毁实例。如果这个属性和android:allowReparenting都设定为“true”,则以这个属性为准。
“singleTask”细节讨论
“singleTask”启动模式由于其特殊性,经常使人迷惑。所以,重点讨论一下设置了”singleTask”启动模式的Activity的特点:
可以从三个方面来理解singTask:
系统创建新任务并实例化位于新任务底部的 Activity。
系统是否会为Activity创建新任务还跟taskAffinity属性有关。设置了”singleTask”启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity相同的任务(默认同一个应用的affinity相同,都为包名);
如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了”singleTask”启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent()方法向其传送Intent,而不是创建新实例。
如果设置了”singleTask”启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。一次只能存在 Activity 的一个实例。
多数人认为通过清单文件为Activity指定singTask启动模式(方式一)和在Intent设置FLAG_ACTIVITY_NEW_TASK标志位(方式二)是一样的,其实二者还是有区别的:
- 第二种方式的优先级要高于第一种,当两种同时存在时,以第二种为准。
针对“一次只能存在Activity的一个实例”这条特性,第二种方式只有在为Activity设置一个独立的taskAffinity属性值的情况下才生效。
举两个例子说明:
例一:
app中有两个Activity,A和B,其中A启动B,然后B通过方式二启动自身两次。默认情况下返回栈的顺序是:A-B-B’-B”。可通过adb shell dumpsys activity
查看返回栈中Activity信息。- 当通过方式一指定B为singTask启动模式时,返回栈的顺序是:A-B;
- 通过方式二在Intent设置FLAG_ACTIVITY_NEW_TASK标志位时,返回栈的顺序是:A-B-B’-B”。说明FLAG_ACTIVITY_NEW_TASK标志位没有起作用。
- 通过方式二在Intent设置FLAG_ACTIVITY_NEW_TASK标志位,并为B设置一个独立的taskAffinity属性值时,返回栈的顺序是:A-B;
Intent intent = new Intent(MainActivity.this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); }
例二:
app1和app2分别有两个Activity,1A,1B和2A,2B。
启动顺序为:1A-1B-2B-1B-2B- 默认情况:1A-1B-2B-1B’-2B’都在同一个Task中。
- 当通过方式一指定1B和2B为singTask启动模式时,并为1B和2B设置一个独立的taskAffinity属性值时,返回栈最后的顺序是:1A-1B-2B;
- 通过方式二在启动1B和2B的Intent设置FLAG_ACTIVITY_NEW_TASK标志位时,返回栈最后的顺序是:1A-1B-1B’-2B-2B’;
- 通过方式二在启动1B和2B的Intent设置FLAG_ACTIVITY_NEW_TASK标志位,并为1B和2B设置一个独立的taskAffinity属性值时,返回栈最后的顺序是:1A-1B-2B;