首页 > 代码库 > Android Api Component---翻译任务和回退栈(Tasks and Back Stack)
Android Api Component---翻译任务和回退栈(Tasks and Back Stack)
一个应用程序通常包含多个activity。每一个activity应当围绕一个指定的用户可以执行的并且可以开启其它activity的动作种类被设计。例如,一个emali应用程序也许有一个activity展示新消息列表。当用户选择了一个消息的时候,一个新的activity会打开查看这个消息。
一个activity甚至可以开启设备上的其它应用程序的activity。例如,如果你的应用程序向发送一个邮件消息,你可以定义一个intent执行一个"send"动作并且包含一些像email地址和消息的数据。来自于另一个应用程序的activity定义他自己使用这种intent打开。在这这个例子中,这个intent将发送一个email,因此一个email应用程序的"compose"activity开启(如果多个activity支持相同的intent,那么系统会让用户选择使用哪一个)。当email发送之后,你的activity恢复并且它就好像这个邮件activity是你的应用程序的一部分。尽管应用程序之间的activity是不同的,但是Android通过保持所有的activity在相同的task中无缝的维护这些用户体验。
一个任务是当执行某种工作时与用户交互的activity的集合。activity被安排在一个栈中(回退栈),每一个activity会按照进入的顺序被打开。
设备的Home屏幕是大多数任务开始的地方。当用户在一个应用程序启动器里按了一个图标时,应用程序的任务就被调到前台。如果给这个应用程序没有任务存在(应用程序最近已经被使用了),那么一个新的任务会被创建并且"main"activity在那个栈中给那个应用程序以根activity打开。
当当前的activity打开另一个的时候,新的activity被推到栈顶并且取得焦点。前面的activity任然在战中,但是被停止了。当一个activity停止的时候,系统回收当前的用户接口的状态。当用户按了返回按钮,当前的activity会从栈顶被弹出(这个activity被销毁)并且前面的activity恢复(前面的UI的状态被恢复)。在栈中的activity用户不会被重新整理,只有从栈中推送和弹出-当在当前activity中开启的时候推送到栈中并且当用户使用返回按钮离开的时候被弹出。像这样,回退栈操作作为一个“后进先出”的对象结构。下图用时间线展示了用当前的回退栈处理activity之间准时的每一个点的行为。
这个图代表了每个新的activity在任务中如何添加项到后台栈中。当用户按了回退按钮的时候,当前的activity被销毁并且前一个activity重新开始。
如果用户继续按返回,在栈中的每一个activity将被弹出去展现前一个activity,直到用户返回到主屏幕未知(或者当任务开始的时候无论哪一个正在运行的activity)。当所有的activity从栈中被移除的时候,这个任务也就不再存在。
当用户开始一个新任务或者通过Home键进入主屏幕的时候,一个任务是一个能够移动到“后台”的连续单元。然后在后台,在任务中的所有activity都会被停止,但是对这个任务的回退栈仍然是完整的-指示当另一个任务进行的时候,这个任务只是失去了焦点,正如下图展示的一样。一个任务然后可以返回到“前端”以至于用户可以获得他们离开时的位置。加入,例如,当前的任务(Task A)在它的栈中有三个activity-两个在当前activity的下面。用户按了Home键,然后从这个应用程序启动器开启一个新的应用程序。当主屏幕出现的时候,Task A进入后台。当新应用程序开始的时候,系统给这个应用程序(Task B)用它拥有的activity的栈开始了一个任务。在跟那个应用程序交互之后,用户又返回Home并且选择了那个原始的被开启的Task A。现在,Task A来到了前端-在它的栈中的所有三个activity是完整的并且在栈顶的activity重新开始。此时,用户可以通过进入Home转换返回到Task B并且选择那个被开始的任务的应用程序图标(或者通过从概览屏幕选择app的任务)。这是Android上的一个多任务的例子。
注意:多任务能在后台被立即操作。然而,如果用户同时运行着很多后台任务,系统也许会按照回收内存的顺序销毁后台activity,这样就导致那些activity的状态将丢失。关于Activity state看下面的段落。
由于在回退栈中的activity永远不会被重新整理,如果你的应用程序允许用户开始超过一个的一个特别的activity,那个activity的实例被创建并且被推送的栈中(而不是带入任何activity实例的上面)。像这样的,在你的应用程序中的一个activity也许被初始化多次(甚至来自于不同的任务),就像在下图被展示的一样。像这样的情况,如果用户使用返回按钮导航返回,每一个activity的实例按照他们被打开的顺序显示(每一个用他们自己的UI状态)。然而,如果你不想让一个activity被初始化多次,你可以修改这种行为。如何这样做,将会在下面的Managing Tasks段落中被讨论。
来给activity和task的行为做一个总结:
当Activity A开启Activity B的时候,Activity A被停止,但是系统保留着它的状态(像滚动位置和输入表单的文本)。如果用户在Activity B中按了返回按钮,Activity A用它的状态重新开始被恢复。
当用户通过按Home键离开任务的时候,当前的activity被停止并且它的任务进入后台。系统保留着每个在任务中的activity的状态。如果用户后来通过选择启动器图标开始任务重新开始了这个任务,那么这个任务会进入前端并且在栈顶重新开始这个activity。
如果用户按了返回按钮,当前的activity从栈中被弹出并且被销毁。在栈中的前面的activity被重新开始。当一个activity被销毁的时候,系统不保留activity的状态。
activity可以被初始化多次,甚至从其它任务中初始化。
导航设计
对于app在android上如果导航工作的,读Android Design‘s Navigation文档
管理Task
在上面被描述的Android管理任务和回退栈的这种方式,通过把所有的被开始的activity放在相同的任务中并且用“后进先出”栈-对于大多数的应用程序起很多的作用并且你不需要担心你的activity如何跟任务关联或者他们在回退栈中是如何存在的。然而,你可能决定你想要终端正常的行为。也许你想让你的应用程序中的activity被开始的时候(代替放在当前任务内)开始一个新的任务;或者当你开始一个activity的时候,你想让一个存在的activity的实例走上前(代替在栈顶创建一个新的实例);或者当用户离开任务的时候你想让你的回退栈清除所有的activity,除了根activity。
你可是使用在<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
在下面的段落中你将看到你如何使用那些清单属性和intent的flag来定义activity如何跟task关联以及他们在回退栈中如何运行。
还有,分开讨论activity和task是考虑到task和activity也许被描述或者被管理在那个综述屏幕中。看更多Overview Screen的信息。通常你应该允许系统定义你的task和activity在综合屏幕中被描述,并且你不需要修改这种行为。
注意:大多数应用程序不会中端给activity和task的默认的行为。如果你决定你的activity需要就该默认行为,使用警告并且确保在运行期测试以及从其它的activity和任务使用返回按钮导航回退到它的时候activity的可用性。确保测试导航行为与用户的期望行为的冲突。
定义启动模式
启动模式允许你定义一个新的activity的实例如果跟当前任务关联起来。你可以用两种方式定义不同的启动模式:
使用清单列表
当你在你的manifest文件中定义了一个activity的时候,你当它开始的时候你可以指定这个activity如何跟任务关联。
使用Intent的flag
当你调用startActivity()的时候,你应该在Intent中包含一个flag,这个flag定义了这个新的activity如何(或者是否)应该和当前任务关联。
像这样的,如果Activity A开启Activity B,Activity B可以在它的manifest中定义它应该如何跟当前的task(或者所有)关联,并且Activity A也可以要求Activity B应该如何跟当前任务关联。如果两个activity都定义了Activity B如何跟一个task关联,那么Activity A的需求(在intent中被定义的)遵守于Activity B的需求(在它的manifest中被定义)。
注意:一些有效的启动模式对于这个manifest文件的intent的flag是无效的,同样的,一些给intent的flag的有效的启动模式不能在manifest中被定义。
使用manifest文件
当在你的manifest文件中定义了一个activity的时候,你可以指定这个activity使用<activity>元素的lanuchMode属性应该如何跟一个任务关联。
这个lanuchMode属性指定了一个关于这个activity应当如何进入一个任务被运行的指令。有四个不同的启动模式你可以分配给launchMode属性:
"standard" (默认模式)
默认的,系统在任务中创建一个activity的实例,这个activity从这个任务中被开启并且把intent路由给这个activity。这个activity能被初始化多次,每一个实例可能属于不同的任务,而且一个任务可能有多个实例。
"singleTop"
如果一个activity的实例已经存在于当前任务的顶部,那么系统通过调用给它的onNewIntent()把这个intent路由到那个实例,然不是创建一个activity的新实例。这个activity可能会被初始化多次,每一个实例可能属于不同的任务,并且一个任务可能有多个实例(但是如果这个activity在回退栈的顶部,那就只有一个activity的实例存在)。
例如,加入一个任务的后台栈用activity B,C和D在顶端组成了根activity A。一个给activity D的intent到达。如果D有默认的"standard"启动模式,一个新的类实例就被启动并且这个栈会变成A-B-C-D-D。然而,如果D的启动模式是"singleTop",那么D重已经存在的实例通过onNewIntent()接收这个intent,因为他在栈的顶部-这个栈任然是A-B-C-D。但是,如果有个给activity B的intent到达,那么一个B的实例将被添加到这个栈中,甚至如果它的启动模式是"singleTop"。
注意:当一个activity实例被创建的时候,用户可能案子啊回退按钮返回到前一个activity。但是当一个activity的实例已经存在的时候运用一个新的intent,在新的intent到达onNewIntent()里面之前,用户不能按返回按钮来回到上一个activity的状态。
"singleTask"
系统创建一个新任务并且在新任务的根部初始化这个activity。然而,如果这个activity的实例已经存在于一个分离的任务中,系统就通过调用给它的onNewIntent()方法路由intent到已经存在的activity的实例中,然不是创建一个新的实例。在同一时间,只有一个activity的实例可以存在。
注意:尽管activity开始在一个新的任务中,但是回退按钮仍然可以给用户返回到前一个activity。
"singleInstance"
除了系统不启动任何其它的activity进入这个持有这个实例的任务中,其它的跟"singleTask"一样。这个activity总是单个的,并且它的任务只有一个成员;任被开启的activity通过这一个分割的任务打开。
作为另一个例子,Android浏览器应用程序通过在<activity>元素中指定singleTask启动模式定义web浏览器应该总是在它自己的任务中打开。这就意味着,如果你的应用程序发布了一个打开Android Browser的intent,它的activity不会被放在跟你的应用程序相同的任务中。相反,要么开始一个新的任务来开启浏览器,如果浏览器已经后台任务中运行,那个任务会被带到前面来操作这个新的intent。
不管是否一个activity开启在新任务或者相同任务中,就好像是这个activity开启了它,回退按钮总是将用户带到前一个activity。然而,如果你开始一个指定了singleTask启动模式的activity,那么如果一个activity的实例存在于一个后台任务中,整个任务会被带到前面。此时,后台栈在它的顶部现在包含了所有来自于被带到前面的这个任务的activity。如下图:
关于在manifest文件中更多使用启动模式的信息,看<activity>元素文档,在哪里lanuchMode属性和被接收的值被讨论更多。
注意:你给你的activity用launchMode属性指定的行为可以被Intent所带的flags覆盖开启你的activity,接下来会讨论这个。
使用Intent的flag
当你开始一个activity的时候,你可能通过包含在你传递给startActivity()的intent中的flag修改了一个activity的默认的关联给它的任务。你能用这些flag修改默认行为的是:
FLAG_ACTIVITY_NEW_TASK
在一个新的任务中开启activity。如果一个任务已经给你现在正在开启的activity运行着,那么那个任务会用它的最后的状态被恢复来带到前端并且这个activity在onNewIntent()中接收一个新的intent。
这个产生的行为跟在前面讨论过的"singleTask"launchMode值是相似的。
FLAG_ACTIVITY_SINGLE_TOP
如果被开启的activity是当前activity(在回退栈顶部),那么这个已经存在的实例代替创建一个新的activity实例接收一个调用到onNewIntent()。
这个跟前面讨论的"singleTop"launchMode值产生相同的行为。
FLAG_ACTIVITY_CLEAR_TOP
如果被启动的activity已经运行在当前任务中,那么代替运行一个activity的新实例,在这个activity的上面的所有其它activity被销毁并且这个通过onNewIntent()给这个被重新开始的activity的实例传递了这个intent。
没有launchMode属性值产生这种行为。
FLAG_ACTIVITY_CLEAR_TOP是最长跟FLAG_ACTIVITY_NEW_TASK结合使用。当一起呗使用的时候,那些flag事一种本地化一个已经在其它认为中存在的activity的方式并且把它放在一个能响应intent的地方。
注意:如果这个activity的指定的启动模式是"standard",那它也会从这个栈中被移除并且在它的位置一个新的实例被启动来操作进入的intent。那是因为当启动模式是"standard"的时候,一个新实例总是为了一个新的intent被创建。
处理关系(Handling affinities)
这个关系指明一个activity更喜欢属于哪一个task。默认的,来自于相同应用程序的所有activity彼此有一个关系。因此,默认的,在相同应用程序中的所有的activity更喜欢在相同的任务中。然而,你可以给activity修改默认关系。被定义在不同应用程序中的activity可以共享一个关系,或者被定义在相同应用程序中的activity可以被安排在不同的任务关系中。
你可以使用<activity>元素中的taskAffinity属性来修改被给定的这个activity的关系。
这个taskAffinity属性取一个字符串值,在<manifest>元素中定义的这个属性它必须在默认的包名中是唯一的,因为系统给应用程序使那个名字来分辨默认的任务关系。
在两种环境下这个关系会开始起作用:
当包含这个FLAG_ACTIVITY_NEW_TASK标记的intent调用startActivity()启动一个activity的时候。你一个新的activity默认的被运行在activity的任务中。它作为调用者被推到相同的后台栈中。然而,如果被传递给startActivity()的intent包含FLAG_ACTIVITY_NEW_TASK标记,系统寻找一个不同的任务来给新的activity空间。通常,它是个新的任务。然而,它不需要是。如果作为新的activity在已经存在的任务中有了相同关系的activity,这个activity被启动到这个任务中。如果没有,它会开始一个新任务。
如果这个标记导致一个activity开始一个新的任务并且用户按Home键离开它,那就必须要有一些方式可以给用户导航回到那个任务。一些实体(像通知管理)总是在一个内部任务中开启activity,从来不会作为他们拥有的部分,因此他们总是把FLAG_ACTIVITY_NEW_TASK放入它们传递给startActivity()的intent中。如果你有一个activity可以通过内部实体调用也许就是用了这个标记,注意用户有一个独立的方式可以回到他们打开的那个任务,像用启动器图标(任务的根activity有一个CATEGOR_LAUNCHER的intent过滤器;看下面的Starging a task段落)。
当一个activity把它的allowTaskReparenting属性设置为"true"。
在这个例子中,当任务来到前端的时候,这个activity可以从这个开启它的有一个关系的任务中移除。
例如,假设在被选择的城市的一个天气预报的activity状态被定义为旅游应用程序的部分。它跟其它的那些在相同应用程序中的activity有相同的关系(默认应用程序的关系)并且它允许父类使用这个属性。当你的其中的一个activity开启了这个天气预报的activity的时候,它最初跟你的activity属于相同的任务。但是,当旅游应用程序的任务回到前台的时候,天气预报activity被安排到那个任务中并且在它里面被陈列。
建议:如果一个.apk文件从你用户的角度来看包含超过一个"application",你可能想要使用taskAffinity属性分配不同的关系给那些跟每一个"application"关联的activity。
清除回退栈
如果用户离开一个任务很长时间,系统会把任务中的除了根activity以外的所有activity清除掉。当用户又返回到任务的时候,只有根activity被恢复。系统表现的这种方式,因为,很长时间以后,用户很可能有已经遗弃了它们之前正在做的和返回到任务开始新的事情。
你可以用activity的一些属性来修改这种行为:
alwaysRetainTaskState
如果这个属性在任务的根activity中被设置为"true",被描述的默认行为就不会发生了。这个任务回收在它的任务中的所有activity甚至在一段时间之后。
clearTaskOnLaunch
如果这个属性在一个任务的根activity中被设置为"true",无论用户什么时候离开并且返回,栈都会被清理到根activity。换句话说,它是alwaysRetainTaskState的对立面。用户总是返回到任务的初始状态,甚至在离开了任务很长时间之后。
finishOnTaskLaunch
这个属性像clearTaskOnLaunch,但是它操作单个的activity,而不是整个任务。它也可以引起任何activity离开,包括根activity。当它被设置为"true",这个activity在当前session下任然是这个任务的一部分。如果用户离开了并且又回到这个任务,它不再呈现。
开始一个任务
你可以设置一个activity作为一个任务的入口点,通过给它一个intent带有"android.intent.action.MAIN"的过滤器当做被指定的动作还有"android.intent.category.LAUNCHER"当做被指定的类别。例如:
<activity ...> <intent-filter ...> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
一个这种intent的过滤器目标是一个给activity的icon和标签将在应用程序启动器中被陈列,给用户一种启动这个activity的和返回到这个它被启动之后的任何时间创建任务的方式。
这个的第二个功能是重要的:用户必须能离开任务并且然后后来使用这个activity的启动器回来。对于这种原因,这两个启动模式标记activity好像总是初始化一个任务,"singleTask"和"singleInstance",只有当activity有一个ACTION_MAIN和一个CATEGORY_LAUNCHER过滤器的时候才应该被使用。想象一下,例如,如果过滤器丢失了可能会发生什么:一个intent启动一个"singleTask"activity,初始化一个新任务,并且用户花费一些时间在那个任务中工作。用户然后按了Home键。任务现在被发送到后台并且不可见了。现在用户没有方式回到这个任务,因为它在这个应用程序启动器中没有被描述。
对于那些你不想让用户能回到一个activity的情况,设置<activity>的元素的finishOnTaskLaunch为"true"(看 Clearing the stack)。
?
Android Api Component---翻译任务和回退栈(Tasks and Back Stack)