Tasks and Back stack 详解

原文地址:http://developer.android.com/guide/components/tasks-and-back-stack.html

一个应用往往包含很多 activities.每个activity都应围绕着用户可执行的特定动作来设计,并且可以启动其它activities.例如,一个email应用可能有一个显示新邮件列表的activity.当用户选择一个邮件,一个新的activity被打开以显示邮件内容.
一个activity也可以打开同一设备上存在于其它应用的activities。例如,如果你的应用想要发送一个邮件,你可以定义一个intent来执行一个"send"动作并包含一些数据,比如一个地址和一条信息.另一个应用中的activity可以自己处理这种intent的然后被打开(如果有多个activitie支持同样的intent,那么系统会让用户选择一个).当email被发送后,你的activity被恢复并且看起来发送邮件的activity好像是你的应用的一部分.即使那些activities可能来自不同的应用,Android也靠着把两个activity保存在同一个任务中来实现这种无缝的用户体验.
一个任务是用户在执行某种工作时所需要的activities的集合.activities们放置在一个栈("back stack")中,按照打开的顺序排列.
设备的Home屏是大多数任务的开始场所.当用户触摸在应用启动台中的图标(或一个home屏上的快捷方式)时,应用的任务就来到了前台.如果没有这个应用已存在的任务(这个应用最近没有被使用),那么一个新的任务被创建并且这个应用的"main"activity被作为栈的根activity打开.
当前的activity启动了另一个activity,新的activity被放置在栈顶并拥有焦点.先前的activity依然保存在栈中,但是停止了.当一个activity停止时,系统保存了它的用户界面的当前状态.当用户点击后退按钮时,当前的activity被从栈顶弹出(activity被销毁了)并且先前的activity被恢复了.栈中的activities永不会被重新排列,只会进行入栈或出栈行为—当被当前activity启动时就入栈,当用户使用后退按钮离开它时就出栈.如此,后退栈也是一个后进先出的栈.

下图按照时间轴展示了activities在stack中的行为变化。

**图1**,展示了一个task中的一个新的activity作为一个item加入到back stack中。当用户按了返回按钮时,当前的activity将被销毁,前一个activity将复活。

如果用户继续后退,那么栈中的每个activity被弹出来然后展示上一个,直到用户退到Home屏(或到达任务开始时运行的那个activity).当所有的activities都从棧种移除,任务就不再存在.
一个任务是一个有聚合力的单元,它可以在用户启动一个新的任务或回到home屏时被整体地移到后台.当位于后台时,任务中的所有的activities都处于停止,但是任务的后退栈却保存完整—当任务被另一个任务取代时,仅仅是失去了焦点.见图2:


**图2.** 两个任务:任务B到了前台,任务A于是被打入后台,伺机恢复.

一个任务可以再回到前台,于是用户可以获得他离开时的模样.举个例子,当前的任务(任务A)有三个activities在其栈中—两个在下面.用户按下Home 按钮,然后又启动一个新的应用.当Home屏出现时,任务A到了后台.当新应用启动时,系统为这个应用开始了一个任务(任务B).当使用完新应用时,用户再次回到了Home屏然后选择了启动任务A的那个应用.现在,任务A来到了前台栈中所有的三个activities都完整保留并且位于顶层的activity被恢复.此时,用户也可以再回到home屏然后选择任务B的应用于是回到任务B(或通过长按Home 按钮以显示最近的任务然后选择它).

_ 注:多个任务可以同时存在于后台.然而,如果用户在同一时刻运行多个后台任务,系统可能会销毁后台activities来釋放内存,从而导致activity状态的丢失._

因为后退栈中的activities从不会被重排,如果你的应用允许用户从不只一个activity启动一个特殊的activity,一个新的activity的实例会被创建并压入栈中(而不是把这个activity的当前实例弄到前台来).所以,你的应用中的一个activity可能被多次实例化(甚至是从不同的任务),如图3所示.同样的,如果用户使用后退按钮向后导航,activity的每个实例都会按照打开的顺序重新显现(每个都保持它们自己的状态).然后,你如果不想某个activity被实例化多次,你可以改变这种行为.后面会讲到如何做.

**图3**.一个**activity被实例化多次.

下面总结一下下activity和任务的默认行为:

  • 当ActivityA启动ActivityB,ActivityA停止,但是系统保存它的状态(比如滚动条的位置和表单中输入的文本).如果用户在Activity B中按下了后退按钮,ActivityA以保存的状态恢复.

  • 当用户按下Home按钮离开了一个任务,当前的activity停止同时它的任务进入后台.系统保持任务中每个activity的状态.如果用户后来运行了这个任务的应用而恢复了这个任务,任务回到前台并使栈顶端的activity恢复.

  • 如果用户按下了后退按钮,当前的activity从栈中弹出并被销毁.前一个activity被恢复.当一个activity被销毁时,系统不再保持activity的状态.

  • Activitie可以被多次实例化,即使是从另外的任务.

保存Activity的状态

如上所述,系统默认下会在activity停止的时候保存其状态.如此一来,当用户导航到前一个activity时,其用户介面显示得跟离开时一样.然后,你应该使用activity的回调方法们保持它的状态,因为activity可能会被销毁然后被重新创建.当系统停止了你的一个activities(比如当新的activity启动或任务被移到后台),系统可能为了释放内存会完全销毁那个activity.当这事发生时,activity的状态丢失.如果真发生了这种现象,系统依然知道那个activity在后退栈中占有一个位置,但是当activity被弄到前台时,系统必须重新创建它(而不是仅仅恢复它).为了避免丢掉用户的工作,你应该通过实现activity的onSaveInstanceState()来提前保存状态.

关于保存activitystate的更多知识,请看 Activities

管理tasks

Android管理任务和后退栈的方式,如前面文章所述—通过把所有接连启动的activity放在同一个任务中并且是同一个后进先出的栈中—在大多数应用中工作得很好并且你无需关心你的activity如何与任务相关连或如何在后退栈中存在.然而,你可能决定要打破这种正常的行为.可能你想在你应用的activity启动时开始一个新的任务(而不是放置到当前栈中);或者,当你启动一个activity,你想把已经运行的它的一个实例提到前台来(而不是创建一个新的实例放在后退栈的顶端);或者,你希望当用户离开任务时,你的后退栈清除了根activity以外的所有activity.

可以做这些事情,甚至更多事情,通过设置manifest中<activity>的属性和传到startActivity()的intent的flag.
在这一点上,你可以设置的最重要的<activity>属性有:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

可以使用的最重要的intent flag:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

注意:大多数应用不应改变activity和任务的默认行为.如果你确定必须要改变默认行为,你必需小心并且保证测试activity在启动时和后退导航到它时的可用性.确保测试了与用户习惯相冲突的导航行为.*

定义启动模式
  启动模式使你可以定义新的activity如何与当前的任务相关联.有两种方法来定义不同的启动模式:

  • 使用manifest文件

当你在你的manifest文件中声明一个activity时,你可以指定activity在启动时如何与任务相关联.

  • 使用Intent的flag

当你调用startActivity()时,你可以在Intent中包含指明新的activity如何(或是否)与当前栈关联的flag.

同样的,如果ActivityA启动ActivityB,ActivityB可以在它的manifest中定义如何与当前的任务关联(如果真的存在)并且ActivityA也可以请求让ActivityB如何与当前的任务关联.如果两个activity都定义了ActivityB如何与任务关联,那么ActivityA的请求(在intent中定义)优先于ActivityB的请求(在它的manifest中定义).

注:一些启动模式可以用在manifest中但不能用在intent的flag上,同样的,一些启动模式可以用在intent的flag上但不能用在manifest中.

使用manifest文件

当在你的manifest文件中声明一个activity时,你可以使用<activity>元素的launchMode属性指定activity如何与一个任务关联.
launchMode属性指明了activity如何启动到一个任务中去.有四种不同的启动模式你可以用于指定给launchMode属性:

  • "standard"(默认模式)

默认.系统创建一个新的activity的实例到启动它的任务中.activity可以被多次实例化,每个实例可以属于不同的任务,也可以属于同一个任务.

  • "singleTop"

如果一个activity的实例已经存在于当前任务的栈的顶端,系统通过调用它的onNewIntent()方法把intent路由到这个实例,而不是创建一个新的实例.activity可以被多次实例化,每个实例可以属于不同的任务,并且一个任务可以具有多个实例(但只是当位于后退栈的顶端的activity不存在时才会出现这种现像).

例如,假设一个任务的后退栈中有根ActivityA和activityB,C,D(A-B-C-D;D位于顶端).一个intent到达了D类型的activity(不是指这里的acitivityD).如果D具有默认的"standard"启动模式,一个新的类的实例被启动并且栈变为A-B-C-D-D.然而,如果D的启动模式是"singleTop",那么这个已存在的ActivityD就通过onNewIntent()接收到intent,因为它在栈的顶端—栈于是依然保持A-B-C-D.又然而,如果一个intent到达了B类型的activity(不是此处的activityB),那么一个新的B实例被添加到栈中,即使它的启动模式是"singleTop".

注:当一个新的activity的实例被创建,用户可以按下后退键回到上一个activity.但当一个已存在的activity实例处理了一个新intent,用户就不能按下后退键回到当前的activity在intent来之前的状态.

  • "singleTask"

系统创建一个新的任务并且实例化activity为新任务的根.然而,如果一个activity的实例已存在于另一个任务,系统就会通过调用这个activity的onNewIntent()把intent路由给它,而不是创建一个新的实例.某个时刻只有一个activity的实例可以存在.

注:尽管activity在一个新任务中启动,后退键依然可以返回到上一个activity.

  • "singleInstance".

跟"singleTask"一样.除了系统不能再启动其它activity到拥有这个activity实例的任务中.activity永远是任务的唯一;任何由这个activity启动的其它activity都在另一个任务中打开.

举一个例子,Android浏览器应用声明网页浏览activity必须在它自己的任务中打开—通过在<activity>元素中指定singleTask启动模式.这表示如果你的应用发出一个intent来打开Android浏览器,它的activity不会放到你的应用所在的任务中.代替的是,可能一个新的任务为浏览器启动,或者,如果浏览器已经运行于后台,它所在的任务就被弄到前台并接受这个intent.

不论一个从一个新任务启动activity还是在一个已存在这种activity的任务中启动,后退键总是能后退到前一个activity.然而,如果你在任务A中启动一个声明为singleTask模式的activity,而这个activity可能在后台已有一个属于一个任务(任务B)的实例.任务B于是被弄到前台并处理这个新的intent.那么此时后退键会先一层层返回任务BActivity,然后再返回到任务A的顶端activity.图4演示了这种情形.

*图**4.**演示一个**"singleTask"**启动模式的**acitvity**如何被添加到一个后退栈中.如果这个**activity**已经是一个后台任务**(**任务**B)**自己的栈的一部分,那么整个后退栈被弄到前台,位于当前任务**(**任务**A)**的上面.*

4.演示一个"singleTask"启动模式的acitvity如何被添加到一个后退栈中.如果这个activity已经是一个后台任务(任务B)自己的栈的一部分,那么整个后退栈被弄到前台,位于当前任务(任务A)的上面.

使用 Intentflags

当启动一个activity时,你可以在给startActivity()的intent中包含flag以改变activity与任务的默认关联方式.你可以用来改变默认行为的flag有:

  • FLAG_ACTIVITY_NEW_TASK
      在新的任务中启动activity-即不在本任务中启动.如果一个包含这个activity的任务已经在运行,那个任务就被弄到前台并恢复其UI状态,然后这个已存在的activity在onNewIntent()中接收新的intent.
      这个标志产生与"singleTask"相同的行为.

  • FLAG_ACTIVITY_SINGLE_TOP
      如果正启动的activity就是当前的activity(位于后退栈的顶端),那么这个已存在的实例就接收到onNewIntent()的调用,而不是创建一个新的实例.
      这产生与"singleTop"模式相同的行为.

  • FLAG_ACTIVITY_CLEAR_TOP
      如果要启动的activity已经在当前任务中运行,那么在它之上的所有其它的activity都被销毁掉,然后这个activity被恢复,而且通过onNewIntent(),initent被发送到这个activity(现在位于顶部了)
      没有launchMode属性值对应这种行为.

  • FLAG_ACTIVITY_CLEAR_TOP
    多数时候与FLAG_ACTIVITY_NEW_TASK联用.当一起使用时,会在其它任务中寻找一个已存在的activity实例并其把它放到一个可以响应intent的位置.

注:如果Activity的启动模式是"standard",FLAG_ACTIVITY_CLEAR_TOP会导致已存在的activity被从栈中移除然后在这个位置创建一个新的实例来处理到来的intent.这是因为"standard"模式会导致总是为新的intent创建新的实例.

处理任务亲和力

亲和力表明了一个activity"心仪"哪个任务.默认下,属于同一个应用的所有activities之间具有相同的任务亲和力.所以,默认下,一个应用的所有activities首选属于同一任务.然而,你可以修改一个activity的默认任务亲和力.定义于不同应用的Activities可以具有相同的任务亲和力,或者同一应用中的activities可以分配不同的任务亲和力.

你可以使用<activity>元素的taskAffinity属性来修改一个activity的任务亲和力.taskAffinity属性使用字符串作为值,这个字符串必须与在<manifest>中声明的默认包名不同,因为系统使用包名来标识默认的任务亲和力.
 亲和力在以下两种情况起作用:

  • 当启动一个activity的intent包含FLAG_ACTIVITY_NEW_TASK标志.

一个新的activity默认是在调用startActivity()的activity所在的任务中安置.然而,如果传给startActivity()的intent包含了FLAG_ACTIVITY_NEW_TASK标志,系统就会查找另一个能安置这个新activity的任务.通常,它会是一个新任务.然而但是,并不是必须这样做.如果有一个已存在的任务具有与新activity相同的亲和力,那么这个activity就被启动并安置到这个已存在的任务中.如果没有这样的任务,才开始一个新的任务.

如果这个标志导致了一个activity在一个新的任务中启动然后用户按下了HOME键离开了这个新任务,那么必须有一些方法使得用户可以重新回到这个任务.一些实体(比如通知管理器)总是在一个另外的任务中启动activity而从不作为自己任务的一部分,于是它总是把FLAG_ACTIVITY_NEW_TASK设置到传给startActivity()的intents中.如果你有一个activity可以被外部实体使用这个标志调用,应小心用户可能用一个独立的方法回到这个启动的任务,比如使用启动图标(任务的根activity有一个CATEGORY_LAUNCHERintent过滤器).-翻译得挺难受,这句话也就是说,只要使用了相同的亲和力,用户就能回到这个已启动的任务中.

  • 当一个activity的allowTaskReparenting属性为"true"时.

在此情况下,activity可以从启动它的任务移动到一个亲和的任务中,当后一个任务来到前台时.

例如,假设一个报告所选城市的天气状况的activity是作为一个旅游应用的一部分.它与同一个应用中的其它activity具有相同的亲和力(默认的application亲合力)并且它被允许重认父母.当你的一个activity启动了这个天气预报activity,它起初是与你的actvity属于同一个任务.然而,当旅游应用的任务进入前台时,天气预报activity就被重新分配到这个任务并在其只显示.

_ 小提示::如果一个.apk文件包含多个从用户角度所认为的"应用",你可能想通过为activity指定属性taskAffinity来使它们连接到不同的"应用"._

清空后退栈

如果用户离开了一个任务很长一段时间,系统会清空任务中除了根activity之外的所有其它activity.当用户重新返回这个任务时,只有根activity被恢复.系统之所以这样做,是因为经过一大段时间之后,用户很可能已抛弃掉他们已经做的并且回到任务开始做一些新的事情.
  有一些activity属性你可以用来改变这种行为:

  • alwaysRetainTaskState
      如果任务的根activity的这个属性被设置为"true",前面所述的默认行为就不会发生.任务保持所有的后退栈中的activity,即使经过很长一段时间.

  • ClearTaskOnLaunch
      如果任务的根activity的这个属性被设置为"true",在用户离开任务再回来时,栈中是清空到只剩下根activity.换句话说,它是与alwaysRetainTaskState反着来的.用户回到任务时永远见到的是初始状态,即使只离开了一小会.

  • finishOnTaskLaunch
      这个属性很像clearTaskOnLaunch,但是它作用于一个单独的activity,而不是整个任务.它也可以导致任何activity死亡,包含根activity.当它被置为"true"时,activity只在当前会话中存活.如果用户离开然后回来,它就已经不在了.

启动一个task

你可以设置一个activity为一个任务的入口,通过给它一个值为"android.intent.action.MAIN"的intent过滤器"和一个值为"android.intent.category.LAUNCHER"的过滤器.例如:
<activity... >
<intent-filter... >
<actionandroid:name="android.intent.action.MAIN" />
<categoryandroid:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>

一个intent这种类型的过滤器导致activity的一个图标和标签被显示于应用启动界面上.使得用户可以启动这个activity并且再次回到这个任务.
  这第二个能力是很重要的:用户必须能离开一个任务并且之后还能通过启动器回来.为此,两种使得activity永远在新任务中启动的启动模式:"singleTask"和"singleInstance",应该只在当activity具有ACTION_MAIN和CATEGORY_LAUNCHER过滤器时使用.想像一下,例如,如果没有这些过滤器将会发生什么:一个intent启动一个"singleTask"activity,在一个新的任务中初始化,并且用户在这个任务中忙乎了一些时间.然后用户按下HOME按钮.任务现在被移到后台并且不可见了.因为这个activity不是应用的启动activity,用户就再也没有办法回到这个任务了.
  但遇到那些你不希望用户能够回到一个activity的情况时怎么办呢?有办法:设置<activity>元素的finishOnTaskLaunch属性为"true"!

转载请说明出处:http://blog.csdn.net/ff20081528/article/details/17219951

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,391评论 25 707
  • 由于航班的延误,我不得不改换了行程,比预计的早一天到了敦煌。其实,并未早一天,因为凌晨一点才到青年旅社。旅社老板...
    蝸小五阅读 323评论 1 2
  • 其实,我打下上面的题目时,我是还不知道要写些什么的。只知道自己要写,还要完全一个打赌,不能输那一块钱。 如果我有八...
    力牧阅读 182评论 1 1
  • 有 天 弱 反 生 下 者 者 道...
    虹元阅读 319评论 0 1