to top

Tasks and Back Stack

快速查看
  • 所有的 activity 都属于一个task
  • task 包含了 activity 的集合,并按照用户使用的顺序排列
  • task 可以转入后台,并会保存每个 activity 的状态,这样用户运行其它 task 时就不会丢失当前的工作了。
在本文中(参见目录)
相关文章
Multitasking the Android Way
参阅
Android 设计 : 导航
应用程序的生命周期视频
<activity> manifest 元素

一个应用程序通常包含多个 activity。 每个 activity 在设计时都应该以执行某个用户发起的 action 作为核心目标,并且它能启动其它 activity。 比如,一个 email 应用可能会用一个 activity 来列出所有的新 email,当用户选中一封 email 时,再打开一个新的 activity 来显示这封 email。

一个 activity 甚至可能会启动另一个应用中的 activity。 比如,如果你的应用需要发送 email,你可以定义一个 intent 来执行“send” action,其中包含一些数据,如 email 地址、正文等。 然后会打开一个其它应用中已声明能够处理这类 intent 的 activity。 这里是一个发送 email 的 intent,所以会打开一个 email 应用的“新建邮件”activity(如果有多个 activity 都支持同一个 intent,则系统或让用户选择一个打开)。 email 发送完毕后,你的 activity 将会恢复,看起来 email activity 就像是你的应用中的一部分一样。 虽然这两个 activity 可能来自不同的应用,通过把它们放入同一个task,Android 保证了无缝的用户体验。

task 是多个 activity 的集合,用户进行操作时将与这些 activity 进行交互。 这些 activity 按照启动顺序排队存入一个栈(即“back stack”)。

大部分 task 都启动自 Home 屏幕。当用户触摸 application launcher 中的图标(或 Home 屏幕上的快捷图标)时,应用程序的 task 就进入前台。 如果该应用不存在 task(最近没有使用过此应用),则会新建一个 task,该应用的“main”activity 作为栈的根 activity 被打开。

当用户返回到 home屏幕执行另一个 task 时,一个 task 被移动到后台执行,此时它的返回栈(back stack)也被保存在后台, 同时 android 为新 task 创建一个新的返回栈(back stack),当它被再次运行从而返回前台时,它的返回栈(back stack)被移到前台,并恢复其之前执行的activity,如下图所示。 如果后台有太多运行 task ,系统将会杀死一些 task 释放内存。

如果当前 activity 启动了另一个 activity,则新的 activity 被压入栈顶并获得焦点。 前一个 activity 仍保存在栈中,但是被停止。activity 停止时,系统会保存用户界面的当前状态。 当用户按下返回键,则当前 activity 将从栈顶弹出(被销毁),前一个 activity 将被恢复(之前的用户界面状态被恢复)。 activity 在栈中的顺序永远不会改变,只会压入和弹出——被当前 activity 启动时压入栈顶,用户用返回键离开时弹出。 这样,back stack 以“后进先出”的方式运行。图1 以时间线的方式展示了多个 activity 切换时对应当前时间点的 back stack 状态。

Diagram backstack.png


图 1. 如图所示, task 中的每个新 activity 都会相应在 back stack 中增加一项。当用户按下返回键时,当前 activity 被销毁,前一个 activity 被恢复。

如果用户不停地按下返回键,则栈中每个 activity 都会依次弹出,并显示前一个 activity,直至用户回到 Home 屏幕(或者任一启动该 task 的 activity)。 当所有 activity 都从栈中弹出后, task 就此消失。

Diagram multitasking.png

图 2. 两个 task :Task B 在前台与用户交互,而 Task A 在后台等待唤醒。

Diagram multiple instances.png

图 3. activity A 被实例化多次。

一个 task (task)是一个整体单位</span>, 当用户启动一个新的 task 或者回到 Home 屏幕时,这个 task 就转为“后台”。 当 task 处于后台时,其中所有的 activity 都处于停止状态,但是这个 task 的 back stack 仍然完整保留——如图2所示,在其它 task 获得焦点期间,这个 task 只是失去焦点而已。 task 可以返回“前台”,所以用户能够自离开的地方继续工作。比如,假定当前 task (Task A)的栈中共有3个 activity —— 下面有两个 activity。 这时,用户按下Home键,然后从 application launcher 中启动一个新的应用。当 Home 屏幕出现时,Task A 进入后台。 新的应用启动时,系统会为它开启一个 task (Task B),其中放入新应用中的 activity。 用完这个应用后,用户再次返回 Home 屏幕,并选中那个启动 Task A 的应用。 现在,Task A 进入前台——栈中的三个 activity 仍然完好,位于最顶部的 activity 恢复运行。 这时,用户仍然可以切回 Task B,通过回到 Home 屏幕并选择相应图标即可(或者触摸并按住Home键调出最近 task 列表并选中)。 以下是 Android 多 task 的实例。

注意: 可以在后台同时保留多个 task 。但是,假如用户同时运行着多个后台 task ,则系统可能会销毁后台 activity 以便腾出内存,这会导致 activity 状态的丢失。 请参阅#保存Activity状态

因为 back stack 中的 activity 顺序永远不会改变,如果应用允许某个 activity 可以让用户启动多次,则新的实例会压入栈顶(而不是打开之前位于栈顶的 activity)。 于是,一个 activity 可能会初始化多次(甚至会位于不同的 task 中),如图3所示。 如果用户用回退键回退时,activity 的每个实例都会按照原来打开的顺序显示出来(用户界面也都按原来状态显示)。 当然,如果你不想让 activity 能被多次实例化,你可以改变它。方法在后续章节#管理多个Task中讨论。

下面把 activity 和 task 的默认特性汇总一下:

当 Activity A 启动 Activity B 时,Activity A 被停止,但系统仍会保存状态(诸如滚动条位置和 form 中填入的文字)。 如果用户在 Activity B 中按下回退键时,Activity A 恢复运行,状态也将恢复。


当用户按下Home键离开 task 时,当前 activity 停止, task 转入后台。 系统会保存 task 中每个 activity 的状态。如果用户以后通过选中启动该 task 的图标来恢复 task , task 就会回到前台,栈顶的 activity 会恢复运行。


如果用户按下回退键,当前 activity 从栈中弹出,并被销毁。 栈中前一个 activity 恢复运行。 当 activity 被销毁时,系统不会保留 activity 的状态。

activity 可以被实例化多次,甚至可以位于不同的 task 中。


导航设计 关于 Android 中应用程序间导航的详情,请参阅 Android 导航 设计指南。


目录

保存Activity状态

如上所述,系统默认会在 activity 停止时保存其状态。这样,当用户返回时,用户的界面能与离开时显示得一样。 不过,你可以——也应该——用回调方法主动地保存 activity 的状态,以便应对 activity 被销毁并重建的情况。

当停止 activity 时(比如启动了一个新 activity 或者 task 转入后台),处于腾出内存的需要,系统也许会完全销毁该 activity。 这样 activity 的状态信息将会丢失。如果发生这种情况,系统仍然是知道该 activity 已经置入 back stack 的。 但是当它位于栈顶时,系统必须重建它(而不是恢复)。为了防止用户工作内容的丢失,你应该主动保存这些内容,通过实现 activity 的 onSaveInstanceState() 回调方法即可。

关于如何保存 activity 状态的详情,请参阅 Activities

管理多个Task

如上所述——把所有已启动的 activity 相继放入同一个 task 中以及一个“后入先出”栈, Android 管理 task 和 back stack 的这种方式适用于大多数应用, 你也不用去管你的 activity 如何与 task 关联及如何弹出 back stack 的。 不过,有时你也许决定要改变这种普通的运行方式。 也许你想让某个 activity 启动一个新的 task (而不是被放入当前 task ); 或者,你想让 activity 启动时只是调出已有的某个实例(而不是在 back stack 顶创建一个新的实例); 或者,你想在用户离开 task 时只保留根 activity,而 back stack 中的其它 activity 都要清空,

你能做的事情还有很多,利用 " <activity> manifest 元素的属性和传入 startActivity() 的 intent 中的标志即可。

这里,你可以使用的 <activity> 属性主要有:


可用的 intent 标志主要有:


在下一节,你可以看到如何利用这些 manifest 属性和 intent 标志来定义 activity 与 task 的关联性,以及 back stack 的工作方式。

警告: 大多数应用不应该改变 activity 和 task 默认的工作方式。 如果你确定有必要修改默认方式,请保持谨慎,并确保 activity 在启动和从其它 activity 用回退键返回时的可用性。 请确保对可能与用户预期的导航方式相冲突的地方进行测试。

定义启动模式

启动模式定义了一个新的 activity 实例与当前 task 的关联方式。定义启动模式的方法有两种:

当你在 manifest 文件中声明一个 activity 时,可以指定它启动时与 task 的关联方式。

调用 startActivity() 时,可以在 Intent 中包含一个标志,用于指明新 activity 如何(是否)与当前 task 相关联。

这样,如果 Activity A 启动了Activity B,则 Activity B 可以在 manifest 中定义它如何与当前 task 关联(如果存在的话), 并且,Activity A 也可以要求 Activity B 与当前 task 的关联关系。 如果两者都定义了,则 Activity A 的请求(intent 中定义)优先于 Activity B 的定义(在 manifest 中)。

注意: manifest 文件中的某些启动模式在 intent 标志中并不可用,反之亦然,intent 中的某些模式也无法在 manifest 中定义。

使用 manifest 文件

在 manifest 文件中声明 activity 时,你可以利用 <activity> 元素的 launchMode 属性来设定 activity 与 task 的关系。

launchMode 属性指明了 activity 启动 task 的方式。 launchMode 属性可设为四种启动模式:

"standard" (默认模式)
默认值。系统在启动 activity 的 task 中创建一个新的 activity 实例,并把 intent 传送路径指向它。该 activity 可以被实例化多次,各个实例可以属于不同的 task,一个 task 中也可以存在多个实例。
"singleTop"
如果 activity 已经存在一个实例并位于当前 task 的栈顶,则系统会调用已有实例的onNewIntent()方法把 intent 传递给已有实例,而不是创建一个新的 activity 实例。activity 可以被实例化多次,各个实例可以属于不同的 task,一个 task 中可以存在多个实例(但仅当 back stack 顶的 activity 实例不是该 activity 的)。
比如,假定 task 的 back stack 中包含了根 activity A 和 activities B、C、D(顺序是 A-B-C-D;D 在栈顶)。
这时过来一个启动 D 的 intent。如果 D 的启动模式是默认的"standard",则会启动一个新的实例,栈内容变为 A-B-C-D-D。
但是,如果 D 的启动模式是"singleTop",则已有的 D 实例会通过onNewIntent():接收这个 intent,因为该实例位于栈顶——栈中内容仍然维持 A-B-C-D 不变。当然,如果 intent 是要启动 B 的,则 B 的一个新实例还是会加入栈中,即使 B 的启动模式是"singleTop"也是如此。

注意: 一个 activity 的新实例创建完毕后,用户可以按回退键返回前一个 activity。 但是当 activity 已有实例正在处理刚到达的 intent 时,用户无法用回退键回到 onNewIntent() 中 intent 到来之前的 activity 状态。

"singleTask"
系统将创建一个新的 task,并把 activity 实例作为根放入其中。但是,如果 activity 已经在其它 task 中存在实例,则系统会通过调用其实例的onNewIntent() 方法把 intent 传给已有实例,而不是再创建一个新实例。 此 activity 同一时刻只能存在一个实例。

注意: 虽然 activity 启动了一个新的 task,但用户仍然可以用回退键返回前一个 activity。

"singleInstance"
除了系统不会把其它 activity 放入当前实例所在的 task 之外,其它均与"singleTask"相同。activity 总是它所在 task 的唯一成员;它所启动的任何 activity 都会放入其它 task 中。

举个例子,Android 的浏览器应用就把 web 浏览器 activity 声明为总是在它自己独立的 task 中打开——把 activity 设为singleTask模式。 这意味着,如果你的应用提交 intent 来打开 Android 的浏览器,则其 activity 不会被放入你的应用所在的 task 中。 取而代之的是,或是为浏览器启动一个新的 task;或是浏览器已经在后台运行,只要把 task 调入前台来处理新 intent 即可。

无论 activity 是在一个新的 task 中启动,还是位于其它已有的 task 中,用户总是可以用回退键返回到前一个 activity 中。 但是,如果你启动了一个启动模式设为singleTask的 activity,且有一个后台 task 中已存在实例的话,则这个后台 task 就会整个转到前台。 这时,当前的 back stack 就包含了这个转入前台的 task 中所有的 activity,位置是在栈顶。 图 4 就展示了这种场景。

Diagram backstack singletask multiactivity.png


图 4. 启动模式为“singleTask”的 activity 如何加入 back stack 的示意。 如果 activity 已经是在后台 task 中并带有自己的 back stack,则整个后台 back stack 都会转入前台,并放入当前 task 的栈顶。

关于在 manifest 文件中使用启动模式的详情,请参阅 <activity> 元素文档,其中详细描述了launchMode属性及其可用值。

注意: 你用 launchMode 属性为 activity 设置的模式可以被启动 activity 的 intent 标志所覆盖,这将在下一节中描述。

<a id="#IntentFlagsForTasks">使用 Intent 标志</a>

在要启动 activity 时,你可以在传给 startActivity() 的 intent 中包含相应标志,以便修改 activity 与 task 的默认关系。这个标志可以修改的默认模式包括:

FLAG_ACTIVITY_NEW_TASK
在新的 task 中启动 activity。如果要启动的 activity 已经运行于某 task 中,则那个 task 将调入前台,最后保存的状态也将恢复,activity 将在onNewIntent()中接收到这个新 intent。
这个过程与前一节所述的"singleTask"launchMode模式值相同。
FLAG_ACTIVITY_SINGLE_TOP
如果要启动的 activity 就是当前 activity(位于back stack 顶),则已存在的实例将接收到一个onNewIntent()调用,而不是创建一个 activity 的新实例。
这个过程与前一节所述的 "singleTop"launchMode模式值相同。
FLAG_ACTIVITY_CLEAR_TOP
如果要启动的 activity 已经在当前 task 中运行,则不再启动一个新的实例,且所有在其上面的 activity 将被销毁,然后通过onNewIntent()传入 intent 并恢复 activity(不在栈顶)的运行。
此种模式在launchMode中没有对应的属性值。

FLAG_ACTIVITY_CLEAR_TOP 常与 FLAG_ACTIVITY_NEW_TASK 一起使用。这表示先定位其它 task 中已存在的 activity,再把它放入可以响应 intent 的位置。

注意: 如果 activity 的启动模式配置为"standard",它会先被移除出栈,再创建一个新的实例来处理这个 intent。 这是因为启动模式为 "standard" 时,总是会创建一个新的实例。

处理affinity

affinity 表示 activity 预期所处的 task 。 缺省情况下,同一个应用中的所有 activity 都拥有同一个 affinity 值。 因此,同一个应用中的所有 activity 默认都期望位于同一个 task 中。 不过,你可以修改 activity 默认的 affinity 值。 不同应用中的 activity 可以共享同一个 affinity 值,同一个应用中的 activity 也可以赋予不同的 task affinity 值。

你可以用<activity> 元素的taskAffinity 属性修改 activity 的 affinity,taskAffinity 属性是一个字符串值,必须与<manifest> 元素定义的包名称保证唯一性,因为系统把这个包名称用于标识应用的默认 task affinity 值。

affinity 将在以下两种情况下发挥作用:

默认情况下,一个新的 activity 将被放入调用 startActivity() 的 activity 所在 task 中,且压入调用者所处的 back stack 顶。 不过,如果传给 startActivity() 的 intent 包含了 FLAG_ACTIVITY_NEW_TASK 标志,则系统会查找另一个 task 并将新 activity 放入其中。这时经常会新开一个任务,但并非一定如此。 如果一个已有 task 的 affinity 值与新 activity 的相同,则 activity 会放入该 task。 如果没有,则会新建一个新 task。

如果本标志使得 activity 启动了一个新的 task,用户按下 Home 键离开时,必须采取一些措施让用户能回到此 task。 某些应用(比如通知管理器)总是让 activity 放入其它 task 中启动,而不是放入自己的 task 中。 因此,它们总是把 FLAG_ACTIVITY_NEW_TASK 标志置入传给 startActivity() 的 intent 中。如果你的 activity 可以被外部应用带此标志来启动,请注意用户会用其它方式返回启动 task,比如通过应用图标(task 的根 activity 带有一个 CATEGORY_LAUNCHER intent 过滤器;参阅下节#启动task)。

这种情况下,当某个 task 进入前台时,activity 的 affinity 值又与其相同,则它可以从启动时的 task 移入这个 task 中。

比如,假定某旅游应用中有一个 activity 根据所选的城市来报告天气情况。 它的 affinity 与同一应用中的其它 activity 一样(整个应用默认的 affinity),且它允许重新指定此属性的归属。 当你的另一 activity 启动此天气报告 activity 时,它会在同一个 task 中启动。 然而,当旅游应用的 task 进入前台时,则天气报告 activity 将会重新放入其 task 中并显示出来。

提示: 如果一个 .apk 文件中包含了多个“application”,你可能需要用 taskAffinity 属性来指定每个“application”中 activity 的 affinity 值。

清理back stack

如果用户长时间离开某个 task,系统将会仅保留一个根 activity,而把其它 activity 都清除掉。 当用户返回 task 时,只有根 activity 会被恢复。 系统之所以这么处理,是因为经过了很长时间后,用户是要放弃之前进行的工作,返回 task 是为了开始新的工作。

你可以利用 activity 的某些属性来改变这种方式:

alwaysRetainTaskState
如果 task 中根 activity 的此属性设为 "true" ,则默认的清理方式不会进行。即使过了很长时间,task 中所有的 activity 也都会保留在栈中。
clearTaskOnLaunch
如果 task 中根 activity 的此属性设为 "true",则只要用户离开并再次返回该 task,栈就会被清理至根 activity。也就是说,正好与alwaysRetainTaskState相反。用户每次返回 task 时看到的都是初始状态,即使只是离开一会儿。
finishOnTaskLaunch
此属性类似于clearTaskOnLaunch,只是它只对一个 activity 有效,不是整个 task。这能让任何一个 activity 消失,包括 根 activity。如果 activity 的此属性设为 "true",则只会保留 task 中当前 session 所涉及的内容。如果用户离开后再返回 task,它就不存在了。

启动task

你可以把某个 activity 设为 task 的入口,通过发送一个 action 为 "android.intent.action.MAIN"、category 为 "android.intent.category.LAUNCHER" 的 intent即可。 比如:

<activity ... >
   
<intent-filter ... >
       
<action android:name="android.intent.action.MAIN" />
       
<category android:name="android.intent.category.LAUNCHER" />
   
</intent-filter>
    ...
</activity>

这种 intent 过滤器将会让此 activity 的 icon 和 label 作为应用启动图标来显示,用户可以启动此 activity,并且在之后任何时候返回其启动时的 task。

后一个能力非常重要:用户必须能离开一个 task ,之后能再回来使用这个启动 task 的 activity。 因此,标明 activity 每次都会启动 task 的这两种 启动模式 "singleTask" 和 ""singleInstance" 应该仅对带有ACTION_MAINCATEGORY_LAUNCHER 过滤器的 activity 才能使用。 想象一下,如果未给出过滤器时会发生什么:某个 intent 启动了一个 "singleTask" activity, 并新建了一个 task,用户在此 task 中工作了一段时间。然后他按了 Home 键。 tassk 就转入后台,变为不可见状态。这时用户就无法再回到 task 了,因为它在 application launcher 中没有相应的条目了。

对于那些不想让用户返回的 activity,把 <activity> 元素的 finishOnTaskLaunch 设为 "true" 即可(参阅 #清理back stack