ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
### Google官网文章任务和返回栈《摘抄》 一个 Activity 甚至可以启动设备上其他应用中存在的 Activity,例如,如果应用想要发送电子邮件,则可将 Intent 定义为执行“发送”操作并加入一些数据,如电子邮件地址和电子邮件。 然后,系统将打开其他应用中声明自己处理此类 Intent 的 Activity。在这种情况下,Intent 是要发送电子邮件,因此将启动电子邮件应用的“撰写”Activity(如果多个 Activity 支持相同 Intent,则系统会让用户选择要使用的 Activity)。发送电子邮件时,Activity 将恢复,看起来好像电子邮件 Activity 是您的应用的一部分。 **即使这两个 Activity 可能来自不同的应用,但是 Android 仍会将 Activity 保留在相同的任务中,以维护这种无缝的用户体验。** **任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。** >[info] **注意**: 可以在Activity的onCreate()方法中调用getTaskId()方法(该方法内部调用了类ActivityManagerService的getTaskForActivity方法,而getTaskForActivity这个方法内部又调用了ActivityRecord.getTaskForActivityLocked()方法)得出该Activity所在的栈的ID **当前 Activity(标记为Activity①)** 启动**另一个 Activity(标记为Activity②)** 时,该新 Activity(**Activity②**) 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity(**Activity①**) **仍保留在堆栈中,但是处于停止状态**。**Activity(就是指Activity,不管是Activity①还是Activity②) 停止时,系统会保持其用户界面的当前状态**。 用户按“返回”按钮时,当前 Activity(**Activity②**) 会从堆栈顶部弹出(Activity(**Activity②**) 被销毁),而前一个 Activity(**Activity①**) 恢复执行(恢复其 UI 的前一状态)。 >[info]**总结**: 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。 因此,返回栈以“后进先出”对象结构运行。 ![](https://box.kancloud.cn/2015-12-01_565da698aaae5.jpg) 图 1 通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,直观呈现了这种行为。 图解:栈stack中原先只有Activity 1 ,之后Activity 1启动Activity 2,栈stack中有Activity 1 和Activity 2,Activity 1 处于停止状态,Activity 2处于活动状态;后来Activity 2又启动了Activity 3,那Activity 3处于活动状态,Activity 2 和Activity 1处于停止状态; 之后按下导航的返回键或者调用了finish()方法后,处于栈顶的Activity 3被弹出,Activity 2又处于栈顶,处于活动状态。这也就印证了**栈的后进先出**的思想 。如果用户继续按“返回”,堆栈中的相应 Activity 就会被弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。 当所有 Activity 均从堆栈中移除后,任务即不复存在。任务栈是一种“后进先出”的栈结构,这个比较好理解,每按一下back 键就会有一个Activity 出栈,直到栈空为止,当栈中无任何Activity的时候,系统就会回收这个任务栈。 ### Android 多任务运行机制 ![](https://box.kancloud.cn/2015-12-01_565da699b8109.jpg) 图 2. 两个任务:任务 B 在前台接收用户交互,而任务 A 则在后台等待恢复。 任务(Task1)是一个有机整体,当用户开始新任务(Task2)或通过“主页”按钮(Home键)转到主屏幕时,可以将该任务(Task1)移动到“后台”。 尽管在后台时,该任务(Task1)中的所有 Activity 全部停止,但是任务(Task1)的返回栈仍旧不变,也就是说,**当另一个任务(Task2)发生时,该任务(Task1)仅仅失去焦点而已**,如图 2 中所示。然后,任务(Task1)可以返回到“前台”,用户就能够回到离开时的状态。 例如,假设当前任务(任务 A)的堆栈中有三个 Activity,即当前 Activity 下方还有两个 Activity。 用户先按“主页”按钮(Home键),然后从应用启动器启动新应用。 显示主屏幕时,任务 A 进入后台。**新应用启动时,系统会使用自己的 Activity 堆栈为该应用启动一个任务(任务 B)**。与该应用交互之后,用户再次返回主屏幕并选择最初启动任务 A 的应用。现在,任务 A 出现在前台,其堆栈中的所有三个 Activity 保持不变,而位于堆栈顶部的 Activity 则会恢复执行。 此时,用户还可以通过转到主屏幕并选择启动该任务的应用图标(或者,通过从概览屏幕选择该应用的任务)切换回任务 B。这是 Android 系统中的一个多任务示例。 >[warning] **注意**:**后台可以同时运行多个任务。但是,如果用户同时运行多个后台任务,则系统可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失**。请参阅下面有关 [Activity 状态](https://developer.android.com/guide/components/tasks-and-back-stack.html#ActivityState)的部分。 Activity 和任务的默认行为总结如下: 1. 当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。 2. 用户通过按“主页”按钮(Home键)离开任务时,当前 Activity 将停止且其任务会进入后台。 系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。 3. 如果用户按“返回”按钮,则当前 Activity(处于栈顶的那个Activity即上面1中的ActivityB) 会从堆栈弹出并被销毁。 堆栈中的前一个 Activity(Activity A) 恢复执行。销毁 Activity 时,系统不会保留该 Activity 的状态。 4. 即使来自其他任务,Activity 也可以多次实例化。(但是多次实例化,会造成内存资源浪费,具体可参考下面) #### 一个 Activity 将多次实例化 ![](https://box.kancloud.cn/2015-12-01_565da69aa2484.jpg) 图 3. 一个 Activity 将多次实例化。 由于**返回栈中的 Activity 永远不会重新排列**,因此如果应用允许用户从多个 Activity 中启动特定 Activity,则会创建该 Activity 的新实例并推入堆栈中(而不是将 Activity 的任一先前实例置于顶部)。 因此,**应用中的一个 Activity 可能会多次实例化(即使 Activity 来自不同的任务)**,如图 3 所示。因此,如果用户使用“返回”按钮向后导航,则会按 Activity 每个实例的打开顺序显示这些实例(每个实例的 UI 状态各不相同)。 但是,如果您不希望 Activity 多次实例化,则可修改此行为。 具体操作方法将在后面的管理任务部分中讨论。 ### 保存 Activity 状态 正如上文所述,当 Activity 停止时,系统的默认行为会保留其状态。 这样一来,当用户导航回到上一个 Activity 时,其用户界面与用户离开时一样。**但是,在 Activity 被销毁且必须重建时,您可以而且应当主动使用回调方法保留 Activity 的状态。** 系统停止您的一个 Activity 时(例如,新 Activity 启动或任务转到前台(即重新开始一个新的任务)),**如果系统需要回收系统内存资源,则可能会完全销毁该 Activity。 发生这种情况时,有关该 Activity 状态的信息将会丢失。如果发生这种情况,系统仍会知道该 Activity 存在于返回栈中,但是当该 Activity 被置于堆栈顶部时,系统一定会重建 Activity(而不是恢复 Activity)**。 **为了避免用户的工作丢失,您应主动通过在 Activity 中实现 [onSaveInstanceState()](https://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle)) 回调方法来保留工作**。 如需了解有关如何保存 Activity 状态的详细信息,请参阅 [Activity](https://developer.android.com/guide/components/activities.html#SavingActivityState) 文档。 ### 管理任务 >[info] **情景**: Android 管理任务和返回栈的方式(如上所述,即:将所有连续启动的 Activity 放入同一任务和“后进先出”堆栈中)非常适用于大多数应用,而您不必担心 Activity 如何与任务关联或者如何存在于返回栈中(也可以自定义设置)。 >但是,您可能会决定要中断正常行为。 >也许您希望应用中的 Activity 在启动时开始新任务(而不是放置在当前任务中); >或者,当启动 Activity 时,您希望将其现有实例上移一层(而不是在返回栈的顶部创建新实例); >或者,您希望在用户离开任务时,清除返回栈中除根 Activity 以外的所有其他 Activity。 通过使用 `<activity>` 清单文件元素中的属性和传递给 [startActivity()](https://developer.android.com/reference/android/app/Activity.html#startActivity(android.content.Intent)) 的 Intent 中的标志,您可以执行所有这些操作以及其他操作。 在这一方面,您可以使用的主要 `<activity>` 属性包括: [taskAffinity](https://developer.android.com/guide/topics/manifest/activity-element.html#aff) [launchMode](https://developer.android.com/guide/topics/manifest/activity-element.html#lmode) [allowTaskReparenting](https://developer.android.com/guide/topics/manifest/activity-element.html#reparent) [clearTaskOnLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#clear) [alwaysRetainTaskState](https://developer.android.com/guide/topics/manifest/activity-element.html#always) [finishOnTaskLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#finish) 您可以使用的主要 Intent 标志包括: [FLAG_ACTIVITY_NEW_TASK](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK) [FLAG_ACTIVITY_CLEAR_TOP](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TOP) [FLAG_ACTIVITY_SINGLE_TOP](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_SINGLE_TOP) 在下文中,您将了解如何使用这些清单文件属性和 Intent 标志定义 Activity 与任务的关联方式,以及 Activity 在返回栈中的行为方式。 此外,我们还单独介绍了有关如何在概览屏幕中显示和管理任务与 Activity 的注意事项。 如需了解详细信息,请参阅[概览屏幕](https://developer.android.com/guide/components/recents.html)。 通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。 >[warning] **注意**:大多数应用都不得中断 Activity 和任务的默认行为: 如果确定您的 Activity 必须修改默认行为,当使用“返回”按钮从其他 Activity 和任务导航回到该 Activity 时,请务必要谨慎并确保在启动期间测试该 Activity 的可用性。请确保测试导航行为是否有可能与用户的预期行为冲突。 ### 定义启动模式 启动模式允许您定义 Activity 的新实例如何与当前任务关联。 您可以通过两种方法定义不同的启动模式: 1. [使用清单文件](https://developer.android.com/guide/components/tasks-and-back-stack.html#ManifestForTasks) 在清单文件中声明 Activity 时,您可以指定 Activity 在启动时应该如何与任务关联。 2. [使用 Intent 标志](https://developer.android.com/guide/components/tasks-and-back-stack.html#IntentFlagsForTasks) 调用 [startActivity()](https://developer.android.com/reference/android/app/Activity.html#startActivity(android.content.Intent)) 时,可以在 [Inten](https://developer.android.com/reference/android/content/Intent.html)t 中加入一个标志(上面管理任务章节中的3种Intent的flag),用于声明新 Activity 如何(或是否)与当前任务关联。 >[info] **注**:某些适用于清单文件的启动模式不可用作 Intent 标志,同样,某些可用作 Intent 标志的启动模式无法在清单文件中定义。 因此,如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。如果这两个 Activity(Activity A和Activity B) 均定义 Activity B 应该如何与任务关联(可以一个在intent中,一个在清单文件中),则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity B 的请求(如其清单文件中所定义)。 #### **使用清单文件(AndroidMainifest启动模式)** 在清单文件中声明 Activity 时,您可以使用 `<activity>` 元素的 [launchMode](https://developer.android.com/guide/topics/manifest/activity-element.html#lmode) 属性指定 Activity 应该如何与任务关联。 [launchMode](https://developer.android.com/guide/topics/manifest/activity-element.html#lmode) 属性指定有关应如何将 Activity 启动到任务中的指令。您可以分配给 launchMode 属性的启动模式共有四种: * **"standard"(默认模式)** 默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。如果不指定Activity 的启动模式,则使用这种方式启动Activity。这种启动模式每次都会创建新的实例,每次点击standard 模式创建Activity 后,都会创建新的MainActivity 覆盖在原Activity 上。 ![standard启动模式](https://box.kancloud.cn/e55ae98a7de1dfa14b8af277990d2608_299x369.jpg) 被创建的实例的生命周期符合典型情况下Activity 的生命周期,如上节描述,它的onCreate 、onStart 、onResume 都会被调用;在这种模式下,谁启动了这个Activity,那么这个Activity 就运行在启动它的那个Activity所在的栈中。 >[warning] **注意**:当我们用ApplicationContext 去启动standard 模式的Activity 的时候会报错,错误如下: ~~~ E/AndroidRuntime(674): android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? ~~~ 相信这句话读者一定不陌生,这是因为standard 模式的Activity 默认会进入启动它的Activity 所属的任务找中,但是由于非Activity 类型的Context 〈如ApplicationContext )并没有所谓的任务栈,所以这就有问题了。解决这个问题的方法是为待启动Activity 指定FLAG_ACTIVlTY _NEW TASK 标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候待启动Activity 实际上是以singleTask 模式启动的,读者可以仔细体会。 * * * * * * **"singleTop"(栈顶复用模式)** 如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 [onNewIntent()](https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)) 方法向其传送 Intent,通过此方法的参数我们可以取出当前请求的信息;而不是创建 Activity 的新实例。需要注意的是,这个Activity 的onCreate 、onStart 不会被系统调用,因为它井没有发生改变。 Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例;这句话是正确的,但前提是**位于返回栈顶部的 Activity 并不是 Activity 的现有实例**,如果是Activity 的现有实例,而且启动模式是**singleTop**,则不会多次实例化。 例如,假设任务的返回栈包含根 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()](https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)) 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。但是,**如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 "singleTop" 也是如此**,也就是说,**如果B没有位于栈顶,即便B的启动模式是"singleTop",在收到针对 B 类 Activity 的 Intent时,也会向堆栈添加 B 的新实例**。 >[info] **注**:为某个 Activity 创建新实例时,用户可以按“返回”按钮返回到前一个 Activity。 但是,当 Activity 的现有实例处理新 Intent 时,则在新 Intent 到达 [onNewIntent()](https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)) 之前,用户无法按“返回”按钮返回到 Activity 的状态。 总结:这种模式通常适用于就收到消息后现实的界面,比如QQ接收到消息后弹出Activity,如果一次来了10条消息,总不能一次弹10个Activity。 * * * * * * **"singleTask"(栈内复用模式)** 系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。 >[info]**注**:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。 singleTask 模式与singlTop 模式类似,只不过singleTop 是检测检顶元素是否是需要启动的Activity ,而singleTask 是检测整个Activity 战中是否存在当前需要启动的Activity。如果存在, 则将该Activity 置于栈顶,并将该Activity 以上的Activity 都销毁(由于singleTask 默认具有clearTop 的效果)。不过这里是指在同一个App 中启动这个singleTask 的Activity,如果是其他程序以singleTask 模式来启动这个Activity ,那么它将创建一个新的任务栈。不过这里有一点需要注意的是,如果启动的模式为singleTask 的Activity 巳经在后台一个任务栈中了,那么启动后,而后台的这个任务栈将一起被切换到到前台。 举例如下: 1. 比如目前任务栈S1 中的情况为ABC,这个时候Activity D 以singleTask 模式请求启动,其所需要的任务栈为S2 ,由于S2 和D 的实例均不存在,所以系统会先创建任务栈S2 ,然后再创建D 的实例井将其入栈到S2 。 2. 另外一种情况,假设D 所需的任务栈为S1,其他情况如上面例子l 所示,那么由于S1 已经存在,所以系统会直接创建D的实例井将其入栈到S1。 3. 如果D 所需的任务栈为S1,并且当前任务栈S1 的情况为ADBC ,根据栈内复用的原则,此时D 不会重新创建,系统会把D 切换到栈顶并调用其onNewlntent 方法,同时由于singleTask 默认具有clearTop 的效果,会导致栈内所有在D 上面的Activity全部出栈,于是最终S1中的情况为AD。这一点比较特殊。 上面例子中所需要的任务栈,具体是指什么??可以下文中摘抄的Android开发艺术探讨中内容 **总结**:使用这个模式创建的Activity不是在新的任务栈中被打开, 就是将已经打开的Activity切换到前台,所以这种启动模式通常可以用来退出整个应用: 将主Activity 设为singleTask 模式,然后在要退出的Activity 中转到主Activity,从而将主Activity之上的Activity 都清除,然后重写主Activity 的onNewIntent()方法,在方法中加上一行代码finish(),将最后一个Activity 结束掉。 * * * * * * **"singleInstance(单实例模式)"** 与 "singleTask" 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。而且**代码中不能实现相同的启动模式效果。** 这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity 只能单独地位于一个任务栈中,换句话说,比如Activity A 是singlelnstance 模式, 当A 启动后,系统会为它创建一个新的任务栈,然后A 独自在这个新的任务找中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。 singleInstance 这种启动模式和使用的浏览器工作原理类似。在多个程序中访问浏览器时 ,如果当前浏览器没有打开,则打开浏览器,否则会在当前打开的浏览器中访问。申明为singleInstance 的Activity 会出现在一个新的任务栈中,而该任务栈中只存在这一个Activity 。举个例子米说,如果应用A 的任务栈中创建了MainActivity 实例,且启动模式为singleInstance,如果应用B 也要激活MainActivity , 则不需要创建,两个应用共享该Activity实例。这种启动模式常用于需要与程序分离的界面,如在SetupWizard中调用紧急呼叫,就是使用这种启动模式。 不同于以上3 种启动模式,指定为singlelnstance 模式的活动会启用一个新的返回找来管理这个活动(其实如果singleTask 模式指定了不同的taskAftinity ,也会启动一个新的返回栈)。那么这样做有什么意义呢? 想象以下场景,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢? 使用前面3 种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singlelnstance 模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。(具体事例可参考郭霖的第一行代码中例子) * * * * * #### 总结及补充 上面介绍了几种启动模式,这里需要补充一种情况,我们假设目前有2 个任务栈,前台任务栈的情况为AB ,而后台任务栈的情况为CD ,这里假设CD 的启动模式均为singleTask 。现在请求启动D,那么整个后台任务校都会被切换到前台,这个时候整个后台任务栈变成了ABCD。当用户按back 键的时候,列表中的Activity 会一一出钱,如图1-7 所示。如果不是请求启动D 而是启动C,那么情况就不一样了,请看图1-8 ,具体原因在本节后面会再进行详细分析。 ![](https://box.kancloud.cn/60fb61f430b97af01c0927d2c29d37a3_601x468.jpg) ![](https://box.kancloud.cn/eccc7b43d2bddda26de64653bebb2cfc_553x574.jpg) * **什么是某个Activity所需要的任务栈**? 在singleTask 启动模式中,多次提到某个Activity所需要的任务栈,什么是Activity 所需要的任务找呢?这要从一个参数说起: **TaskAffinity**,可以翻译为**任务相关性,可以定义Activity 的亲和关系**。 - TaskAffinity,这个参数在清单文件的`<activity>`标签的的`android:taskAffinity="string"`,同时特也是类ActivityInfo的成员变量;它**标识了一个Activity 所需要的任务栈的名字**,**默认情况下,所有Activity所需的任务栈的名字为应用的包名**。当然,我们**可以为每个Activity 都单独指定TaskAffinity属性**,这个**属性值必须不能和包名相同,否则就相当于没有指定**。 - **TaskAffinity 属性主要和singleTask 启动模式或者allowTaskReparenting 属性配对使用,在其他情况下没有意义**。 - 另外,**任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity 位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。** - 当TaskAffinity 和singleTask 启动模式配对使用的时候,它是具有该模式的Activity 的目前任务栈的名字,待启动的Activity 会运行在名字和TaskAffinity 相同的任务栈中。 - 当TaskAffinity 和allowTaskReparenting 结合的时候,这种情况比较复杂,会产生特殊的效果。当一个应用A 启动了应用B 的某个Activity 后,如果这个Activity 的allowTaskReparenting 属性为true 的话,那么当应用B 被启动后,此Activity 会直接从应用A 的任务栈转移到应用B 的任务栈中。这还是很抽象,可看下面的例子以及解释。 - **allowTaskReparenting**这个参数可以在`<application>`和`<activity>`标签定义,具体含义是:当启动 Activity 的任务接下来转至前台时,Activity 是否能从该任务转移至与其有亲和关系的任务 —“true”表示它可以转移,“false”表示它仍须留在启动它的任务处。如果未设置该属性,则对 Activity 应用由 `<application>` 元素的相应 allowTaskReparenting 属性设置的值。 **默认值**为“**false**”。 - **正常情况下,当 Activity 启动时,会与启动它的任务关联,并在其整个生命周期中一直留在该任务处**。您可以利用该属性强制 Activity 在其当前任务不再显示时将其父项更改为与其有亲和关系的任务。**该属性通常用于使应用的 Activity 转移至与该应用关联的主任务**。 - **Activity 的亲和关系由 taskAffinity 属性定义**。 任务的亲和关系通过读取其根 Activity 的亲和关系来确定。因此,按照定义,根 Activity 始终位于具有相同亲和关系的任务之中。 由于**具有“singleTask”或“singleInstance”启动模式的 Activity 只能位于任务的根,因此更改父项仅限于“standard”和“singleTop”模式。** - **举例如下**:再具体点,比如现在有2 个应用A和B, A 启动了B 的一个Activity C,然后按Home 键回到桌面,然后再单击B 的桌面图标,这个时候井不是启动了B 的主Activity ,而是重新显示了已经被应用A 启动的Activity C,或者说, C 从A 的任务栈转移到了B 的任务栈中。可以这么理解,由于A 启动了C,这个时候C 只能运行在A 的任务栈中,但是C 属于B 应用,正常情况下,它的Task.Affinity 值肯定不可能和A 的任务栈相同〈因为包名不同〉。所以,当B 被启动后, B 会创建自己的任务栈,这个时候系统发现C 原本所想要的任务栈已经被创建了,所以就把C 从A 的任务栈中转移过来了。 无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按“返回”按钮始终会转到前一个 Activity。 但是,如果启动指定 singleTask 启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity。 图 4 显示了这种情况。 ![](https://box.kancloud.cn/9e1a6ba53c9f3cfd646b7f2c9ab879c4_550x309.png) 图 4. 显示如何将启动模式为“singleTask”的 Activity 添加到返回栈。 如果 Activity 已经是某个拥有自己的返回栈的后台任务的一部分,则整个返回栈也会上移到当前任务的顶部。 如需了解有关在清单文件中使用启动模式的详细信息,请参阅 `<activity> `元素文档,其中更详细地讨论了 launchMode 属性和可接受的值。 >[info] **注**:使用 launchMode 属性为 Activity 指定的行为可由 Intent 附带的 Activity 启动标志替代,下文将对此进行讨论。 #### **使用 Intent 标志** > 启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity 与其任务的默认关联方式。 代码如下: ~~~ Intent intent = new Intent(); intent.setClass(MainActivity.this, SecondActivity.class); intent . addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); ~~~ Activity 的Flags 有很多,这里主要分析一些比较常用的标记位。标记位的作用很多, - 设定Activity 的启动模式,比如FLAG_ACTIVITY_NEW_TASK 和FLAG_ACTIVITY _SINGLE_ TOP 等: - 影响Activity 的运行状态,比如FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 等。 下面主要介绍几个比较常用的标记位,剩下的标记位读者可以查看官方文档去了解,**大部分情况下,我们不需要为Activity 指定标记位**,因此,对于标记位理解即可。在使用标记位的时候,要注意有些标记位是系统内部使用的,应用程序不需要去手动设置这些标记位以防出现问题。 - **FLAG_ACTIVITY_NEW_TASK** 在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 onNewIntent() 中收到新 Intent。 正如前文所述,这**会产生与 "singleTask"launchMode 值相同的行为**。 使用一个新的Task 来启动一个Activity ,但启动的每个Activity 都将在一个新的Task 中。该Flag 通常使用在从Service 中启动Activity 的场景,由于在Service 中并不存在Activity 栈,所以使用该Flag来创建一个新的Activity 栈,井创建新的Activity实例。 - **FLAG_ACTIVITY_SINGLE_TOP** 如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则现有实例会接收对 onNewIntent() 的调用,而不是创建Activity 的新实例。 正如前文所述,这**会产生与 "singleTop"launchMode 值相同的行为**。 - **FLAG_ACTIVITY_CLEAR_TOP** 如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。 产生这种行为的 launchMode 属性没有值。 具有此标记为的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity 都要出栈。这个模式一般需要和FLAG_ACTIVITY_ NEW_ TASK 配合使用,在这种情况下, 被启动Activity 的实例如果已经存在,那么系统就会调用它的onNewIntent()。 如果被启动的Activity 采用standard 模式启动,那么它连同它之上的Activity 都要出栈,系统会创建新的Activity 实例并放入栈顶。通过1.2.1 节中的分析可以知道, singleTask启动模式默认就具有此标记位的效果。 FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。 >[info] **注**:如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中移除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例。 - **FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS** 具**有这个标记的Activity 不会出现在历史Activity 的列表**中,当某些情况下我们不希望用户通过历史列表回到我们的Activity 的时候这个标记比较有用。它等同于在XML 中指定Activity 的属性`android :excludeFromRecents=true”` ### 处理关联 **“关联”指示 Activity 优先属于哪个任务**。**默认情况下,同一应用中的所有 Activity 彼此关联**。 因此,**默认情况下,同一应用中的所有 Activity 优先位于相同任务中**。 不过,您**可以修改 Activity 的默认关联**:在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。 #### 如何关联 **可以使用 `<activity>` 元素的 [taskAffinity](https://developer.android.com/guide/topics/manifest/activity-element.html#aff) 属性修改任何给定 Activity 的关联。** **taskAffinity** 属性**取字符串值**,该值必**须不同于在 [`<manifest> `](https://developer.android.com/guide/topics/manifest/manifest-element.html)元素中声明的默认软件包名称,因为系统使用该名称标识应用的默认任务关联。** 在两种情况下,关联会起作用: - 启动 Activity 的 Intent 包含 [FLAG_ACTIVITY_NEW_TASK](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK) 标志。 **默认情况下,新 Activity 会启动到调用 startActivity() 的 Activity 任务中。它将推入与调用方相同的返回栈**。 **但是,如果传递给 [startActivity()](https://developer.android.com/reference/android/app/Activity.html#startActivity(android.content.Intent)) 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志,则系统会寻找其他任务来储存新 Activity。这通常是新任务,但未做强制要求**。 **如果现有任务与新 Activity 具有相同关联,则会将 Activity 启动到该任务中。 否则,将开始新任务。** **如果此标志导致 Activity 开始新任务,且用户按“主页”按钮离开,则必须为用户提供导航回任务的方式**。 有些实体(如通知管理器)始终在外部任务中启动 Activity,而从不作为其自身的一部分启动 Activity,因此它们始终将 FLAG_ACTIVITY_NEW_TASK 放入传递给 startActivity() 的 Intent 中。请注意,**如果 Activity 能够由可以使用此标志的外部实体调用,则用户可以通过独立方式返回到启动的任务**,例如,使用启动器图标(任务的根 Activity 具有 [CATEGORY_LAUNCHER](https://developer.android.com/reference/android/content/Intent.html#CATEGORY_LAUNCHER) Intent 过滤器;请参阅下面的[启动任务](https://developer.android.com/guide/components/tasks-and-back-stack.html#Starting)部分)。 - Activity 将其 allowTaskReparenting 属性设置为 "true"。 **在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)**。 例如,假设将报告所选城市天气状况的 Activity(**假定为Activity W**) 定义为**旅行应用(App T**)的一部分。 它(**Activity W**)与同一应用(**App T**)中的其他 Activity 具有相同的关联(默认应用关联),并允许利用此属性重定父级。当您的一个 Activity (**Activity O**)启动天气预报 Activity(**Activity W**) 时,它(**Activity W**)最初所属的任务与您的 Activity (**Activity O**)相同。 但是,当旅行应用(**App T**)的任务出现在前台时,系统会将天气预报 Activity (**Activity W**)重新分配给该任务并显示在其中。 >[info] **提示**:如果从用户的角度来看,一个 .apk 文件包含多个“应用”,则您可能需要使用 [taskAffinity ](https://developer.android.com/guide/topics/manifest/activity-element.html#aff)属性将不同关联分配给与每个“应用”相关的 Activity。 ### 清理返回栈 **如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。** 您可以使用下列几个 Activity 属性修改此行为: - [alwaysRetainTaskState](https://developer.android.com/guide/topics/manifest/activity-element.html#always) 如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。 * [clearTaskOnLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#clear) **如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。** 换而言之,它与 alwaysRetainTaskState 正好相反。 **即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。** * [finishOnTaskLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#finish) 此属性类似于 clearTaskOnLaunch,但它**对单个 Activity 起作用,而非整个任务**。 此外,它还**有可能会导致任何 Activity 停止,包括根 Activity。** **设置为 "true" 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。** ### 启动任务 **通过为 Activity 提供一个以 "`android.intent.action.MAIN`" 为指定操作、以 "`android.intent.category.LAUNCHER`" 为指定类别的 Intent 过滤器,您可以将 Activity 设置为任务的入口点。** 例如: ~~~ <activity ... > <intent-filter ... > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> ... </activity> ~~~ 此类 Intent 过滤器会使 Activity 的图标和标签显示在应用启动器中,让用户能够启动 Activity 并在启动之后随时返回到创建的任务中。 **第二个功能**非常重要:**用户必须能够在离开任务后,再使用此 Activity 启动器返回该任务**。 因此,**只有在 Activity 具有 [ACTION_MAIN](https://developer.android.com/reference/android/content/Intent.html#ACTION_MAIN) 和 [CATEGORY_LAUNCHER](https://developer.android.com/reference/android/content/Intent.html#CATEGORY_LAUNCHER) 过滤器时,才应该使用将 Activity 标记为“始终启动任务”的两种启动模式,即 "singleTask" 和 "singleInstance"**。例如,我们可以想像一下如果缺少过滤器会发生什么情况: Intent 启动一个 "singleTask" Activity,从而启动一个新任务,并且用户花了些时间处理该任务。然后,用户按“主页”按钮。 任务现已发送到后台,而且不可见。现在,用户无法返回到任务,因为该任务未显示在应用启动器中。 **如果您并不想用户能够返回到 Activity,对于这些情况,请将 `<activity>`元素的 [finishOnTaskLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#finish) 设置为 "true"(请参阅清理清理返回栈)。** 有关如何在概览屏幕中显示和管理任务与 Activity 的更多信息,请参阅[概览屏幕](https://developer.android.com/guide/components/recents.html)。 ### Android群英传《摘抄》