任务是用户在执行特定作业时与之交互的活动的集合。
活动按堆栈排列 - (返回栈) - 按每个活动打开的顺序排列。 例如,电子邮件应用可能有一个活动来显示新消息列表。 当用户选择消息时,将打开一个新活动以查看该消息。 此新活动将添加到后台堆栈。 如果用户按下“返回”按钮,则表示新活动已完成并从堆栈中弹出。 以下视频概述了后端堆栈的工作原理。
当应用程序在多窗口环境中同时运行时,在Android 7.0(API级别24)及更高版本中受支持,系统会为每个窗口单独管理任务; 每个窗口可能有多个任务。 对于在Chromebook上运行的Android应用程序也是如此:系统基于每个窗口管理任务或任务组。
设备主屏幕是大多数任务的起始位置。 当用户触摸应用程序启动器中的图标(或主屏幕上的快捷方式)时,该应用程序的任务将进入前台。 如果应用程序不存在任务(最近未使用该应用程序),则会创建一个新任务,该应用程序的“主”活动将作为堆栈中的根活动打开。
当前活动从另一个活动开始时,新活动将被推到堆栈顶部并获得焦点。 之前的活动仍在堆栈中,但已停止。 当活动停止时,系统将保留其用户界面的当前状态。 当用户按下“返回”按钮时,当前活动将从堆栈顶部弹出(活动被销毁),之前的活动将恢复(其UI的先前状态将恢复)。 堆栈中的活动永远不会重新排列,只能在当前活动启动时从堆栈中推送并弹出到堆栈中,并在用户使用“返回”按钮离开时弹出。 因此,后堆栈作为“后进先出”对象结构操作。 图1显示了这种行为,时间轴显示了活动之间的进度以及每个时间点的当前后栈。
图1.表示任务中的每个新活动如何将项添加到后台堆栈。 当用户按下“返回”按钮时,将破坏当前活动并恢复先前的活动。
如果用户继续按Back,则弹出堆栈中的每个活动以显示前一个活动,直到用户返回主屏幕(或任务开始时运行的任何活动)。 从堆栈中删除所有活动后,该任务不再存在。
任务是一个内聚单元,当用户开始新任务或通过主页按钮进入主屏幕时,可以移动到“后台”。在后台,任务中的所有活动都会停止,但任务的后台堆栈保持不变 - 任务在发生另一项任务时完全失去焦点,如图2所示。然后任务可以返回到“前台“所以用户可以从中断的地方继续前进。例如,假设当前任务(任务A)在其堆栈中有三个活动 - 在当前活动下有两个活动。用户按下主页按钮,然后从应用启动器启动新应用。出现主屏幕时,任务A进入后台。当新应用程序启动时,系统会使用自己的一系列活动为该应用程序(任务B)启动任务。在与该应用程序交互之后,用户再次返回Home并选择最初启动任务A的应用程序。现在,任务A进入前台 - 其堆栈中的所有三个活动都完好无损,并且堆栈顶部的活动将恢复。此时,用户还可以通过返回主页并选择启动该任务的应用程序图标(或从“最近”屏幕中选择应用程序的任务)切换回任务B.这是Android上的多任务处理的一个示例。
图2.两个任务:任务B在前台接收用户交互,而任务A在后台,等待恢复。
图3.单个活动多次实例化。
注意:可以在后台同时保存多个任务。 但是,如果用户同时运行许多后台任务,系统可能会开始销毁后台活动以恢复内存,从而导致活动状态丢失。
由于后备堆栈中的活动永远不会重新排列,如果您的应用程序允许用户从多个活动启动特定活动,则会创建该活动的新实例并将其推送到堆栈(而不是带来任何先前的活动实例) 到顶部)。 因此,您的应用中的一个活动可能会被多次实例化(甚至来自不同的任务),如图3所示。因此,如果用户使用“后退”按钮向后导航,则活动的每个实例都按顺序显示 被打开(每个都有自己的UI状态)。 但是,如果您不希望多次实例化活动,则可以修改此行为。 有关如何执行此操作将在后面的“管理任务”一节中讨论。
总结活动和任务的默认行为:
- 当活动A启动活动B时,活动A停止,但系统保留其状态(例如滚动位置和输入到表单中的文本)。 如果用户在活动B中按下“返回”按钮,则活动A将恢复其状态。
- 当用户通过按Home键离开任务时,当前活动将停止,其任务将进入后台。 系统保留任务中每个活动的状态。 如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将到达前台并恢复堆栈顶部的活动。
- 如果用户按下“返回”按钮,则会从堆栈中弹出当前活动并将其销毁。 堆栈中先前的活动已恢复。 当活动被销毁时,系统不会保留活动的状态。
- 活动可以多次实例化,甚至可以从其他任务实例化。
导航设计
有关Android导航如何在Android上运行的更多信息,请阅读Android Design的导航指南。
一、管理任务
Android管理任务和后台堆栈的方式,如上所述 - 通过将所有活动连续启动到同一任务和“后进先出”堆栈 - 对于大多数应用程序而言非常有用,您不必担心 关于您的活动如何与任务相关联或它们如何存在于后台堆栈中。 但是,您可能决定要中断正常行为。 也许您希望应用程序中的活动在启动时开始新任务(而不是放在当前任务中); 或者,当你开始一个活动时,你想要提出它的现有实例(而不是在后面的堆栈顶部创建一个新的实例); 或者,您希望在用户离开任务时清除除了根活动之外的所有活动的后台堆栈。
您可以使用<activity>清单元素中的属性以及传递给startActivity()的intent中的标记来执行这些操作。
在这方面,您可以使用的主要<activity>属性是:
- taskAffinity
- launchMode
- allowTaskReparenting
- clearTaskOnLaunch
- alwaysRetainTaskState
- finishOnTaskLaunch
您可以使用的主要意图标志是:
- FLAG_ACTIVITY_NEW_TASK
- FLAG_ACTIVITY_CLEAR_TOP
- FLAG_ACTIVITY_SINGLE_TOP
在以下部分中,您将了解如何使用这些清单属性和意图标志来定义活动与任务的关联方式以及它们在后端堆栈中的行为方式。
另外,单独讨论的是在“最近”屏幕中如何表示和管理任务和活动的注意事项。 有关详细信息,请参阅最近屏幕。 通常,您应该允许系统在“最近”屏幕中定义您的任务和活动的表示方式,而不需要修改此行为。
警告:大多数应用程序不应该中断活动和任务的默认行为。 如果您确定您的活动需要修改默认行为,请谨慎使用,并确保在启动期间以及使用“返回”按钮从其他活动和任务导航回活动时测试活动的可用性。 请务必测试可能与用户预期行为冲突的导航行为。
1、定义启动模式
启动模式允许您定义活动的新实例与当前任务的关联方式。 您可以通过两种方式定义不同的启动模式:
- Using the manifest file
在清单文件中声明活动时,可以指定活动在启动时应如何与任务关联。 - Using Intent flags
当您调用startActivity()时,您可以在Intent中包含一个标志,该标志声明新活动应如何(或是否)与当前任务关联。
因此,如果活动A启动活动B,活动B可以在其清单中定义它应该如何与当前任务相关联(如果有的话),活动A也可以请求活动B应该如何与当前任务相关联。 如果两个活动都定义了活动B应该如何与任务相关联,则活动A的请求(如意图中所定义)将遵循活动B的请求(如其清单中所定义)。
注意:清单文件可用的某些启动模式不可用作intent的标志,同样,某些启用模式可用作intent的标志,无法在清单中定义。
a、使用清单文件
在清单文件中声明活动时,可以使用<activity>元素的launchMode属性指定活动应如何与任务关联。
launchMode属性指定有关如何将活动启动到任务的说明。 您可以为launchMode属性分配四种不同的启动模式:
"standard" (the default mode)
默认。 系统在启动它的任务中创建活动的新实例,并将意图路由到该实例。 活动可以多次实例化,每个实例可以属于不同的任务,一个任务可以有多个实例。
"singleTop"
果活动的实例已存在于当前任务的顶部,则系统通过调用其onNewIntent()方法将意图路由到该实例,而不是创建活动的新实例。活动可以多次实例化,每个实例可以属于不同的任务,一个任务可以有多个实例(但只有当后端堆栈顶部的活动不是活动的现有实例时)。
例如,假设任务的后向堆栈由根活动A组成,活动B,C和D位于顶部(堆栈为A-B-C-D; D位于顶部)。意图到达类型D的活动。如果D具有默认的“标准”启动模式,则启动该类的新实例并且堆栈变为A-B-C-D-D。但是,如果D的启动模式是“singleTop”,则现有的D实例通过onNewIntent()接收意图,因为它位于堆栈的顶部 - 堆栈仍然是A-B-C-D。但是,如果意图到达类型B的活动,则即使其启动模式为“singleTop”,也会将新的B实例添加到堆栈中。
注意:创建活动的新实例时,用户可以按“返回”按钮返回上一个活动。 但是,当活动的现有实例处理新意图时,在新意图到达onNewIntent()之前,用户无法按“返回”按钮返回活动状态。
"singleTask"
系统创建新任务并在新任务的根目录下实例化活动。 但是,如果活动的实例已存在于单独的任务中,则系统会通过调用其onNewIntent()方法将意图路由到现有实例,而不是创建新实例。 一次只能存在一个活动实例。
注意:虽然活动在新任务中启动,但“后退”按钮仍会将用户返回到上一个活动。
"singleInstance"
与“singleTask”相同,但系统不会在持有实例的任务中启动任何其他活动。 活动始终是其任务的唯一成员; 任何由此开始的活动都在一个单独的任务中打开。
作为另一个示例,Android浏览器应用程序声明Web浏览器活动应始终在其自己的任务中打开 - 通过在<activity>元素中指定singleTask启动模式。这意味着,如果您的应用发出打开Android浏览器的意图,则其活动不会与您的应用放在同一任务中。相反,要么为浏览器启动新任务,要么如果浏览器已经在后台运行任务,则该任务将被提前处理新意图。
无论活动是在新任务中启动还是在与启动它的活动相同的任务中启动,“返回”按钮始终会将用户带到上一个活动。但是,如果启动指定singleTask启动模式的活动,则如果后台任务中存在该活动的实例,则将整个任务带到前台。此时,后端堆栈现在包括堆栈顶部提出的任务中的所有活动。图4说明了这种情况。
图4.表示如何将具有启动模式“singleTask”的活动添加到后台堆栈。 如果活动已经是具有自己的后台堆栈的后台任务的一部分,那么整个后台堆栈也会在当前任务的基础上出现。
有关在清单文件中使用启动模式的更多信息,请参阅<activity>元素文档,其中详细讨论了launchMode属性和接受的值。
注意:您使用launchMode属性为活动指定的行为可以被启动活动的intent所包含的标记覆盖,如下一节中所述。
b、使用Intent标志
启动活动时,您可以通过在传递给startActivity()的intent中包含标志来修改活动与其任务的默认关联。 您可以用来修改默认行为的标志是:
在新任务中启动活动。 如果任务已在为您正在启动的活动运行,则该任务将返回到前台,并恢复其上一个状态,并且活动将在onNewIntent()中接收新的意图。
这会产生与上一节中讨论的“singleTask”launchMode值相同的行为。
如果正在启动的活动是当前活动(在后台堆栈的顶部),则现有实例将接收对onNewIntent()的调用,而不是创建活动的新实例。
这会产生与上一节中讨论的“singleTop”launchMode值相同的行为。
如果正在启动的活动已在当前任务中运行,则不会启动该活动的新实例,而是销毁其上的所有其他活动,并将此意图传递给活动的恢复实例(现在开启) top),通过onNewIntent())。
生成此行为的launchMode属性没有任何值。
FLAG_ACTIVITY_CLEAR_TOP通常与FLAG_ACTIVITY_NEW_TASK结合使用。 当一起使用时,这些标志是一种在另一个任务中定位现有活动并将其置于可以响应意图的位置的方法。
注意:如果指定活动的启动模式是“标准”,它也会从堆栈中删除,并在其位置启动一个新实例来处理传入的意图。 这是因为当启动模式为“标准”时,总是为新意图创建新实例。
2、处理亲和力
亲和力指示活动喜欢属于哪个任务。 默认情况下,同一应用程序中的所有活动都具有彼此的关联。 因此,默认情况下,同一个应用程序中的所有活动都希望处于同一任务中。 但是,您可以修改活动的默认关联。 在不同应用中定义的活动可以共享亲和力,或者可以为同一应用中定义的活动分配不同的任务亲和力。
您可以使用<activity>元素的taskAffinity属性修改任何给定活动的亲缘关系。
taskAffinity属性采用字符串值,该值必须与<manifest>元素中声明的默认包名称唯一,因为系统使用该名称来标识应用程序的默认任务关联。
亲和力在两种情况下起作用:
- 当启动活动的intent包含FLAG_ACTIVITY_NEW_TASK标志时。
默认情况下,新活动将启动到调用startActivity()的活动的任务中。它被调到与调用者相同的后栈。但是,如果传递给startActivity()的intent包含FLAG_ACTIVITY_NEW_TASK标志,则系统会查找另一个任务以容纳新活动。通常,这是一项新任务。但是,它不一定是。如果已存在与新活动具有相同亲缘关系的现有任务,则会将活动启动到该任务中。如果没有,它开始一项新任务。
如果此标志导致活动开始新任务,并且用户按下主页按钮以离开它,则必须有某种方式让用户导航回任务。某些实体(例如通知管理器)总是在外部任务中启动活动,从不作为自己的一部分,因此它们总是将FLAG_ACTIVITY_NEW_TASK置于它们传递给startActivity()的意图中。如果您有可以由可能使用此标志的外部实体调用的活动,请注意用户有一种独立的方式返回已启动的任务,例如使用启动器图标(任务的根活动)有一个CATEGORY_LAUNCHER意图过滤器;请参阅下面的“启动任务”部分。 - 当一个活动的allowTaskReparenting属性设置为“true”时。
在这种情况下,当该任务到达前台时,活动可以从它开始的任务移动到它具有亲和力的任务。
例如,假设在所选城市中报告天气状况的活动被定义为旅行应用程序的一部分。 它与同一应用程序中的其他活动(默认应用程序关联)具有相同的亲和力,并允许使用此属性重新生成父项。 当您的某个活动启动天气报告者活动时,它最初属于与您的活动相同的任务。 但是,当旅行应用程序的任务到达前台时,天气报告者活动将重新分配给该任务并显示在其中。
提示:如果APK文件从用户的角度包含多个“app”,您可能希望使用taskAffinity属性为与每个“app”关联的活动分配不同的亲和力。
3、清理后栈
如果用户长时间离开任务,系统将清除除根活动之外的所有活动的任务。 当用户再次返回任务时,仅还原根活动。 系统以这种方式运行,因为在很长一段时间之后,用户可能已经放弃了之前正在做的事情并返回任务以开始新的事情。
您可以使用一些活动属性来修改此行为:
alwaysRetainTaskState:
如果在任务的根活动中将此属性设置为“true”,则不会发生刚才描述的默认行为。 即使经过很长一段时间,任务仍会保留堆栈中的所有活动。
clearTaskOnLaunch:
如果在任务的根活动中将此属性设置为“true”,则只要用户离开任务并返回到该任务,就会将堆栈清除为根活动。 换句话说,它与alwaysRetainTaskState相反。 即使在离开任务片刻之后,用户也始终以初始状态返回任务。
finishOnTaskLaunch:
此属性类似于clearTaskOnLaunch,但它在单个活动上运行,而不是整个任务。 它还可以导致任何活动消失,包括根活动。 当它设置为“true”时,活动仍然是当前会话的任务的一部分。 如果用户离开然后返回任务,它将不再存在。
4、开始一项任务
您可以将活动设置为任务的入口点,方法是为其指定一个过滤器,其中“android.intent.action.MAIN”作为指定的操作,“android.intent.category.LAUNCHER”作为指定的类别。 例如:
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
这种意图过滤器会导致活动的图标和标签显示在应用程序启动器中,从而为用户提供启动活动并返回其在启动后随时创建的任务的方法。
第二种能力很重要:用户必须能够离开任务,然后使用此活动启动器返回该任务。因此,仅当活动具有ACTION_MAIN和CATEGORY_LAUNCHER过滤器时,才应使用将活动标记为始终启动任务的两种启动模式“singleTask”和“singleInstance”。例如,想象一下,如果缺少过滤器会发生什么:intent会启动“singleTask”活动,启动新任务,并且用户会花一些时间在该任务中工作。然后用户按下主页按钮。该任务现在发送到后台并且不可见。现在用户无法返回任务,因为它未在应用启动器中显示。
对于您不希望用户能够返回活动的情况,请将<activity>元素的finishOnTaskLaunch设置为“true”(请参阅清除后台堆栈)。
有关如何在“概述”屏幕中表示和管理任务和活动的更多信息,请参见“最近用户”屏幕。