Activity、任务、返回堆栈
任务是一系列 Activity 的集合,这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。
应用启动之后,该应用的任务就会转到前台运行,如果系统中不存在该应用对应的任务,则会创建一个新的任务,并且该应用的“主”Activity 将会作为堆栈的根 Activity 打开。
在当前 Activity 启动另一个 Activity 时,新的 Activity 将被推入到堆栈顶部并获得焦点,上一个 Activity 仍保留在堆栈中,但会停止。当 Activity 停止时,系统会保留其界面的当前状态。当返回时,当前 Activity 会从堆栈顶部退出(该 Activity 销毁),上一个 Activity 会恢复(界面会恢复到上一个状态)。
堆栈中的 Activity 永远不会重新排列,只会被送入和退出,Activity 子启动时被送入堆栈,在离开时从堆栈中退出。返回堆栈按照“后进先出”的对象结构运作,如图所示:
移除堆栈中的所有 Activity 后,该任务将不复存在。
任务是一个整体单元,当开始一个新任务或通过主屏幕按钮进入主屏幕时,当前任务将移至“后台”,在后台时,任务中的所有 Activity 都会停止,但任务的返回堆栈会保持不变。
Android支持多任务,任务处于前台时,获得焦点,任务处于后台时,失去焦点,任务的前后台状态是可以切换的。
注意:虽然任务可以在后台运行,但如果后台任务过多占用了大量内存,系统可能会为了恢复内存而销毁后台 Activity,导致 Activity 状态丢失。
由于返回堆栈中的 Activity 不会被重新排列,所以如果应用允许从多个地方启动特定的 Activity,系统默认会创建该 Activity 的新实例并将其推入到堆栈中(而不是将该 Activity 的某个先前的实例移至堆栈顶部)。这样一来,应用中的一个 Activity 就可能被多次实例化(甚至是在不同任务中进行实例化),如图所示:
Activity 和任务的默认行为总结如下:
- 当 Activity A 启动 Activity B 时,Activity A 会停止,但系统会保留其状态(例如滚动位置和输入到表单中的文本)。如果在 Activity B 中按返回按钮,系统会恢复 Activity A 及其状态。
- 当离开任务时,当前 Activity 会停止,其任务会转到后台。系统会保留任务中每个 Activity 的状态。当恢复任务时,任务会进入前台并恢复堆栈顶部的 Activity。
- 如果用户按返回按钮,当前 Activity 将从堆栈中退出并销毁。堆栈中的上一个 Activity 将恢复。Activity 被销毁后,系统不会保留该 Activity 的状态。
- Activity 可以多次实例化,甚至是从其他任务对其进行实例化。
管理任务
可以借助清单文件 <activity>
节点的属性,或者给 startActivity()
的 intent
添加标记来实现任务管理。
和任务相关的主要的<activity>
属性包括:
- taskAffinity
- launchMode
- allowTaskReparenting
- clearTaskOnLaunch
- alwaysRetainTaskState
- finishOnTaskLaunch
和任务相关的主要的主要 intent
标记包括:
- FLAG_ACTIVITY_NEW_TASK
- FLAG_ACTIVITY_CLEAR_TOP
- FLAG_ACTIVITY_SINGLE_TOP
Activity 启动模式
可以通过启动模式定义 Activity
的新实例如何与当前任务关联。
可以通过两种方式定义不同的启动模式:
- 使用清单文件
在清单文件中声明Activity
时,可以指Activity
在启动时如何与任务关联。 - 使用 Intent 标记
调用 startActivity() 时,可以在 Intent 中添加标记,用于声明新 Activity 如何(或是否)与当前任务相关联。
使用清单文件
在清单文件中声明 Activity
时,可以使用 <activity>
元素的 launchMode
属性指定 Activity
应该如何与任务关联。可以为 launchMode 属性指定 4 种不同的启动模式:
-
standard(默认模式)
默认值。会在启动该Activity
的任务中创建Activity
的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。 -
singleTop
如果当前任务的顶部已存在Activity
的实例,则系统会通过调用其 onNewIntent() 方法来将 intent 转送给该实例,而不是创建 Activity 的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 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" 也是如此。
注意:创建 Activity 的新实例后,用户可以按返回按钮返回到上一个 Activity。但是,当由 Activity 的现有实例处理新 intent 时,用户将无法通过按返回按钮返回到 onNewIntent() 收到新 intent 之前的 Activity 状态。
- singleTask
系统会尝试创建新任务并实例化新任务的根Activity
,但是并不一定会创建新任务。
例如:
如果另外的任务中已存在该Activity
的实例,则系统会通过调用其onNewIntent()
方法将intent
转送到该现有实例,而不是创建新实例。Activity
一次只能有一个实例存在。
如果当前已存在和该Activity
的taskAffinity
相同的任务,则不会创建新的任务,而是将该Activity
直接添加到对应的任务中。
注意:虽然 Activity 在新任务中启动,但用户按返回按钮仍会返回到上一个 Activity。
- singleInstance
与singleTask
相似,但是并不相同。
如果当前已存在包含该Activity
实例的任务,则不会创建新的任务,而是直接把包含该Activity
实例的任务换回前台,并调用该Activity
实例的onNewIntent()
方法将intent
传递给现有实例。
如果当前不存在包含该Activity
实例的任务,则一定会创建新的任务,并将该Activity
实例作为唯一成员添加到任务中,注意,这个任务不管是现在还是在未来,都只会包含这一个Activity
实例,不会再添加其它任何Activity
实例,由该Activity
启动的任何其它Activity
也都会在其他的任务中打开。
使用 Intent 标记
可以在传送给 startActivity()
的 intent
中添加相应的标记来设置 Activity
与其任务的关联关系。可以使用以下标记来修改默认行为:
-
FLAG_ACTIVITY_NEW_TASK
与singleTask
launchMode 值产生的行为相同。 -
FLAG_ACTIVITY_SINGLE_TOP
与singleTop
launchMode 值产生的行为相同。 -
FLAG_ACTIVITY_CLEAR_TOP
如果要启动的Activity
已经在当前任务中运行,则不会启动该Activity
的新实例,而是会销毁位于它之上的所有其它Activity
,并通过onNewIntent()
将intent
传送给Activity
实例(现在位于堆栈顶部)。
launchMode 属性没有可产生此行为的值。
FLAG_ACTIVITY_CLEAR_TOP 最常与 FLAG_ACTIVITY_NEW_TASK 结合使用。将这两个标记结合使用,可以查找其他任务中的现有Activity
,并将其置于能够响应 intent 的位置。
注意:如果指定
Activity
的启动模式为standard
,系统也会将其从堆栈中移除,并在它的位置启动一个新实例来处理传入的intent
。这是因为当启动模式为standard
时,始终会为新intent
创建新的实例。
处理亲和性
“亲和性”表示 Activity
倾向于属于哪个任务。
默认情况下,同一应用中的所有 Activity
彼此具有亲和性。因此,在默认情况下,同一应用中的所有 Activity 都倾向于位于同一任务。
不过,可以修改 Activity
的默认亲和性。在不同应用中定义的 Activity
可以具有相同的亲和性,或者在同一应用中定义的 Activity
也可以被指定不同的任务亲和性。
可以使用 <activity>
元素的 taskAffinity
属性修改 Activity
的亲和性。
taskAffinity
属性采用字符串值,该值必须不同于 <manifest>
元素中声明的默认软件包名称,因为系统使用该名称来标识应用的默认任务亲和性。
亲和性可在两种情况下发挥作用:
-
当启动
Activity
的intent
包含 FLAG_ACTIVITY_NEW_TASK 标记时。
默认情况下,新Activity
会启动到调用startActivity()
的Activity
的任务中。它会被推送到调用方Activity
所在的返回堆栈中。但是,如果传递给startActivity()
的intent
包含 FLAG_ACTIVITY_NEW_TASK 标记,则系统会寻找其他任务来容纳新Activity
。通常会是一个新任务,但也可能不是。如果已存在与新Activity
具有相同亲和性的现有任务,则会将 Activity 启动到该任务中。如果不存在,则会启动一个新任务。
如果此标记导致Activity
启动一个新任务,而用户按下主屏幕按钮离开该任务,则必须为用户提供某种方式来返回到该任务,例如使用启动器图标(任务的根 Activity 具有一个 CATEGORY_LAUNCHER intent 过滤器)。 -
当
Activity
的allowTaskReparenting
属性设为 "true" 时。
在这种情况下,一旦和Activity
有亲和性的任务进入前台运行,Activity
就可从其启动的任务转移到该任务。
例如,假设一款旅行应用中定义了一个报告特定城市天气状况的Activity
,该Activity
与同一应用中的其他Activity
具有相同的亲和性(默认应用亲和性),并通过设置allowTaskReparenting
为 "true" 支持重新归属。当某个Activity
启动该天气预报Activity
时,该天气预报Activity
最初会和启动它的Activity
同属于一个任务,不过,当旅行应用的任务进入前台运行时,该天气预报Activity
就会被重新分配给该任务并显示在其中。
提示:如果一个 APK 文件中包含了就用户角度而言的多个“应用”,可能需要使用
taskAffinity
属性为每个“应用”所关联的Activity
指定不同的亲和性。
清除返回堆栈
如果用户离开任务较长时间,系统会清除任务中除根 Activity
以外的所有 Activity
。当用户再次返回到该任务时,只有根 Activity
会恢复。系统之所以采取这种行为方式是因为经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。
可以使用一些 Activity 属性来修改此行为:
- alwaysRetainTaskState
如果在任务的根Activity
中将该属性设为 "true",则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有Activity
。 - clearTaskOnLaunch
如果在任务的根Activity
中将该属性设为 "true",那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与alwaysRetainTaskState
正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。 - finishOnTaskLaunch
该属性与clearTaskOnLaunch
类似,但它只作用于单个Activity
而非整个任务。它还可导致任何Activity
消失,包括根Activity
。如果将该属性设为 "true",则Activity
仅在当前会话中归属于任务,如果用户离开任务再返回,则该任务将不再存在。