Android Task 与 Back Stack
Tasks and Back Stack
一个应用通常包括多个 activity。每个 activity应用设计为围绕针对执行用户特定的行为和可以启动其它 activity。
一个 Activity也可以启动别个应用的 Activity。当别的应用完成,你的应用会重新激活 ,来自别个应用的 activity看起来像是自己的应用中的一样。尽管这些 Activity处于不同的应用, Android将这些 activity维护到同一个 task中给用户这种无缝的应用体验。
一个 task是用户执行一个特定的工作与用户交互的一组特定的 Activity的集合。 Activity被安排到同一个栈 (back stack)中,其中的 activity按顺序的打开的。
桌面是绝大多数任务被启动的地方。当用户在应用启动器中触击一个应用的图标,这个应用就会回到前台。如果没有这个应用的任务存在,那么创建一个新的 task,这个应用的 “main” Activity打开,并且作为这个 task栈的根 activity。
当当前 Activity启动另一个 Activity,新的 Activity被推到栈顶并且占据焦点。前一个 Activity保持在栈中,但是 处于 stop状态。当一个 activity停止掉后,系统保存他当前的用户接口的状态。当用户按 BACK键,当前的 Activity从栈中弹出并销毁,前一个 Activity被激活。栈中的 Actvity不会被重置,只会推入或者弹出。所以, back stack遵循一个后进先出的机制。
如果用户继续按 BACK键,栈中的每个 Activity都从栈中弹出显示前一个 Activity,直到返回到桌面 (或者到这个栈开始时正在运行的 Activity)。当所有的 Activity从栈中移出了,这个栈就不存在了。
一个 Task是一个聚合单元,当用户开启一个新的任务,或者通过 HOME键回到桌面,这个 task就移动到后台。当 task处于后台,所有的 activity都处于 stop状态,但是这个任务的 back stack仍是完好无损的。当新的栈占据了焦点之后,这个栈会很简单的失去焦点。
因为 back stack中的 activity不会被重置,如果你的应用允许你启动一个特定的 activity多次,创建一个新的 activity的实例,并且推入栈顶。所以在这种情况下,如果用户使用 BACK键导航,可能会多次看到同一个 activity。
总结 Activity 与 task 的默认的行为 :
- 当 Activity A 启动 Activity B, Activity A停止了,但是系统会保存他的状态 (例如滚动条的位置以及输入的文本信息 )。当用户在 B中按 BACK键, Activity 将继续他之间的状态。
- 当用户通过按 HOME键的方式离开一个 task,当前的 Activity停止,并且这个 task转到后台。系统将保持这个 task的所有的 activity的状态。如果稍后再继续这个 task,这个 task将回到前台,并且继续之间最栈顶的 Activity。
- 如果用户按 BACK键,当前 Activity弹出栈并被销毁。栈中之前的 Activity得以继续。当 Activity被销毁了,系统将不再保存其状态。
- Activity可以被实例化多次,甚至从其它的应用中实例。
保存Activity状态(Saving Activity State)
正如前面讨论的那样,当 activity停止的时候,系统默认的行为会保存他的状态。这样的话,当导航到上一个 Activity,他的接口会像他离开时的一样展现给用户。然而,你可以且应该在回调方法中主动保存你的状态,以避免你的 Activity被销毁掉之且必须重新创建。
当系统停止掉你的 Activity,系统可能为了重新获得内存而完全销毁掉他。当这种情况发生了, activity的状态信息会丢失掉。这种情况发生了,系统仍然知道这些 activity在 back stack有一个位置的,但是当 activity到栈顶的时候,系统会重新创建他,而不是 resume他了。为了避免丢失用户的工作,你需要实现 onSaveInstanceState()主动保存你的 activity状态。
管理Task(Managing Tasks)
像前面描述的一样, Android管理 task与 back stack的方法是,将所有的打开的 activity连续的放进同一个 task中,并且放到一个“后入,先出”的堆栈中,面对大多数的应用,你不必关心你的 activity是如何与 task关联的,不必关心你的 activity是如何存在于 back stack中的。然而你可能想打破这种常态的行为。也许你想要让你应用的 activity被启动的时候去开启一个新的 task(而不是将其放入当前的栈中 );或者,当你启动一个 activity的时候,你想将其转到一个已存在的他的实体中去 (而不是在 back stack 的栈顶创建一个新的实体 );或者,当你离开这个 task时,你想让你的 back stack栈中除根 activity的所有的 activity都被清理掉。
你可以做到这些甚至更多,用 <activity>的 manifest元素的属性以及你传递给 startActivity()的 intent的 flag。
关于这些,你可以用的 <activity>的主要属性如下:
taskAffinity
lauchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
主要的 intent的 flag如下:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
定义启动模式(Defining launch modes)
Launch mode 允许你定义一个 activity的新的实体与当前 task是如何关联的。你可以用两种方式定义不同的启动模式:
- 使用 manifest 文件
当在 manifest中声明你的 activity的时候,你可以指定当他启动时他如何与 task关联。
- 使用 intent的 flag
当你调用 startActivity()的时候,你可以为 intent 设置一个 flag以声明这个新的 activity应如何与当前的 task关联。
照这样,如果 Activity A启动 Activity B,可以在 manifest中定义 B如何与当前的 task如何关联,也可以在 Activity A中要求 Activity B与当前 task 如何关系。如果两个 Activity都定义了 Activity B应该如何与当前 Task关联,那么 Activity A的要求会更为荣幸的得到应用。
使用manifest文件(Using the manifest file)
当在 manifest文件中声明一个 activity的时候,你可以通过 <activity>的 lauchMode属性来指定 activity应该怎样与 task关联。
launchMode可以指定一个指定其 activity应该如何启动到一个 task中。这里有四种不同的 launchMode你可以使用。
“standard”(默认模式 )
默认模式。系统会从启动他的 task中创建一个该 Activity的新实例 ,并且导向他。一个 Activity可以被实例化多次,每个实例可以属于不同的 task,一个任务可以拥有多个实例。
“singleTop”
如果一个 Activity的实例已经存在当前的 task的栈顶了,系统会通过 onNewIntent()方法将 intent导向这个实例,而不是创建这个 Activity的新实例。这个 Activity可以被实例化多次,每个实例可以属于不同的 task,且每个 task可以拥有多个该 Activity的实例 (但是 back stack的栈顶的 activity不是已存在的该 Activity的实例 )。
例如:一个 task的 back stack包括一个根 Activity A和 Activity B,C和在栈顶的 D(当前栈的情况是 A-B-C-D,D在栈顶 )。一个 D的 intent到达了,如果 D是 ”standard”的启动模式,那么一个新的实例产生,并且栈会变成 A-B-C-D-D。然而,如果 D的启动模式是 ”singleTop”的,那么 intent通过 onNewIntent()传给已存在的栈顶的 D的实例 ,这时候的栈仍然是 A-B-C-D。如果一个 B的 intent到达了,那么一个 B的新实例会压入的栈中,即使 B的启动模式是 ”singleTop”。
注:当一个 Activity的实例被创建,用户可以通过 BACK键回到前一个 activity。但是如果是一个已存在的实例处理了 Intent,那用户不能通过 BACK键回到 onNewIntent()之前的 Activity状态了。
“singleTask”
系统创建一个新的任务,并实例化一个新的 Activity对象作为其根。如果已经有一个该 Activity的实例存在在一个单独的 task中了,系统会将 intent通过 onNewIntent()发布到已存在的这个实例中去,而不是创建一个新实例。同时只能有一个 Activity的实例存在。
注:尽管该实例存在于任务的根部,但是 BACK键仍然返回到之前的 Activity中去。
“singleInstance”
与 ”singleTask”一样,但是系统不能启动别个 Activity到这个 Activity的实例所在的 task中去。这个 Activity总是单一的,且是他所在的 task的唯一成员。通过这个 Activity启动的其它任何 Activity都将单独启动一个单独的 task。
不论一个 Activity是在同一个栈中启动,还是在一个新的栈中启动, BACK键都会让用户回到前一个 Activity中。然而,如果从你的 task(task A)启动一个被设置为 ”singleTask”启动模式的 Activity,然后这个 Activity可能有一个实例存在后台的,这个实例属于一个 task,并且 有他自己的 back stack(Task B)。在这种情况下, Task B被带到前台去处理一个新的 intent,按 BACK键在回到 Task A的栈顶 Activity之前,首先会导向 Task B的后台 Activity。
使用Intent的flag(Using Intent flags)
当启动一个 Activity的时候,你可以修改 Activity与他的 task的默认的关联关系,你可以通过向 startActivity()传递的 Intent中包含一个 flag来实现。这些你可以使用来改变默认行为的 flag如下:
FLAG_ACTIVITY_NEW_TASK:
在一个新的 task中启动 Activity。如果你启动的这个 Activity已在一个 task中运行了,这个 Activity将随其最后一次保存的状态一起被置到前台,并且这个 Activity会在 onNewIntent()中接到这个请求的 Intent。
这个过程和 ”singleTask”的启动模式一样。
FLAG_ACTIVIT_SINGLE_TOP:
如果 Activity启动的是当前的 Activity(在 Back Stack的栈顶 ),那么这个存在的实体会接到一个 onNewIntent()的调用,而不是创建一个新的实例。
这个过程和 ”singleTop”的启动模式一样。
FLAG_ACTIVITY_CLEAR_TOP:
如果一个 Activity已经在运行的栈中启动了,然后替代启动一个 Activity新的实例的是,所有的在他上面的 Activity将被销毁,并且这个 Intent会被传递给这个 Activity的实例的 onNewIntent()中。
没有合适的 lauchMode值与之对应。
处理affinity(Handling affinities)
Affinity表示 Activity更应该属于哪个 task。默认情况下,同一个应用的所有 Activity有相同的 affinity。所以,默认情况下,同一个应用的所有 Activity更倾向于属于同一个 task。然而你可以修改 Activity默认的 affinity。不同的应用的 Activity可以拥有共享一个 affinity。同一个应用的 Activity可以分配不同的任务的 affinity。
你可以通过修改 <activity>元素的 taskAffinity属性修改任何给定的 Activity的 affinity。
TaskAffinity属性是一个字符值,他必须在 <manifest>被定义成在包名中唯一的 ,因为系统用名字来识别应用的默认的 task affinity。
Affinity在两种情况下发生作用。
- 当 intent启动一个 Activity包含一个 FLAG_ACTIVITY_NEW_TASK 的 flag。
一个新的 Activity在默认情况下属于通过 startActivity()启动他的那个 Activity所在的 task。它被压入调用者相同的 back stack中。然而,如果给 startActivity()传递的 intent包含一个 FLAG_ACTIVITY_NEW_TASK的 flag,系统将会寻找一个不同的 task去安置这个新的 Activity。通常他是一个新 task。然而,它并一是必定是的。如果这里已存在一个与新的 Activity相同的 affinity的 task, Activity会启动到这个 task中去。如果没有存在的,那就开启一个新的 task。
如果这个 flag促使一个 Activity属于一个新的 task且用户是按 HOME键离开他的,这里必须要有办法让用户导回之前的 task。一些实体(像 notification管理者)通常在一个扩展的 task中启动,从不让他们作为自己的 task的一部分,所以他们常把 FLAG_ACTIVITY_NEW_TASK放到 Intent中传给 startActivity()。如果你有一个 Activity被一个外部实体激活,而且这个激活可能会使用到这个 flag,那么你要注意,用户有一个独特的方式返回到启动他的 task中去,比如通过一个 lancher的图标。
- 当 Activity的 allowTaskReparenting属性被设为 true时。
在这种情况下,那个 Activity可以从他启动的 task移动到他的 affinity的 task中去,当这个 task转到前台的时候。
例如:假如有一个旅游的应用,他包含一个报告选择了的城市的天气状况的 Activity。他有一个与同一应用的其它 Activity有相同的 affinity(默认的系统的 affinity)并且他允许用这个属性 re-prearenting。当你的一个 Activity启动了这个天气报告的 Activity,他初始的属于与你的 Activity相同的 task。然而,当这个旅游应用的 task转到了前台,天气报告的 Activity被移到那个 task并且显示他。
清理back stack(Clearing the back stack)
如果用户离开一个 task较长的时间,系统清理掉除 root Activity之外的其它所有 Activity。当用户返回这个 task,只有 root Activity被恢复。系统的这么做是因为,经过一个相当长的时间,用户可能是放弃了之前的工作,而现在返回这个 task是为了开启某项新的工作。
修改这个行为你可以使用如下一些 Activity的属性:
alwaysRetainTaskState
如果一个 root activity的这个属性被设为 true,那么上面描述到的默认的行为将不会发生。 Task将在栈中保留所有的 Activity,即使过了较长一段时间。
clearTaskOnLaunch
如果 task的 root activity的这个属性被设为 true, 不论什么时候,用户离开这个 task,然后回到他,这个栈会清理到 root activity。换句话说,他是 alwaysRetainTaskState的反义词。用户总是返回到这个栈的初始化状态,即使是刚刚离开这个栈。
finishOnTaskLaunch
这个属性像 clearTaskOnLaunch一样,但是他仅针对一个单独的 Activity,而不是整个 Task。他也可以促使任何一个 Activity离开,包括 root Activity。当他设为 true时,这个 activity只会当前会话保存部分 task。当用户离开再返回这个 task,他将不再重现。
启动一个task(Starting a task)
你可以设置一个 Activity为一个 task的入口,其方法是给出一个 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产生一个图标和一个标签,他们将显示在应用启动器中,他给用户了一个启动这个 activity的方法,以及当其启动之后任何时候回到这个任务的途径。
第二个能力很重要,必须让用户可以离开一个任务,并且可以通过 Activity launcher返回来。为些,那两个启动模式通常用来初始化一个任务,,仅当这个 Activity拥有一个 ACTION_MAIN和一个 CATEGORY_LAUNCHER的 filter时才使用 ”singleTask”或 ”singleInstance”。想象一下,假如,如果没有这个 filter将有可能发生什么 ?一个 intent启动了一个 ”singleTask”的 Activity,初始化为一个新的 task,用户在这个任务上消磨了一些时间。然后用户按 HOME键,这时这个任务被发到后台,不可见了。由于 其在 application 的 launcher中没有一个 描述,用户就没有办法回到这个 task了。
这种情况下,如果你不希望用户回到某个 Activity,可以设置 <Activity>元素的 finishOnTaskLaunch为 true。