活动 —— 任务与后退堆栈

文章译自:http://developer.android.com/intl/zh-CN/guide/components/tasks-and-back-stack.html



内容内容


  1. 保存活动状态
  2. 管理任务
    1. 定义启动模式
    2. 处理亲缘关系
    3. 清理后退堆栈
    4. 启动一个任务

 



快速浏览


  • 所有活动都属于任务。
  • 任务内包含顺序排列的活动集合,用户以此顺序与它们交互。
  • 任务可以移动到后台并保留每个活动的状态来让用户去执行其他的任务而不丢失他们之前的操作


一个应用通常包括多个活动。应该围绕着一个特定的用户可以执行并开始其他活动的行为类型来设计每个活动。例如,邮件应用可能有一个活动来显示新邮件的列表。当用户选择一个邮件时,一个新的活动被打开以查看该邮件。


一个活动甚至可以启动存在于设备上的其他应用里的活动。例如,如果你的应用想发送一封邮件,你可以定义一个意图来执行“发送”动作,并包含一些数据,譬如邮件地址和消息。接着,一个来自其他应用里宣称可以处理此类意图的活动被打开。在这种情况下,意图是为了发送邮件,所以邮件应用的“编辑”活动开始启动(如果多个活动都支持相同的意图,那么系统会让用户选择一个来使用)。当邮件被发送时,恢复你的活动并且看上去好像邮件活动是你应用的一部分。尽管活动可能来自不同的应用,但Android可以通过把两个活动保持在相同的任务中来维持无缝的用户体验。


任务就是一个当执行某项工作时用户与之交互的活动集合。活动以它们被打开的顺序排列在堆栈中(即“后退栈”)。


设备的主屏幕是绝大多数任务开始的地方。当用户在应用程序启动器中触击一个图标时(或主屏幕上的快捷方式),那个应用的任务变来到前台(即,该任务之前已被创建,并且位于后台)。如果尚未存在该应用的任务(应用最近没有被使用),那么一个新的任务被创建,然后应用的“主”活动作为堆栈内的根活动被打开。


当当前活动开始其他的活动时,新的活动被推至堆栈顶部,并获得焦点。先前的活动仍然在堆栈里,但是被停止了。当一个活动被停止时,系统保留活动用户界面的当前状态。当用户按下Back按钮时,当前的活动从栈顶弹出(该活动被销毁了),接着,先前的活动被恢复(活动UI先前状态被恢复)。堆栈内的活动从未从新排列,只是推向和弹出堆栈—— 当由当前活动开始新活动时,新活动被推到栈顶,当用户使用Back按钮离开它时,它从栈顶弹出。因此,后退堆栈运作为“后进,先出”的对象结构。图1以一条时间线可视化了此行为,该时间线显示了活动和当前堆栈间在每个时间点上的进展。

   

图-1说明了任务中的每个新的活动如何被添加到后退堆栈中。当用按下Back按钮,当前的活动被销毁,然后先前的活动被恢复。


如果用户继续按Back按钮,那么堆栈内的每个活动都弹出以揭露先前的活动,直到返回到主屏幕(或开始任务时任何正在运行的活动)。当所有的活动都被移出堆栈,任务便不再存在了。


任务是具有凝聚性的单元,当用户开始一个新的任务或通过Home按钮转到主屏幕时它可以移向“后台”。在后台时,所有任务内的活动都被停止了,但是任务的后退堆栈依然完整 — 它只是在其他任务发生时简单地失去了焦点,如图-2所示。然后,任务可以回到“前台”,这样用户就可以从他们离开的地方重新开始。例如,假定当前任务(Task A)拥有三个活动在它的堆栈里 — 两个位于当前活动之下。用户按下Home按钮,然后从应用管理中开始了一个新的应用。当主屏幕出现时,Task A进入后台。当新的应用开始时,系统为这个应用开始一个新的任务(Task B),并拥有它自己的活动堆栈。同该应用交互后,用户又返回到主屏幕并选择最初开始Task A的应用。此时,Task A来到前台 — 其堆栈内所有三个活动都是完整的,并且堆栈顶部的活动得以恢复。此刻,用户仍可以通过回到主屏幕并选择开始Task B的应用图标来切回到Task B(或者通过触碰并按住Home按钮来显示最近的任务,然后选择一个)。这是一个Android上多任务的例子。


    

图 - 2. 两个任务: 当Task A 在后台等待被恢复时,Taks B在前台接收用户的交互。


注意: 后台可以同时持有多个任务。然而,如果用户同时在后台运行个任务,系统可能开始销毁一些后台活动以恢复内寸,这会导致丢失这些活动的状态。请参阅接下来有关活动状态的章节



因为活动在后退堆栈里从未重新排列,所以,如果应用允许用户从不只一个活动里开始某个特定活动,则那个活动的新实例被创建并推入堆栈里(而不是把该活动先前的实例提至堆栈顶部),如图 - 3所示。


       


图 - 3. 单个活动被创建多次。


同样,如果用户使用Back按钮向后浏览,活动的每个实例以它们的创建顺序被显示出来(伴随着它们自己的UI状态)。然而,你也可以改变这个行为,如果不想让活动被创建多次的话。在稍后的关于管理任务章节里讨论如何这样做。


总结活动和任务的默认行为:

  • 当活动A开始活动B,活动A被停止,但是系统会保留它的状态(诸如滚动位置和输入窗体的文本)。如果用户在活动B中按下Back按钮,活动A随同它被保存的状态得以恢复。
  • 如果用户通过按Home按钮离开一个任务,则当前的活动被停止,同时它的任务进入后台。系统保留任务里每个活动的状态。如果用户稍后通过选择开始该任务的启动图标来恢复该任务,则该任务重新来到前台并在堆栈顶部恢复活动。
  • 如果用户按下Back按钮,当前活动从堆栈内弹出并销毁。此时,堆栈内上一个活动被恢复。当活动被销毁时,系统不会保留该活动的状态。
  • 活动可以被创建多次,甚至从其他的任务里。


导航设计

为了解更多关于在Android上应用导航是如何工作的,请阅读Android设计之导航指南。


1. 保存活动状态



如上所述,当活动被停止时,系统的默认行为是保存它的状态。这样一来,当用户向后浏览至先前的活动时,该活动的用户界面呈现为他们离开它时的样子。然而,在活动被销毁以及被重建的情况下,你可以 — 及应该 — 通过使用回调方法来主动地保留你的活动状态。


当系统停止其中的一个活动时(譬如当新活动开始或任务移至后台时), 系统可能会立刻销毁那个活动,如果它需要恢复系统内存的话。当这种情况发生时,关于该活动的状态信息就丢失了。如果发生这种情况,系统依然知道那个活动在后退堆栈中的位置,但是当活动被提到堆栈顶部时,系统必须再次创建它(而不是恢复它)。为了避免丢失用户的操作,你应该主动地保存这些操作,通过在你的活动里实现onSaveInstanceState()回调方法。


更多关于如何保存活动状态的信息,请参阅活动文档。




2. 管理任务



如上所述 , 通过把所有已启动的活动连续放置在相同的任务里及"后进,先出”的堆栈中 ,Android这样管理任务和后退堆栈的方式对大多数应用来说都可以很的好运作,并且不必担心活动和任务是如何关联的或它们在堆栈中是如何存在的。


然而,你可能决定打算中断这种常规行为。你可能想让一个自己应用内的活动在其被启动时来开始一个新的任务(而不是被放置在当前任务里);或者,当你开始一个活动时,想要把该活动现有的实例提前(而不是在后退堆栈顶部创造一个新的实例);或者,当用户离开任务时,你要让后退堆栈清除所有除根活动以外的所有活动。


通过清单文件内的<activity>元素以及传递给startActivity()的意图内的标志,你可以实现上述想法,或者更多。


就这一点而言, 可以使用的最主要的<activity>属性有:


以及可供使用的主要意图标志有:


在接下来的章节中,你将看到如何使用这些属性和意图标志来定义活动与任务的关联以及在它们后退堆栈内的行为。


注意: 大多数应用不应该中断活动和任务的这种默认行为。如果你决定对你的活动来说需要改变这种默认行为,请谨慎使用并一定要测试活动在启动期间以及通过Back按钮从其他活动和任务向后导航至它时的可用性。一定要测试可能与用户期望行为相冲突的导航行为。


2.1 定义启动模式


启动模式允许你定义活动实例和当前任务间的关联。可以通过两种方式来定义不同的启动模式:

  • 使用清单文件

    当在清单文件中声明活动时,可以指定活动在其启动时与任务的关联。

  • 使用意图标志

    当调用startActivity()时,可以在意图中包含一个标志,它说明了新活动应该如何(或是否)与当前任务关联。


因此,如果活动A开始了活动B,活动B可以在其清单文件定义它应该如何与当前任务关联(如果有的话),同时活动A也可以要求活动B应该如何与当前任务关联。如果两个活动都定义了活动B与当前任务的关联,那么活动A的请求(依照意图中的定义)将优先于活动B的请求(依照清单文件中的定义)。


注意:  一些对于清单文件可用的启动模式不可以作为意图标志,同样,一些可以作为意图标志的启动模式不可以在清单文件中定义。


2.1.1  使用清单文件


当在清单文件中声明活动时,你可以通过使用<activity>元素的launchMode属性来指定活动应该如何与任务进行关联。

launchMode 属性指定了一条活动应该如何被启动到任务里的指令。有四个不同的启动模式,你可以将它们赋值给launchMode属性:


"standard" (默认模式)
默认情况下,系统从活动开始的任务里创建活动的新实例,并将意图发送给它。该活动可以被多次创建,每个实例可以属于不同任务,同时,一个任务可以拥有该活动的多个实例。

"singleTop"
如果活动的一个实例已经存在于当前任务的顶部,则系统通过调用该活动的onNewIntent()方法把意图发送给那个实例,而不是创建一个活动实例。该活动可以被创建多次,每个实例可以属于不同的任务,并且一个任务可以拥有该活动的多个实例(但是仅当后退堆栈顶部的活动不是该活动的一个现有实例)。

例如,假定一个任务的后退堆栈由根活动A随同活动B,C和顶部的D构成(堆栈为A-B-C-D;D在顶部)。 当被发送的意图是为了启动活动D时,如果D的启动模式为默认的"standard",那么该类一个新实例被创建,同时堆栈的变成A-B-C-D-D。 然后,如果D的启动模式为"singleTop",  则D的现有实例通过onNewIntent()接受该意图,因为它已在堆栈顶部,所以,堆栈依旧是A-B-C-D。然而,如果发送的意图使为了启动活动B,则一个新的活动B的实例被添加至堆栈顶部,即使它的启动模式为"singleTop"


注意:当活动的一个新的实例被创建时,用户可以按Back按钮返回先前的活动。但是,当活动的一个现有实例处理了新的意图,用户就不能通过按Back按钮返回到新意图抵达onNewIntent()方法之前时的该活动的状态。


"singleTask"

系统创建一个新的任务并在新任务的底部初始化活动。然而,如果活动的一个实例已经存在于某个单独的任务里,则系统会通过调用该活动的onNewIntent() 方法把意图分派给其现有的实例,而非创建一个新的实例。同一时刻只能存在一个该活动的实例。


注意: 尽管活动在新的任务中开始,但Back按钮依然可以使用户返回到先前的活动。


"singleInstance".

"singleTask"一样, 除了系统不会向持有实例的任务里启动任何其他活动。该活动通常是它的任务的单个且唯一的成员;任何通过此模式开始的活动都是在一个单独的任务中打开。


再如,Android浏览器应用声明web浏览器活动通常应该在其自己的任务中打开,即在<activity>元素中指定“singTask”启动模式。这意味着,如果你的应用程序发送一个意图以打开Android浏览器,则它的活动不会被放置在与你的应用相同的任务中。相反,要么为浏览器开始一个新的任务, 或者,如果浏览器已经拥有一个任务运行在后台,则该任务被提前以处理新的意图。


无论活动是否在一个新的任务中或者与启动它的活动的任务相同的任务中开始,Back按钮通常可以把用户带至先前的活动。然而,如果你启动的活动指定了singTask启动模式,那么如果该活动已存在于后台任务中,则该任务被提至到前台。此时,后退堆栈在其堆栈顶部包含了所有来自被提前的任务里的活动。图-4说明了此类情况



图-4. 阐述了带有“singTask”启动模式的活动是如何被添加到后退堆栈里的。如果活动已经是一个拥有自己后退堆栈的后台任务的一部分,那么整个该后退堆栈也要提前,并置于当前任务的顶部。


更多关于在清单文件内使用启动模式的内容,请参阅<activity>元素文档,在那里,有launchMode属性和可接受值的更多讨论。


注意: 开始活动的意图内包含的标记可以覆盖掉通过launchMode属性为活动指定的行为,如下一个章节中的讨论。


2.1.2  使用意图标志


当开始一个活动时,你可以通过在递送给startActivity()方法的意图里包含标志来修改活动和它的任务间的关系。可以用来修改默认行为的标志有:

FLAG_ACTIVITY_NEW_TASK
在新的任务里开始活动。如果正在开始的活动的任务已经为其运行,则该任务随其最后被保留的状态提前到前台,然后活动用onNewIntent()方法接收新的意图。

这产生了和“singleTasklaunchMode属性值相同的行为,如上一章节的讨论。


FLAG_ACTIVITY_SINGLE_TOP
如果正被开始的活动就是当前的活动(在堆栈顶部),那么现有的实例收到onNewIntent()的调用,而不是创建一个该活动的新实例。

这产生了和 "singleTop" launchMode属性值一样的行为,如上一章节的讨论。


FLAG_ACTIVITY_CLEAR_TOP
如果正被开始的活动已经运行在当前的任务中,那么所有处在该活动之上的活动都被销毁,并且意图通过onNewIntent()被递送给该活动刚被恢复的实例(现在位于顶部),而不是启动一个此活动的新的实例。
没有launchMode属性值可以产生该行为。

FLAG_ACTIVITY_CLEAR_TOP最常与FLAG_ACTIVITY_NEW_TASK共同使用。当一起使用时,这些标志就是一个在其他任务里定位现有活动并把它放置在一个可以  响应意图的位置的方法。


注意: 如果目标活动的启动模式为"standard",它也要被移出堆栈,同时一个新的实例在其位置上被启动以处理接收的意图。这是因为当活动的启动模式为"standard"时,通常要为新的意图创建该活动的新实例。——没搞懂这句是什么意思!大哭


2.2   处理亲缘关系


亲缘关系说明活动倾向于属于哪个任务。默认情况下,所有来自同一个应用的活动彼此间具有亲缘关系。所以,默认情况下,同一个应用内所有活动更喜欢在同一个任务里。然而,你可以改变这种默认的活动亲缘关系。定义在不同应用内的活动可以共享亲缘关系,或者,定义在同一个应用内的活动可以被指定不同的任务亲缘关系。


可以通过<activity>元素的taskAffinity属性来修改任何一个指定活动的亲缘关系。


taskAffinity属性需要一个字符串值,它必须独一于默认的声明在<manifest>元素内的包名,因为系统使用这个名字(指包名)来识别应用程序的默认任务亲缘关系。


亲缘关系在两种情况下发挥作用:

默认情况下,新活动被载入了调用startActivity()的活动的任务里。它被推入与调用者相同的后退堆栈。然而,如果传递给startActivity()的意图内包含FLAG_ACTIVITY_NEW_TASK标志,系统将寻找一个不同的任务来容纳这个新的活动。通常,它是一个新的任务。然而,这不是必须的。如果已经存有一个与该新活动有相同亲缘关系的任务,那么该活动被载入此任务。如果不存在这样的任务,该活动开始新的活动。

如果该标志导致活动开始新的任务,然后用户按Home按钮离开了它,因此必须有方法让用户导航回该任务。一些实体(譬如通知管理器)通常在外部任务里启动活动,它从未将这些活动作为其自身的一部分,所以,它们常常在传递给startActivity()的意图内放置FLAG_ACTIVITY_NEW_TASK标志。如果你有一个可以由使用了该标志的外部实体启动的活动,注意,用户需要有独立的方法返回由该活动启动的任务,比如通过启动图标(任务的根活动拥有CATEGORY_LAUNCHER意图过滤器;请参与下面的启动一个任务章节)。

这种情况下,活动可以从它开始的任务中移至到与它有亲缘关系的任务,当该任务来到前台时。

比如,假定有一个作为旅行应用程序一部分的活动,它可以报告选定城市的天气情况。它与该应用内其他的活动有相同的亲缘关系(默认的应用亲缘关系),同时它允许通过这个属性来重新指定亲缘关系。当你的一个活动起动了天气报告活动,它最初属于和你的活动相同的任务。然而,当旅行应用的任务来到前台时,天气报告活动被重新指派给该任务,并在其内显示。

提示: 从用户的角度来看,如果一个.apk文件包含了不止一个"应用程序",你很可能想使用taskAffinity属性来为与每个"应用程序"有关联的活动指派不同的亲缘关系。


2.3   清理后退堆栈


如果用户长时间离开任务,系统会清理掉除根活动以外的所有活动的任务。当用户再次回到任务时,只有根活动被恢复。系统的这种行为,是因为经过一段时间以后,用户很可能放弃他们之前所做的事情并且等返回任务时开始做新的东西。


这里有些活动属性,你可以使用它们来修改上述系统行为:

alwaysRetainTaskState
在任务的根活动中,如果这个属性被设置为"true",刚刚描述的默认行为则不会发生。任务保留堆栈内所有的活动,即使长时间离开后。

clearTaskOnLaunch
如果在任务的根活动中,将该属性设置为 "true",堆栈被向下清理至根活动,无论用户何时离开任务以及何时返回到它。换句话说,它与alwaysRetainTaskState属性的作用刚好相反。此时,用户通常返回到任务的初始状态,即便是离开任务一小会。

finishOnTaskLaunch
该属性与clearTaskOnLaunch属性相似,但是它只在单个活动上执行操作,而不是整个任务。它还可以导致任何活动从任务中移除,包括根活动。当该属性被设置为"true"时, 活动依然为仅对当前会话的任务的一部分。如果用户离开,然后又返回到任务,则该活动就不再出现了。



2.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>

此类意图过滤器会导致活动的图标和标签在应用程序启动器中显示,这给用户一种启动活动以及在由它创建的任务被启动后的任何时候返回到该任务的途径。


这种意图过滤的第二个能力是重要的:用户必须能够离开任务并随后使用该活动启动器返回到它。出于这个原因,两个标记了活动通常作为用来初始化任务的启动模式(launchmodes)"singleTask""singleInstance",应该仅在活动具有ACTION_MAINCATEGORY_LAUNCHER过滤器的时候使用。想象一下,例如,如果缺少该过滤器缺时可能会发生什么:意图启动了一个"singleTask"活动,并初始化一个新的任务,接着用户在这个任务中花费大量时间做了些操作 。然后用户按Home按钮离开。现在,该任务进入后台并不再可见。此时,用户没有办法返回到该任务,因为它没有出现在应用程序启动其中。


对于那些不想让用户能够返回到活动的情况,可以设置<activity>元素的finishOnTaskLaunch属性为"true"(参阅清理堆栈章节)



                                                                                                                    

                                                                                                                2012年9月18日,毕




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值