美文网首页
Understand Tasks and Back Stack

Understand Tasks and Back Stack

作者: Lucifer23 | 来源:发表于2019-01-18 10:59 被阅读0次

    原文地址: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>元素的以下几个重要属性:

    1、TaskAffinity

    2、launchMode

    3、allowTaskReparenting

    4、clearTaskOnLaunch

    5、alwaysRetainTaskState

    6、finishOnTaskLaunch

    你可以使用intent的几个重要flag:

    1、FLAG_ACTIVITY_NEW_Task

    2、FLAG_ACTIVITY_CLEAR_TOP

    3、FLAG_ACTIVITY_SINGLE_TOP

    接下来的部分,你将会看到如何使用这些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如下:

    FLAG_ACTIVITY_NEW_Task

          在新的Task中启动这个Activity。如果你启动的这个Activity已经有一个在运行的Task了,这个Task会带着他最后保存的状态回到前台,这个            Activity通过onNewIntent()方法接收这个新的Intent。

          他产生的效果与之前讨论的使用"singleTask"  launchMode是一样的。

    FLAG_ACTIVITY_SINGLE_TOP

          如果启动的Activity就是当前的Activity(Task中最上面的Activity),此时,这个已存在的实例会通过onNewIntent()方法响应这次调用,而不是再        创建一个这个Activity的实例。

          他产生的效果与之前讨论的使用"singleTop"  launchMode是一样的。

    FLAG_ACTIVITY_CLEAR_TOP

          如果想要启动的这个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>的属性可以修改这种行为:

    alwaysRetainTaskState

          如果一个Task的root Activity的这个属性设置为"true",刚才所讨论的默认的行为就不会发生。就算过了很长时间Task也会在他的stack中保留         所有的Activity。

    clearTaskOnLaunch

          如果一个Task的root Activity的这个属性设置为"true",当用户离开再回到这个Task时,stack中除了root Activity之外的所有Activity都会被清            空。换句话说,他是alwaysRetainTaskState相反的情况。用户总是会回到这个Task的初始状态,就算只是离开了一小会。

    finishOnTaskLaunch

          这个属性很像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"。

    喜欢的话,可以打赏哦!!!

    相关文章

      网友评论

          本文标题:Understand Tasks and Back Stack

          本文链接:https://www.haomeiwen.com/subject/krggdqtx.html