转自:http://wiki.eoeandroid.com/Tasks_and_Back_Stack
任务栈和返回堆栈
一个应用程序通常包含多个Activity.每个Activity都必须设计成一种特定的操作, 用户可以通过该操作去实现某项功能,并且操作其他的Activity.例如.一个电子邮件的应用程序可能有一个Activity,用于展现出新的电子邮件列表,当用户选择了一个电子邮件,就打开一个新的Activity以查看该电子邮件.
一个Activity可以启动设备上的另外的一个应用程序.例如,如果您的应用程序想要发送一个电子邮件,您可以定义一个意图来执行一个“发送”行动,其中包括一些数据,如电子邮件地址和消息.一个Activity在另一个应用程序,声明本身来处理这种意图然后打开.在这种情况下.目的是为了发送电子邮件,所以电子邮件应用该创建Activity启动器(如果多个活动支持相同的意图,那么系统让用户选择使用哪一个).当发送邮件时,你恢复了的Activity和电子邮件的Activity好像是您的应用程序的一部分一样.即是这些Activity可能来自不同的应用,android系统也会通过相同的任务栈来保存这种无缝的用户体验.
当用户进行某项操作时,任务栈就收集相互交互的Activity.Activity会被安排在堆栈中(返回堆栈),堆栈中的Activity会按顺序来重新打开.
一个设备的主屏幕是大多数任务栈的起点.当用户触摸图标(或者在屏幕上的快捷方式)时,该应用程序就会到达任务栈的最前面.如果该应用在任务栈中不存在(应用在近段时间内没有使用过)就会在任务栈中创建一个新的任务,并将该应用作为"主"Activity放置在根任务栈中.
当前Activity开始时,新的Activity推入堆栈的顶部和焦点.以前的Activity仍然在堆栈中,但已停止.当Activity停止时,系统会保留其用户界面的当前状态.当用户按下返回按钮时,当前Activity就会从堆栈的顶部(当前的Activity就会被销毁)和之前的一个Activity就会恢复(恢复到之前的UI界面). Activity在栈中的顺序永远不会改变,只会压入和弹出——被当前Activity启动时压入栈顶,用户用返回键离开时弹出. 这样,back stack 以“后进先出”的方式运行。图1 以时间线表的方式展示了多个Activity切换时对应当前时间点的Back Task状态.
图1.图1所表示的Task中的每个新Activity都会相应在Back Task中增加一项。当用户按下返回键时,当前的Activity就会被销毁,而之前一个Activity就会被恢复. 如果用户不停地按下返回键的时候,那么栈中每个Activity都会依次弹出,并且显示前一个Activity,直至用户回到Home屏幕(或者任一启动该Task的Activity,当所有Activity都从栈中弹出后,Task就不再存在.
Task是一个整体的单位, 当用户启动一个新的Task或者通过Home按钮回到Home屏幕的时候,这个TASK就转为“后台”.当Task处于后台时,里面所有的Activity都处于停止状态,但是这个Task的BACK Task仍然完整保留——如图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 的实例.
图2.. 两个Task :Task B 在前台与用户交互,而 Task A 在后台等待唤醒.
图 3. activity A 被实例化多次。
注意:
Android系统可以在后台同时保存多个Task.但是,假如用户同时运行着多个后台Task,系统可能会销毁后台Activity用于释放内存,这样的情况就会导致Activity 状态的丢失.详细情况请参阅#保存Activity状态.
因为Back Stack 中的Activity顺序永远不会改变,如果应用允许某个Activity 可以让用户启动多次,则新的实例会压入栈顶(而不是打开之前位于栈顶的Activity).于是,一个Activity 可能会初始化多次(甚至会位于不同的Task 中),如图3所示.如果用户用返回键键返回时,Activity 的每个实例都会按照原来打开的顺序显示出来(用户界面也都按原来状态显示.当然,如果你不想让Activity 能被多次实例化,你可以改变它.具体方法在后面的章节#管理多个Task中详细说明.
- 总结Activity和Task的默认行为:
- 当 Activity A 启动 Activity B 时,Activity A 被停止,但系统仍会保存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都定义了,则 Activity A 的请求(intent 中定义)会比Activity B 的定义(在 manifest 中)优先.
注意: manifest 文件中的某些启动模式在 intent 标识中并不可用,反之亦然,intent 中的某些模式也无法在 manifest 中定义。
配置 manifest 清单文件
在manifest文件中声明Activity 时,你可以使用 <activity> 元素的 launchMode 属性来指定Activity与Task的关系.
launchMode 属性指明了Activity启动Task的方式。 launchMode 属性可设置四种启动模式:
"standard" (默认模式): "每次访问实例化新的Activity" 默认,系统在启动Activity 的Task 中创建一个新的Activity 实例,并且把 intent 传送路径指向它.该Activity 可以被实例化多次,各个实例可以属于不同的Task,一个Task 中也可以存在多个实例.
"singleTop" "每次访问,看栈顶元素目标对象,是则返回,不再实例化,否则,还是实例化新的Activity." 如果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" "保证activity实例化一次,单任务,由此所开启的活动和本活动位于同一task中" 系统将创建一个新的Task,并把Activity 实例作为根放入其中.但是,如果Activity 已经在其它Task 中存在实例,则系统会通过调用其实例的onNewIntent() 方法把 intent传给已有实例,而不是再创建一个新实例. 此 activity 同一时刻只能存在一个实例.
注意: 虽然Activity启动了一个新的Task,但用户仍然可以用返回键返回前一个activity.
"singleInstance" "保证Activity实例化一次,单实例,由此所开启的Activity在新的task中,和本活动id不一致." 除了系统不会把其它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所描述的就是这一种场景
图 4. 启动模式为“singleTask”的Activity 如何加入Back Stack 的示意.如果Activity 已经是在后台 Task 中并带有自己的Back Stack,则整个后台Back Stack 都会转入前台,并放入当前Task 的栈顶.
在manifest 文件中使用启动模式的详情,请参阅<activity>元素文档,其中详细描述了launchMode属性及其可用值.
注意: 你使用launchMode 属性为Activity设置的模式可以被启动Activity的intent标识所覆盖,这将在下一章节具体说明。
使用 Intent 标识
在启动Activity 时,你可以在传给 startActivity() 的 intent 中包含相应标识,用于修改Activity 与Task 的默认关系。这个标识可以修改的默认模式包括:
- 在新的Task 中启动Activity.如果要启动的Activity 已经运行于某个Task 中,则那个Task 将调入前台中,最后保存的状态也将会恢复,Activity 将在onNewIntent()中接收到这个新 intent.
- 这个模式与前一章节所描述述的"singleTask"launchMode模式相同.
- 如果要启动的Activity 就是当前Activity(位于Back Stack 顶),则已存在的实例将接收到一个onNewIntent()调用,而不是创建一个Activity 的新实例.
- 这个模式与前一章节所述的 "singleTop"launchMode模式相同.
- 如果要启动的Activity 已经在当前Task中运行,则不再启动一个新的实例,且所有在其上面的Activity 将被销毁,然后通过onNewIntent()传入 intent 并恢复Activity(不在栈顶)的运行.
- 此种模式在launchMode中没有对应的属性值.
FLAG_ACTIVITY_CLEAR_TOP 经常与 FLAG_ACTIVITY_NEW_TASK 结合起来一起使用.这些标识定位在其它Task中已存在的Activity,再把它放入可以响应 intent的位置上.
注意:如果Activity 的启动模式配置为"standard",它就会先被移除出栈,再创建一个新的实例来处理这个 intent,因为启动模式为 "standard" 时,总是会创建一个新的实例。
affinities处理
affinity指明Activity对于哪些Task亲和力更高.默认情况下,同一个应用中的所有 activity 都拥有同一个affinity 值. 因此,在同一应用程序中的所有Acttivity都喜欢在相同的Task.不过,你可以修改Activity 默认的 affinity 值.不同应用中的Activity 可以共享同一个affinity 值,同一个应用中的Activity也可以赋予不同的Task affinity值.
你可以使用<activity>元素的taskAffinity 属性修改Activity的affinity
taskAffinity 属性是一个字符串值,必须与<manifest> 元素定义的包名称保证唯一性,因为系统把这个包名称用于标识应用的默认Task affinity值.
下面的两种情况下affinity将会发挥作用:
-
- 当启动Activity的intent包含了FLAG_ACTIVITY_NEW_TASK标志.
- 默认情况下,一个新的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).
-
- 当一个Activity的allowTaskReparenting属性设置为 "true".
- 在这种情况下,当某个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的某些属性来改变这种行为:
- 如果Task 中根Activity 的此属性设为 "true" ,则默认的清理方式不会进行.即使过了很长一段时间,Task 中所有的Activity也还会保留在栈中.
- 如果Task 中根Activity 的此属性设为 "true",则只要用户离开并再次返回该 Task,栈就会被清理至根Activity。也就是说,正好与alwaysRetainTaskState相反.用户每次返回Task时看到的都是初始状态,即使只是离开一会儿.
- 此属性类似于clearTaskOnLaunch,只是它只对一个 Activity有效,不是整个Task.这能让任何一个Activity 消失,包括 根Activity.如果Activity 的此属性设为 "true",则只会保留Task中当前session所涉及的内容.如果用户离开后再返回Task,它就不存在.
启动Task
你可以通过发送一个指定动作和类别为 "android.intent.action.MAIN"、category 为 "android.intent.category.LAUNCHER" 发送 intent来指定某个Activity为Task的入口,例如:
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
这种intent过滤器将会让此Activity 的图标和标签作为应用启动图标来显示,用户可以启动此Activity,并且在之后任何时候返回其启动时的Task.
第二个能力非常重要.用户必须能离开一个Task ,之后能再回来使用这个启动Task 的Activity.由于这个原因,标明Activity每次都会启动Task的这两种启动模式 "singleTask" 和 ""singleInstance"应仅用于ACTION_MAIN 和CATEGORY_LAUNCHER过滤器的Activity才能使用.例如,如果缺少过滤器会发生怎样的问题:某个intent启动了一个 "singleTask" activity,并新建了一个Task,用户在此Task中工作了一段时间.然后他按了Home键,Tassk 就转入后台,变为不可见状态,这时用户就无法再回到Task了,因为它在application launcher中配置对应的属性.
对于那些你不希望用户能够返回的Activity,将<activity>元素的finishOnTaskLaunch 设置为"true" 即可.详细请(参阅 #清除Back Stack)