原文地址:https://developer.android.com/guide/components/activities/tasks-and-back-stack#TaskLaunchModes
理解Tasks和Back Stack
一个Task就是一个Activity的集合,这些Activity是用户与之交互并且可以完成特定功能的界面。这些Activity都是按照启动的顺序被放在一个stack(back stack)中的。例如,一个Email应用可能有一个显示新邮件列表的Activity。当用户选择一个邮件,一个新的Activity被打开以显示邮件内容。这个新的Activity被添加到back stack中。如果用户点击了Back按键,这个新的Activity就结束了,并从back stack中弹出。
当APPS同时运行在多窗口环境时,这种特性在Android7.0(API 24)以及更高的版本中支持,系统会分别为每一个窗口单独管理Task,每一个窗口可能都会有很多的Task。
设备的主屏幕是启动绝大多数Task的场所。当用户在launcher上点击了一个APP的icon(或者是主屏幕上的一个shortcut),那个APP的Task会来到前台。如果这个APP的Task不存在(这个APP最近没有使用过),这时会创建一个新的Task,这个APP的“main” Activity会作为root Activity保存在这个stack中。
当current Activity启动了另外一个Activity,这个新的Activity会被压入当前stack的栈顶,然后获得焦点。之前的Activity仍然保留在stack中,但是处于stop状态。当一个Activity处于stop状态时,系统会保存它的用户界面的当前状态。当用户点击了Back按键,current Activity从当前stack的栈顶弹出(这个Activity将被destroy),然后之前的Activity处于resume状态(重新加载它的UI的previous state)。在stack中的Activity不会被重新排序,只能在当前的stack中压入或者弹出——当从current Activity启动一个新的Activity时压入stack,当用户点击了Back按键时弹出stack。因此,back stack执行的是“后进,先出”的原则。图1可视化了这个过程。
如果用户继续点击Back按钮,stack中的每一个Activity会依次弹出,然后显示前一个Activity,直到用户返回Home主界面(或者返回启动这个Task并且还在运行的那个Activity)。当所有的Activity都从这个stack弹出之后,这个stack也就不再存在了。
一个Task是有用户粘性的,直到用户启动了一个新的Task或者通过点击Home按键回到了Home主屏幕,让之前的Task回到后台。在后台时,这个Task中的所有Activity都是stop状态,但是这个Task的back stack的所有状态都是完整的——这个Task只是因为当另一个Task占据了他的前台位置时简单的失去了焦点而已,就像图2所显示的那样。
当一个Task回到前台时,用户可以回到他离开这个Task时的状态。比如,当前的Task(Task A)在他的stack中有3个Activity——两个后台Activity,一个前台Activity。然后,用户点击了Home按键,从APP launcher启动了一个新的APP。当回到Home主屏幕后,Task A就处于后台。当启动一个新的APP后,系统为这个新APP启动了一个Task(Task B),并且具有他自己的Activity的stack。当用户操作完这个APP,再一次点击Home按键回到Home主屏幕,并且启动了之前那个APP,Task A。现在,Task A回到了前台——他的stack中的三个Activity还是完好无损的,stack中最上面的那个Activity处于resume状态。此时,用户还可以通过点击Home按键,然后通过点击Task B的APP的icon切换回那个Task(也可以通过Recents screen选择这个Task)。这就是Android上多任务的一个例子。
因为在back stack中Activity不会被重新排序,如果你的APP允许用户启动一个特殊的Activity不止一次的话,那么每次都会创建这个Activity的一个新的实例然后压入栈中(而不是把这个Activity的任何已有的实例放到前台)。因此,在你的APP中一个Activity可能被实例化多次(甚至在不同的Task中),图3展示了这种情况。因此,如果用户通过点击Back按键回退,这个Activity的每一个实例会按照他们被创建的顺序依次展示(每一个都展现他们自己的状态)。但是,如果你不希望一个Activity被实例化多次,你可以改变这种行为。至于如何实现,我们将在有关Managing Tasks的部分进行讨论。
总结一下Activity和Task的默认行为:
1、当Activity A启动Activity B,Activity A处于stop状态,但是系统保存了他的状态(例如滚动的位置等等)。如果用户在Activity B中点击了Back按 键,Activity A就会处于resume状态,并且restore他之前的状态。
2、当用户通过点击Home按键离开一个Task时,current Activity将处于stop状态,并且他的Task将进入后台。系统会保存这个Task中每一个 Activity的状态。如果稍后用户通过在launcher中点击这个APP的icon来恢复这个Task,这个Task会回到前台,然后恢复最上层的那个Activity。
3、如果用户点击了Back按键,current Activity从stack中弹出,并且会destroy。这个stack中的前一个Activity会处于resume状态。当一个Activity 处于destroy状态时,系统不会保存这个Activity的状态。
4、Activity可以被多次实例化,甚至在不同的Task中。
Navigation Design
For more about how app navigation works on Android, read Android Design's Navigation guide.
Managing Tasks
Android管理Task和back stack的方式——把所有启动成功的Activity都放到这个Task中的一个遵循“后进,先出”原则的stack中——这种方式对于绝大多数APP来讲都可以工作的很好,你不需要考虑你的Activity和Task之间是怎么联系的,或者他们在back stack中是怎么存在的。可是,你可能想要打破这种常规的行为。你可能想要你APP中的Activity启动时创建一个新的Task(而不是被放入当前的Task中);或者,当你实例化一个Activity时,你想要把一个已经存在这种类型的Activity的实例放到前台(而不是在back stack中创建该Activity的一个新的实例);或者,当用户离开这个Task的时候你想要你的back stack清空所有的Activity,除了你的root Activity。
你可以通过在manifest文件中设置<activity>元素的属性,或者通过你传递给startActivity()方法的intent携带flag参数来实现这些,甚至更多。
对于这一点,你可以使用<activity>元素的以下几个重要属性:
你可以使用intent的几个重要flag:
接下来的部分,你将会看到如何使用这些manifest的属性和intent的flag去定义Activity如何与Task相关联以及他们在back stack中有怎样的行为。
同时,我们也应该分开讨论并且考虑周全在Recent屏幕中如何展示和管理我们的Task和Activity。可以查看 Recents Screen获取更多信息。通常情况下你应该让系统定义你的Task和Activity在Recent屏幕中的具体表现,你不需要修改默认的行为。
定义启动模式
启动模式允许你定义一个新的Activity实例如何与current Task相关联。你可以通过两种方式定义不同的launch mode:
1、在manifest文件中:
当在你的manifest文件中声明一个Activity时,你可以指定当这个Activity启动时如何与Task相关联。
2、使用Intent的flags:
当你通过调用startActivity()方法时,你可以在Intent中包含一个flag参数来声明这个Activity的新实例如何(或者是否)与current Task相关联。
比如Activity A启动Activity B,Activity B可以在manifest中定义如何与current Task相关联,Activity A也可以请求Activity B如何与current Task相关联。如果两个Activity都定义了Activity B如何与一个Task相关联,这时Activity A的请求(定义在Intent中的)将会覆盖Activity B的请求(定义在他的manifest文件中的)。
在manifest文件中定义
当在manifest中声明一个Activity时,你可以使用<activity> 元素的launchMode 属性来指定这个Activity如何与一个Task相关联。
launchMode属性指明了一个Activity如何被一个Task启动的指令。launchMode 属性有四种不同的数值供你使用:
"standard" (默认的模式)
默认的模式。系统在启动这个Activity的那个Task中创建一个该Activity的新的实例,然后把Intent发送给他。这个Activity可以被实例化多次, 每一个实例可以属于不同的Task,一个Task也可以有多个该Activity的实例。
"singleTop"
如果在current Task的最上面已经存在了该Activity的一个实例,系统就会通过调用这个实例的onNewIntent()方法把intent传递给它,而不是再 创建一个该Activity的新的实例。这个Activity可以被实例化多次,每个实例可以属于不同的Task,一个Task也可以有该Activity的多个实例(当且 仅当这个back stack的最上面的Activity不是这个Activity的实例)。
例如,假设一个Task的back stack包含一个root Activity A,还有B,C和D,D是最上面的Activity(目前的stack是A->B->C->D,D在最上面)。 现在有一个启动类型Activity D的Intent。如果D的launch mode为默认的"standard",就会实例化一个D的新的实例,stack现在就变成了A->B- >C->D->D。可是,如果D的launch mode为"singleTop",这个已存在的D的实例就会通过onNewIntent()方法接收到这个Intent,因为D在stack 的最上面——stack现在还是A->B->C->D。但是,如果这个Intent是来启动类型Activity B的,就会有一个B的新的实例被创建,然后加入到 stack中,就算Activity B的launch mode是"singleTop"。
"singleTask"
系统创建一个新的Task,然后实例化这个Activity作为这个Task的root Activity。但是,如果该Activity的一个实例已经在另外的Task中存在了, 系统会通过调用这个已存在实例的onNewIntent()方法,然后把Intent传给他,而不会再创建一个新的实例。同一时间只会存在该Activity的一个 实例。
"singleInstance"
和"singleTask"一样,除了系统不会启动任何一个别的类型Activity到这个Task中。这个Activity永远都是这个Task中唯一的一个实例;他启动的 任何Activity都会在另外单独的一个Task中。
再举一个例子,Android的Browser APP通过把<activity>元素的launch mode属性设置为"singleTask"来让他的网页浏览Activity永远在自己的Task中。这意味着,如果你的APP发出一个打开Android Browser的Intent,他的Activity不会与你的APP在同一个Task中。而是,或者为Browser启动一个新的Task,或者如果在后台已经有了一个运行的Browser的Task,会把他拉到前台来处理这个Intent。
不论一个Activity是在一个新的Task中还是在与启动他的那个Activity在同一个Task中启动,Back按键总是会把用户带回前一个Activity。但是,如果你启动的Activity带有"singleTask"的launch mode,此时如果在后台已经有了这个Activity的一个实例存在,那么,他的Task会被调回前台。此时,现在的back stack会带有这个Task里面所有的Activity,并且他们在Task的最上面。图4说明了可能发生的这种情况。
有关在manifest文件中使用launch mode的更多信息,可以查看<activity>元素文档,那里有关于launchMode属性以及他可以接受的参数的更多讨论。
使用Intent的flags
当启动一个Activity时,你可以在调用startActivity()方法的Intent参数中携带flags来修改这个Activity如何与Task相关联的默认行为。你可以用来修改默认行为的flags如下:
在新的Task中启动这个Activity。如果你启动的这个Activity已经有一个在运行的Task了,这个Task会带着他最后保存的状态回到前台,这个 Activity通过onNewIntent()方法接收这个新的Intent。
他产生的效果与之前讨论的使用"singleTask" launchMode是一样的。
如果启动的Activity就是当前的Activity(Task中最上面的Activity),此时,这个已存在的实例会通过onNewIntent()方法响应这次调用,而不是再 创建一个这个Activity的实例。
他产生的效果与之前讨论的使用"singleTop" launchMode是一样的。
如果想要启动的这个Activity在current Task中已经存在一个实例了,那么就不会再创建一个此Activity的实例,而是把这个Activity当前实例最上 面的Activity全部destroy,然后通过onNewIntent()方法把Intent发送给这个处于resume状态的实例(现在是在当前Task的最上面了)。
没有launchMode属性可以产生这样的行为。
FLAG_ACTIVITY_CLEAR_TOP经常会与FLAG_ACTIVITY_NEW_Task一起使用。当一起使用时,这些flags是在另外的Task中定位一个已存 在的Activity的实例并且把它放到可以响应这个Intent的位置的方式。
Handling affinities
Affinity指明了一个Activity更倾向于属于哪个Task。默认情况下,同一个APP的所有Activity彼此之间有一个共同的affinity。所以,默认情况下,同一个APP中的所有Activity都倾向于属于同一个Task。不过你可以修改一个Activity的默认affinity。定义在不同APP中的Activity可以共享一个affinity,或者在同一个APP中的Activity可以被指定为不同的Task affinity。
你可以在<activity>元素中通过 TaskAffinity属性修改任何指定Activity的affinity。
TaskAffinity属性是一个String值,他必须与定义在<manifest> 元素中的默认包名不同,因为系统会使用这个默认的包名作为Task affinity的默认值。
Affinity主要在两种情况下使用:
1、当启动一个Activity的Intent中包含FLAG_ACTIVITY_NEW_Task。
默认情况下一个新的Activity的实例会加入到调用startActivity()方法的那个Activity的Task中。他会被放在与调用者相同的back stack中。但是, 如果传递给startActivity()方法的Intent中包含FLAG_ACTIVITY_NEW_Task,系统会寻找一个不同的Task来安置这个Activity。通常情况会是一 个新的Task。但是,并不一定会是这样。如果当前已经存在了一个与这个Activity相同affinity的Task,这个Activity会在这个Task中启动。如果 没有,就启动一个新的Task。
如果这个flag导致在新的Task中启动这个Activity,当用户通过点击Home按键离开他后,必须有方式让用户回到这个Task。有一些应用的 Activity(比如notification manager)总是在别人的Task中启动,而不是他们自己的Task中,所以他们总是在调用startActivity()方法的Intent中带 有FLAG_ACTIVITY_NEW_Task。如果你的应用中有Activity能够被外部调用并且可能会带有这个flag,你要小心了,用户可能会用一种完全不 同的方式回到他们启动的这个Task,比如在launcher中点击icon(一个Task中的root Activity带会有CATEGORY_LAUNCHER的Intent过滤,可 以查看下面关于 Starting a Task的部分)。
2、当一个Activity的allowTaskReparenting属性被设置为"true"。
这种情况下,当跟他有相同的affinity的Task回到前台时,这个Activity可以从启动他的那个Task转移到跟他有相同affinity的这个Task。
举例说明一下,假如,在一个旅行APP中有一个报道选中城市天气情况的Activity。他和这个APP中的其他Activity有相同的affinity(APP默认的 affinity),并且他的allowTaskReparenting属性被设置为"true"。当你的APP中某个Activity启动了这个天气预报Activity,他最初与你的Activity 在同一个Task中。但是,当这个旅行APP的Task回到前台的时候,这个天气预报Activity被重新分配到那个Task中,并在其中显示。
清空back stack
如果用户长时间离开一个Task,系统会清除这个Task中除了root Activity之外的所有Activity。当用户再次回到这个Task,只有root Activity会被恢复。系统会遵循这种方式,因为,如果长时间没有回来,用户很可能已经不在乎他们之前做过什么了,当回到这个Task后用户会重新再做一些事。
下面有一些<activity>的属性可以修改这种行为:
如果一个Task的root Activity的这个属性设置为"true",刚才所讨论的默认的行为就不会发生。就算过了很长时间Task也会在他的stack中保留 所有的Activity。
如果一个Task的root Activity的这个属性设置为"true",当用户离开再回到这个Task时,stack中除了root Activity之外的所有Activity都会被清 空。换句话说,他是alwaysRetainTaskState相反的情况。用户总是会回到这个Task的初始状态,就算只是离开了一小会。
这个属性很像clearTaskOnLaunch,但是,他是针对一个Activity的,而不是整个Task。他也可以用来清除任何的Activity,包括root Activity。 当这个属性被设置为"true"时,Task只会在当前会话中保留该Activity。当用户离开再回到这个Task时,不会再显示这个Activity。
启动一个Task
你可以通过一个带有"android.intent.action.MAIN"的action和"android.intent.category.LAUNCHER"的category的intent filter为一个Task设定入口Activity。例如:
这种类型的intent filter会让这个Activity的icon和label显示在APP launcher中,这给了用户启动这个Activity的一种方式,以及这个Task启动之后可以回到这个Task的方式。
第二点很重要:用户必须能够离开一个Task,然后通过使用launcher回到这个Task。出于这种原因,标记Activity总是启动一个新的Task的"singleTask"和"singleInstance"的这两种launch mode,应该仅用在带有 ACTION_MAIN和 CATEGORY_LAUNCHER的Activity中。你可以想象,如果没有这些属性会发生什么,举个例子:一个Intent启动了一个"singleTask"的Activity,初始化了一个新的Task,当用户在这个Task中做了一些操作。然后用户点击了Home按键。这个Task现在会退回到后台,不可见了。这时,用户没有方法再回到这个Task了,因为在APP launcher中找不到他。
对于那些你不希望用户能够返回一个具体Activity的情况,你可以设置<activity>元素的finishOnTaskLaunch属性为"true"。
喜欢的话,可以打赏哦!!!