您的位置:首页 > 其它

Task and Back Stack

2013-06-11 21:50 316 查看
Task and Back Stack
如需转载,请注明转载地址:/article/7784444.html
一个Application通常包含多个acitvities。每一个activity应该被设计为实现用户想做的一些列互相相关的操作,并且可以启动其他的activities。例如,一个email应用可能会有一个显示email列表的Activity,当用户选择了一封email,一个显示email内容的新activity就打开了。

一个Activity甚至可以打开同一部设备中存在于其他Application中的activities。例如,如果你的应用想要发送一封email,你可以定义一个intent来执行"send" action,并添加一些data,比如email address和message,然后调用startActivity()。另一个应用中如果有一个Activity声明了它可以处理这种类型的intent,那么这个activity就会打开。在这种情况下,这个intent想要发送email,于是一个email
application内部的activity启动(如果有多个activity支持这种相同的intent,那么系统会让用户来选择使用哪一个)。当邮件被发送出去了,你的activity继续工作就好像之前那个email activity是你的application的一部分一样,尽管这些activity可能来自于不同的application。Android提供了这种无缝的用户体验,可以让这些activity保持在同一个task中。

一个task是一系列activity的集合,用户可以与这一系列的activity进行交互来完成某一项工作。这些activity排列在一个栈中(the "back stack"),按照它们被打开的顺序进行排列。

设备的Home Screen是大多数task的起点。当用户在launcher中触摸application的图标的时候(或者在Home screen中的快捷方式),那个application的task就会来到foreground,如果目前不存在这个application的task(这个application最近没有被用过),那么一个新的task就会被创建,并且那个application的 "main" activity就会被创建打开作为 stack 中的 root activity。

如果当前的activity启动了另一个activity,新的activity就会被push到stack的顶部并且获得焦点,之前的activity继续保存在stack中,但是处于stopped状态。当一个activity stop的时候,系统会保存其当前的user interface状态,当用户按下Back按钮,当前的Activity会被popped from栈的顶部(这个activity会被destroyed),并且之前的activity会resumes(之前保存的UI状态会被恢复)。stack中的activities永远不会重新排列,只会被从stack中pushed和popped——
当一个activity被从当前的activity启动的时候会被pushed onto the stack,当用户点击Back按键的时候被popped off。back stack是一个类似 "last in,first out"的结构。图片1展示了在当前的back stack中,activities在不同的时间点相互之间的变化情况。



图1.显示了一个新建的activity如何添加到back stack。当用户点击Back按钮,当前的activity被destroyed,前一个activity resumes。

如果用户继续点击Back,那么stack中的每一个activity都被popped off,之前的activity显示,直到用户回到了Home screen(或者启动task的那个activity)。当所有的activities被从stack中移除之后,task就不复存在。

一个task是一个可以移动到background的整体单元,当用户开始一个新的task或者通过Home键前往Home screen的时候,当前的task会被移动到background。当处于background的时候,task中所有的activities都处于stopped状态,但是task的back stack保持完整——在另一个task获得焦点的时候这个task失去焦点,就像图片2中显示的一样。



图2.两个task:Task B位于foreground接受用户的交互,Task A 位于 background,等待重新被resumed。

一个background task可以回到foreground,这样用户就可以从离开时的状态继续进行操作。例如,当前的task(Task A)的stack中有三个activities——当前的activity下面还有两个activities。用户点击Home键,然后从application launcher启动一个新的application。当Home screen出现的时候,Task A进入background。当新的application启动,系统为那个application启动一个新的task(Task
B),这个task有自己的stack of activities。当用户与新开启的application交互完毕之后,回到了Home screen 然后选择最初启动Task A的那个应用。现在Task A来到了foreground——它的stack中的所有三个activities都是完整的并且处于stack顶部的那个activity resumes(进入resumed状态)。这个时候,用户也可以通过回到Home screen并选择启动Task B的那个application来切换回Task B(或者通过长按Home
按钮来查看最近的 tasks 然后选择一个)。这是Android上多任务的一个例子。

注意:多个tasks可以同时被保持在background。但是,如果用户同时运行了很多background tasks,系统可能会在内存不足的时候destroying background activities 来回收内存,这会造成activity states的丢失。具体请阅读下面的章节。

因为back stack中的activities永远都不会重新排列,如果你的应用允许用户从不止一个activity中启动一个特殊的activity,那么一个新的特殊的activity的实例会被创建并被pushed onto the stack(而不是将之前的实例移动到栈顶)。也就是说,你应用中的一个activity可能会被实例化很多次(甚至是从不同的tasks里),如图3中所示。



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

这样一来,当用户通过使用Back button来返回之前的页面的时候,每一个这个特殊activity的实例都会按照他们被打开的顺序重新呈现给用户(每一个都有自己的UI state)。但是,如果你不想要让一个activity实例化多次,你可以更改这种行为。更改的方法在下面的文章中有所讲解。

总结一下activities 和 tasks的默认行为:

当Activity A 启动了 Activity B,Activity A就处于 stopped状态,但是系统会保持它的状态(比如scroll position 和 text entered into forms)。如果用户在Activity B显示的时候点击Back按钮,Activity A会以其之前保持的状态resumes。
如果用户通过点击Home button离开了一个task,当前的activity就会stopped,并且它的task会进入background。系统会保持task中的每一个activity的状态。如果用户通过选择开始task的launcher icon来resumes the task,the task会回到foreground并且resumes the activity at the top of the stack(栈顶的activity显示并回到resumed状态)。
如果用户点击了Back button,当前的activity会popped from the stack and destroyed(弹栈并销毁)。在stack中的之前的activity会回到resumed状态。当一个activity is destroyed,系统就不会保持这个activity的state了。
Activities可以被实例化很多次,及时是从不同的tasks中。

Navigation设计
要学习更多关于Android应用中navigation的工作和设计方式,请阅读Android Design's Navigation guide。

保存Activity状态

正如上面所讨论的,系统默认会在activity stop的时候保存它的状态。这样一来,当用户返回之前的activity的时候,它的用户界面会显示为用户离开它时候的样子。然而,你也可以并且应该主动地通过callback方法来保存activities的状态,以防activity被destroyed而必须recreated。

当系统stops one of your activities(比如当一个新的activity启动或者task移动到background),系统在内存不足的时候可能会destroy这个activity。当这种情况发生时,activity的state信息就丢失了。如果这种情况发生了,系统还是会知道这个被destroy的activity在back stack中有一个位置,但是在activity被带回the top of the stack(栈顶)的时候,系统必须要recreate it(而不是resume
it)。为了避免丢失用户的操作信息,你应该通过实现onSaveInstanceState() callback方法来在你的activity中主动保存状态。

要学习更多关于保存activity state的内容,请阅读Activities文档。

管理Tasks

正如上面所提到的,Android管理tasks和back stack的方式是——按照同一个task中的成功启动的activities的顺序将其填入一个"后进先出"的stack——在大多数的应用中都足以满足需要,你并不需要去关心你的activities与tasks之间的关系或者它们在back stack中的排列方式。然而,有的时候,你可能会想要改变这种默认的规则。你可能会想让你应用中的某一个activity在一个新的task里启动(而不是在当前的task里启动);或者,当你启动一个activity,你想要使用一个已经存在的该activity的实例,并将其提升到栈顶(之前的activities将popped
off),而不是在back stack的顶部创建一个新的activity实例;再或者,你希望在用户离开当前的task的时候,back stack会把除了root activity之外的所有activities都clear。

使用manifest文件中的<activity>节点中的属性以及通过向startActivity()方法的Intent参数设置flags,你可以实现以上的功能,还可以实现更多其他功能。

与管理task相关的主要的<activity>节点属性如下:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

与管理task相关的主要的intent flags如下:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_SINGLE_TOP

在下面的章节中,你将会看到如何使用这些manifest属性以及intent flags来定义activities与tasks之间的关系以及如何定义activity在back stack中的行为。

注意:大多数应用都不应该改变默认的activities和tasks行为。如果你确实认为有必要更改这种默认的行为,一定要确保测试activity在lanuch的时候以及从其他activities和tasks返回的时候可用(有实例)。确保测试那些可能与用户的期望相冲突的activities导航操作。

定义launch modes

launch modes允许你定义一个新建的activity实例与current task的关系。你可以通过两种不同的方式来定义不同的launch modes:

使用manifest文件

当你在你的manifest文件中声明activity的时候,你可以指定当activity启动的时候应该如何与tasks关联。

使用Intent flags

当你调用startActivity()的时候,你可以为Intent对象设置一个flag,这个flag声明了这个新的activity实例应该如何(或者是否)与current stack相关联。

如此一来,如果Activity A启动Activity B,Activity B可以在它的manifest中定义它应该如何与current task(如果会关联的话)相关联,Activity A也可以主动来请求定义Activity B与current task之间的关联方式。如果这两个activities都定义了Activity B与tasks的关联方式,那么Activity A的请求(定义在intent中)会优先于Activity
B的请求(定义在manifest中)。

注意:有一些launch modes在manifest文件中能够定义但是在intent对象的flags中没有对应的定义方式。同样的,有一些launch mode在intent对象的flags中可以定义但是在manifest文件里没有对应的定义方式。

使用manifest文件

当在manifest文件中声明一个activity的时候,你可以通过使用<activity>标签的launchMode属性来设置这个activity应该如何与tasks关联。

launchMode属性指定了activity如何launch into a task(在一个task中启动)。你可以指定四种不同的launch modes 给launchMode属性:

"standard"(默认mode)
默认模式。系统会在启动这个activity的task中创建这个activity的一个实例并将intent传递给该实例。这个activity可以被实例化很多次,每一个实例可以属于不同的tasks,一个task也可以有多个实例。

"singleTop"
如果当前的task已经有了一个activity的实例并且处于当前task顶部,系统会通过调用该实例的onNewIntent()方法来把intent传递给该实例,而不是创建这个activity的一个新的实例。这个activity依然可以被实例化很多次,这些实例可以属于不同的tasks,一个task也可以拥有多个实例(但是只有在back stack顶部的activity并不是要启动的这个activity的实例的情况下)。

例如,假设一个task的back stack是这样排列的:一个root activity A 上面是 activities B,C 顶部是D(这个stack是 A-B-C-D;D位于顶部)。这时收到一个要启动activity D的intent。如果D的launchMode是默认的"standard",那么一个新的activity D的实例会被创建,并被launched,stack变成A-B-C-D-D。然而,如果D的launchMode是"singleTop",那么顶部已存在的D的实例会通过onNewIntent()方法收到intent,stack保持A-B-C-D。但是,如果收到一个启动activity
B的intent,那么一个新的B的实例会被添加到stack中,就算B的launchMode是"singleTop"。

注意:当一个 Activity的新的实例被创建,用户可以通过按Back button来返回到之前的activity。但是当一个已经存在的activity实例处理了a new intent(onNewIntent()),用户不可能通过按Back button来返回到这个activity在onNewIntent()回调处理new intent之前的状态。

"singleTask"
系统会创建一个新的task并实例化activity作为这个新的task的root activity。但是,如果这个activity的一个实例已经在一个task中存在了,那么系统会把intent传递给那个实例并调用其onNewIntent()方法。而不是创建一个新的实例,在同一时间只能有一个该activity的实例存在。

注意:尽管这个activity在一个新的task中启动,Back button还是会把用户带回到之前启动它的activity,而不是Home screen。

”singleInstance“
跟"singleTask"很类似,不同的是,系统不会在保持”singleInstance“类型activity的task中再launch任何的activities。也就是说这个"singleInstance" activity永远是它所在的task内唯一的activity;这个activity启动并打开的所有的activities都会在另一个task中启动。

举个例子,Android Browser application声明了它的web browser activity应该永远在它自己的task中运行——通过在<activity>标签中指定singleTask launch mode来实现。这意味着如果你在自己的application中发布一个打开Android Browser的intent,那么启动的 activity不会进入你自己的application的task中。可能的情况是,要么一个新的task为Browser开启,要么如果Browser已经有一个task在background
running,那个后台的task会被带到前端来处理the new intent(onNewIntent())。

不管一个activity是在一个新的task中启动还是与启动它的activity在同一个task。按Back button总会把用户带回到之前的activity。然而,如果你启动的activity指定了singleTask launch mode,而且在background task中已经存在一个这个activity的实例,那么整个background task都会被带到foreground。这样的话,back stack中将包含所有brought forward的那个task中的activities,位于stack的顶部,图4说明了这种场景。



图4.launch mode定义为"singleTask"的activity是如何被添加进back stack的。如果这个activity已经有一个实例,并且这个实例是一个拥有自己的back stack的background task的一部分,那么整个的back stack都会comes forward,位于current task的顶部。

关于更多的关于manifest文件中的launch mode的内容可以查看<activity>节点的文档,关于launchMode属性和其属性值有更详细的讨论。
注意:通过launchMode属性为activity指定的行为可以被启动该activity的intent中设置的flags所影响,下面将对这种情况进行讨论。

使用Intent flags

当要启动一个activity的时候,你可以通过给传递给startActivity()方法的intent添加flags来修改默认的activity与task之间的关联。可以用来修改默认行为的flags如下:

FLAG_ACTIVITY_NEW_TASK

在新的task里启动the activity。如果已经有一个后台运行的task里面有要启动的activity的实例,那么这个task将来到foreground并恢复之前保存的state,其中的activity将会在onNewIntent()回调中收到intent。

这种设置方式与前面在launchMode中设置"singleTask"将产生相同的效果。

FLAG_ACTIVITY_SINGLE_TOP

如果要启动的activity是current activity(在back stack的顶部),那么顶部的实例将在onNewIntent()中收到intent,而不是创建一个新的activity实例。

这种设置方式与之前在launchMode中设置"singleTop"将产生相同的效果。

FLAG_ACTIVITY_CLEAR_TOP

如果在current task中已经有一个要启动的activity的实例,那么将不会launch一个新的activity的实例,所有在已有的该activity的实例之上的activities都会被destroyed,the activity实例会回到resumed状态并位于back stack顶部,intent会被传递给该activity的实例的onNewIntent()方法。

在launchMode属性中,没有属性值可以定义这种行为。

FLAG_ACTIVITY_CLEAR_TOP经常与FLAG_ACTIVITY_NEW_TASK结合在一起使用。当这二者在一起用的时候,可以在其它的task中找到存在的activity实例并使其位于一个可以响应到intent的位置(弹空其上的activities)。

注意:有一种情况的处理稍有特殊,如果指定的activity的launchMode是"standard",并且在task中已经有一个目标activity的实例,那么这个实例(以及之上的activities)也会被从stack中移除,然后一个新的实例会在原来的实例的位置被launched并处理incoming
intent。这是因为在launchMode是"standard"的时候一定会创建一个新的activity实例来接受传递的intent并处理。(这说明Intent
flags 和 launchMode 之间是结合起来影响task的)

处理affinities

affinities表示一个activity更倾向于从属于哪一个task。默认情况下,相同application内的所有的activities彼此之间情投意合(互相相关)。所以,默认情况下,一个app内的所有的activities都倾向于相同的task。然而,你可以修改activity的 default affinity。不同app里的activities可以共享相同的affinity,或者同一个app内的不同的activities可以被指定为不同的task
affinities。

你可以通过修改<activity>节点的taskAffinity属性来修改指定activity的affinity。

taskAffinity属性设置一个string类型的值(注意这个String的格式是有要求的,必须是x.x,比如a.b,而且每一个"."分割的部分必须用字母开头不能用数字,简单说,就是一个包结构类型的String),这个值必须不同于在<manifest>节点中定义的package name,因为系统默认会使用这个name作为本应用默认的task
affinity。

affinity在两种情况下会起作用:

当launch一个activity实例的intent包含FLAG_ACTIVITY_NEW_TASK flag的时候。默认情况下,一个新的activity实例会被launched into到调用startActivity()的那个activity所在的task。它会被pushed onto调用者所在的back stack。然而,如果传递给startActivity()的intent包含FLAG_ACTIVITY_NEW_TASK flag,系统会寻找一个不同的task来容纳新的activity实例。通常,这是一个new
task。但是,这也不一定。如果已经有了一个task并且这个task的与新的activity实例有相同的affinity,那么这个新的activity实例会被launched into that task。如果不是这种情况,那就启动一个新的task。如果设置了这个flag从而导致一个activity实例在一个新的task里启动然后用户按了Home button离开它返回了主界面,此时应该有一种途径可以让用户返回之前的task。有一些对象(比如notification manager)总是在另外的task中来启动activities,而不是作为定义Intent的activity所在的task的一部分,所以它们在调用startActivity()方法的时候,会给参数intent设置FLAG_ACTIVITY_NEW_TASK。如果你有一个会被一个外部对象(比如上面说的notification
manager)调用的activity,并且intent中会设置FLAG_ACTIVITY_NEW_TASK,应该确保用户有一种途径可以返回activity所在的这个task,比如通过launcher上的一个icon(task的root activity应该有CATEGORY_LAUNCHER intent filter;参见下面的讨论,有详细的解释)。
当一个activity有allowTaskReparenting属性并且设置为"true"的时候。在这种情况下,这个activity可以从启动它的task移动到affinity相同的task(the task it has an affinity for)——当这个有相同affinity的task来到foreground的时候。例如,假设有一个预报选定城市的天气情况的activity,这个activity被定义为一个旅游应用的一部分。它与它所在应用中的其他的activities有相同的affinity(默认的application
affinity)并且它通过设定allowTaskReparenting属性为true允许re-parenting。当你的应用中的activity启动了这个天气预报的activity,它最开始的时候会属于你的应用中启动它的activity所在的task。然而,当旅游应用的task来到foreground,这个天气预报的activity会被重新指定到旅游应用的task(会直接添加到栈顶并显示,但是测试后只有在点击launcher中的图标来使task来到foreground才能得到这种效果,通过Recents来打开最近任务却不行)。

提示:如果想要实现从用户的角度看来一个.apk文件包含了多个"application",可以使用taskAffinity属性来为不同的activities指定不同的attinities,其中affinity相同的activities属于同一"application"。

清空back stack

如果用户离开一个task很长很长时间没有返回(通常30min以上),系统将会清空task中除了root activity之外的所有的activities,当用户返回到task的时候,只有root activity被恢复。系统会这样做是因为,经过一个比较长的时间之后,用户可能已经忘记了或者不再关心,放弃了他们之前做的操作,就算之后再次返回task也是为了做一些重新开始的操作。

如果想要修改这种默认的行为,可以通过下面的<activity>属性:

alwaysRetainTaskState

如果在task的root activity中,这个属性被设置为“true”,那么上面描述的默认行为将不会发生。即使过了很长时间(30min)task还是会在stack中保持所有的activities。

clearTaskOnLaunch

如果在task的root activity中,这个属性被设置为"true",那么无论何时,主要用户离开了当前的task并返回的时候,stack都已经被清空包括root activity。也就是说,这个属性刚好与alwaysRetainTaskState相反。用户在返回task的时候看到的永远都是它的初始状态,即使是用户只离开了一小段时间。

finishOnTaskLaunch

这个属性与clearTaskOnLaunch类似,不同的是它影响的是特定的一个activity,而不像clearTaskOnLaunch影响的的是整个的task。这个属性可以使得任何一个activity消失,包括root activity。当这个属性的值被设置为"true"的时候,只有当activity所在的task依然属于用户的当前会话的时候(current session,简单说就是用户看得见的时候,foreground),这个activity才会被保持。如果用户离开这个task然后返回,这个activity将不再存在,不会出现了。

启动一个task

你可以通过intent filter来设置一个activity作为一个task的入口(entry point)。具体设置办法是:在intent filter中设置"android.intent.action.MAIN"作为指定的action,设置"android.intent.category.LAUNCHER"作为指定的category。如下:
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>

这样设置的intent filter会使得为<activity>设置的icon和label被显示到application launcher(安装即显示),为用户提供了启动该activity以及返回之前创建并启动过的task的途径(这个task的root activity应该是这里的设置了intent filter的activity)。

这样的设置方式还有一个很重要的用途:用户可以通过这个activity在launcher中的图标,在离开相应的task之后通过点击图标返回该task。因此,launchMode中的那两个表示activities启动的时候可能会开始一个新的task的mode值:”singleTask“和"singleInstance",应该只用在当activity有ACTION_MAIN和CATEGORY_LAUNCHER filter设置的情况下。想象一下,如果没有这个filter设置:一个intent启动了一个"singleTask"的activity,开启了一个新的task,并且用户在这个task中做了不少的操作。如果用户按了Home
button。这个task进入了background不可见了。此时用户就没有办法来返回这个task了,因为在application launcher中找不到代表该task或者说其root activity的图标(入口)。

如果你不希望用户返回已经进入background的activity,设置<activity>的finishOnTaskLaunch属性为"true"(见上面关于”清空back stack“的讨论)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: