Understand Tasks and Back Stack 文档翻译

原文地址: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"。

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



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容