原文地址:Tasks and Back Stack
- 一、说明
- 二、保存activity状态
- 三、管理task
- 3.1 定义launch mode
- 3.1.1 使用manifest指定
- 3.1.2 使用intent的flag指定
- 3.2 处理affinity
- 3.3 清理回退栈
- 3.4 启动task
- 3.1 定义launch mode
一、说明
一个应用通常会包含多个activity。每个activity都可以启动其他的activity。例如,邮件应用会有一个activity显示信息列表,当用户选择了某个信息时,它会启动新的activity以显示信息的具体内容。
activity甚至可以启动其他应用的activity。例如,如果你的应用想要发送一封邮件,那么你可以定义一个加入了“send”action的intent,其他应用的声明了可以处理此intent的activity将会被启动(如果有多个activity可以处理此intent,系统会显示对话框让用户选择使用哪个activity)。当邮件发送完毕后,你的activity会重新进入resumed状态,就好像发送邮件的activity是你的应用的一部分一样。Android系统通过将这些activity放在同一个任务(task)中做到应用的无缝切换。
任务(task)是用户在进行某些操作时交互的activity的集合。这些activity被放在一个栈中(back stack),表示activity打开的顺序。
设备的主页(Home screen)是大多数task的起点。当用户点击launcher的图标或者主页的快捷方式时,应用的task会到前台。如果这个应用的task不存在(此应用在最近没有被打开过),那么系统会新建一个task,并将应用的“main”activity启动放置在栈底。
当前activity启动另一个activity时,新的activity会push到栈顶并且获得焦点。之前的activity会进入sotpped状态。当activity进入stopped状态时,系统会保存界面的状态。当用户点击返回键时,当前activity会被pop出栈顶并销毁,之前的activity进入resume状态(此activity的UI状态会恢复)。栈中的activity不会重新排列,只会进行push和pop操作——启动新的activity时push到栈顶,用户使用返回键离开此activity时pop出栈顶。所以说,这个回退栈是“先进后出”(last in,first out)的结构。下图1描述了不同动作对于回退栈的影响。
如果用户连续按返回键,那么回退栈中的activity将会依次pop出栈顶,直到返回到主页。当所有activity都移出回退栈时,task就不存在了。
当用户启动一个新的task或按home键时,task会进入后台。进入后台后,task中所有的activity都会进入stopped状态,但是回退栈仍然存在——此task只是失去了焦点,被另一个task覆盖了,如下图2。task可以重新回到前台,用户可以继续他们的工作。例如,当前task(Task A)有三个activity在栈中,用户按下home键,从应用launcher启动一个新应用。在这个过程中,当主页显示时,Task A进入后台。当新的应用启动时,系统为新应用新建一个task(Task B)。在与新应用交互完成后,用户再次点击home键,然后选中Task A中的应用。这时,Task A会再次进入前台————栈中的三个activity都存在着,且栈顶的activity进入resume状态。另外,用户还可以用相同的方式切换到Task B。
注意:后台可以存在多个task。如果用户在后台运行了太多task,系统可能会为了回收内存销毁一些后台activity,从而导致activity状态丢失。具体信息见下文。
由于回退栈中的activity不会重排,所以如果你启动一个已经存在的activity,那么会创建一个新的此activity实例并加入到栈顶(而不是将已存在的activity移到栈顶)。因此,你的应用的activity可能会被实例化多次,如下图3。用户点击返回键时,activity实例会按照打开的顺序依次显示。如果你不希望你的activity被实例化多次,你可以通过某种方式做到,具体方法见下文。
下面对activity和task的行为做个总结:
- 当activity A启动activity B时,activity A进入stopped状态,系统会保存它的状态信息(如滑动的位置、列表中填写的信息)。如果用户点击了返回键,那么activity A会进入resume状态并恢复状态信息
- 当用户按下home键离开当前task时,当前activity会进入stopped状态,它所在的task进入后台。系统会保存task中所有activity的状态信息。如果后面用户点击task对应的launcher图标,那么此task会重新进入前台,栈顶的activity进入resume状态
- 如果用户按下返回键,当前activity会被pop出栈顶并且销毁。之前的activity会进入resume状态。activity被销毁后,系统不会再保存activity的信息
- activity可能被实例化多次
二、保存activity状态
默认情况下,系统会保存进入stopped状态的activity的信息。这样的话,当user返回到之前的activity时,activity就可以恢复之前的界面了。但是,你还是应该自己通过回调方法保存activity状态,以防activity被销毁后需要恢复状态。
当你的activity进入stopped状态时,系统可能会因为内存紧张销毁你的activity,系统保存的你的activity的状态也会丢失。这时,你的activity仍然在回退栈中,当此activity重新回到栈顶时,系统会重建它(recreate而不是resume)。为了避免丢失用户操作信息,你应该通过onSaveInstanceState()方法保存用户操作。
三、管理task
就如之前所说的,Android管理task和回退栈的方式,是将activity依次放在同一个栈中(先进后出),这种方式可以满足大多数应用的需求,你不必担心你的activity怎样与task交互或者activity怎样存在于回退栈中。如果你有“不正常需求”的话,比如你希望启动activity时新建一个task并加入其中(而不是加入到当前task),或者你希望启动activity时让已存在的它的实例到达栈顶(而不是新建一个activity实例加入到栈中),或者你希望用户离开此task时将除根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>
属性和intent的flag,会对activity与task的交互和在回退栈中的行为,造成怎样的影响。
另外,在“overview screen”部分也有task和activity如何管理的内容,你可以在overview screen部分查看更多内容。通常,你允许系统管理task和activity在overview screen界面的显示即可,不需要改变默认设置。
注意:大多数应用都不需要修改activity和task的默认实现。如果你确定你的activity需要改变这些内容,请确保activity在启动时、从其他activity或者task中返回时可以符合用户的预期
3.1 定义launch mode
launch mode表示你的activity怎样加入到task中。你可以通过两种方式添加launch mode:
- 使用manifest文件。在manifest文件中,你可以指定当activity启动时怎样与task交互
- 使用intent的flag。当你调用startActivity()方法时,可以在传入的intent中添加flag。此flag表示新的activity怎样与当前task交互
如果activity A启动activity B,那么activity B可以在manifest文件中指定它将怎样与当前task交互,同时,activity A也可以要求activity B与当前task的交互方式(通过flag)。如果activity A和activity B都指定了activity B与task的交互方式,那么activity A的请求(intent中的flag指定)会高于activity B的请求(manifest中指定)。
注意:有些launch mode,在manifest文件中可用,而intent的flag不可用。同样的,有些launch mode在intent的flag中可用,在manifest中不可用
3.1.1 使用manifest指定
你可以在manifest文件中的
<activity>
标签中添加launchMode属性来指定launch mode。
launchMode属性可用的launch mode有以下几种:
- standard(默认)。系统会新建一个activity实例加入到它被启动的intent所在的栈中。此activity可以被实例化多次,它们可以处于不同的task中,同一个task也可以有多个此activity实例
-
singleTop。如果当前task的栈顶已经是此activity的实例了,那么系统会调用此activity的onNewIntent()方法,而不是新建一个activity。此activity可以被实例化多次,并且存在于不同的task中,一个task可以有多个此activity的实例。
注意:当新的activity的实例被创建时,用户可以通过返回键返回到上一个activity。但是,如果栈顶的activity是singleTop模式且通过onNewIntent()更新了某些数据,那么用户就不能通过返回键回到之前的状态了
-
singleTask。系统会创建一个新的task,并且将此activity作为根activity放置在task中。如果此activity的实例已经存在于其他task中,系统会调用此activity的onNewIntent()方法,而不是新建一个activity实例(此时如果此activity上面还有activity,会全部弹出,让此activity成为栈顶)。此activity的实例同一时间仅允许存在一个。
注意:虽然此activity是在新的task中,但是返回键还是可以返回之前的activity
- singleInstance。类似于singleTask,但是singleInstance不会将其他activity加入到此activity存在的task中。此activity将会是此task的唯一成员,此activity启动的其他activity会在其他task启动
例如,Android浏览器应用可以将
<activity>
的launchMode设置为singleTask,这意味着,如果你的应用启动了此浏览器,它的activity不会跟你的应用的activity处于同一个task,而是新建一个task并把它的activity放进去(如果此浏览器已经有了一个task,那么此task会到前台并处理新的intent)
不管被启动的activity和启动它的activity是在同一个task还是不同task,用户都可以用返回键返回到之前的activity。但是,如果目标activity为singleTask且已经存在于后台的task,那么当启动目标activity时,会将它所在的task放到前台。这样的话,回退栈中就会包含后台task的所有activity,下图4显示了当这种情况发生时系统的动作
注意:通过launchMode属性指定的launch mode可以被intent中的flag覆盖(overridden)
3.1.2 使用intent的flag指定
在启动activity时,你可以在startActivity()方法传入的intent中添加flag,来修改目标activity默认与task的互动方式。你可以使用的flag如下:
- FLAG_ACTIVITY_NEW_TASK。在新的task中启动activity。如果有某个task已经有了目标activity的实例,那么系统会将此task放在前台,并通过onNewIntent()将数据发送给目标activity(原文中还有一句——此flag的作用与“singleTask”相同。但是实践发现,并不相同,不同之处为:1.此flag启动的activity如果存在于其他task,而且其他task的此activity实例之上还有其他activity,那么不会像“singleTask”那样将其他activity弹出,2.此flag被taskAffinity严重影响,会影响其是否新建task,3.此flag不保证activity在所有栈中唯一,甚至不保证在同一栈中唯一。所以这个flag的描述应该为:尝试寻找一个合适的task启动activity,不存在这样的task则新建一个task)
- FLAG_ACTIVITY_SINGLE_TOP。如果启动的activity是当前(原文档没有“当前”两个字,但是我在写demo测试的时候发现,只有当前task的栈顶为目标activity时,FLAG_ACTIVITY_SINGLE_TOP才有用)task顶部activity,那么不会创建此activity,而是调用其onNewIntent()方法。此flag的作用与“singleTop”相同
-
FLAG_ACTIVITY_CLEAR_TOP。如果启动的activity已经在当前task中,那么不会新建此activity,而是将栈中此activity实例之上的activity销毁,然后调用其onNewIntent()方法。FLAG_ACTIVITY_CLEAR_TOP通常与FLAG_ACTIVITY_NEW_TASK一起使用。
注意:如果目标activity的launch mode为“standard”,那么它也会被弹出回退栈,系统会在它的位置新建一个activity实例
3.2 处理affinity
affinity表示activity应该属于哪个task。默认情况下,同一个应用的所有activity有相同的affinity。所以默认情况下同一应用的所有activity都会放在同一个task中。但是,你可以修改activity的默认affinity。不同应用的activity可以使用相同的affinity,同一应用的不同activity也可以使用不同的affinity。
你可以在
<activity>
标签下添加taskAffinity属性以修改activity的affinity。
taskAffinity是一个string。它的值不能为manifest文件的默认包名,因为系统将包名作为应用的默认taskAffinity。
affinity在以下两个情况起作用:
-
启动目标activity时添加了FLAG_ACTIVITY_NEW_TASK。
默认情况下,被启动的activity会加入到调用startActivity()的activity所在的task中。但是,如果startActivity()传入的intent中添加了FLAG_ACTIVITY_NEW_TASK,那么系统会寻找一个新的task以存放新的activity。通常,那会是一个新的task。但是,如果已经存在一个与新activity的affinity一致的task,那么新activity会加入到此task中。
如果这个flag导致启动了一个新task,且用户通过home键离开了此task,那么你必须提供某种方式让用户返回到此task。有些实体,如通知栏,总是在其他task中启动activity,即总是在startActivity()时传入FLAG_ACTIVITY_NEW_TASK。如果你的activity可能被外部实体用FLAG_ACTIVITY_NEW_TASK调用,请注意为用户提供某种方法回到此task,例如通过launcher图标
-
activity的allowTaskReparenting属性为“true”。
如果将activity此属性设为true,那么当与此activity的affinity相同的task进入前台时,此activity会从启动它的task移动到此task中。
例如,某个activity可以查看某个城市的天气,它是一个旅游应用的一部分,它的affinity为默认,allowTaskReparenting为true,此时,你的应用启动此天气activity,那么此activity跟你自己的activity在同一task中,然后,如果旅游应用的task进入前台,那么此天气activity会移动到旅游应用的task并显示
提示:如果你的应用包含多个用户可以看到的“应用”(即launcher有多个),那么你最好根据不同的launcher为相关的activity分配不同的taskAffinity
3.3 清理回退栈
如果用户离开某个task很长时间,那么系统会清理此task除了根activity的所有activity。当用户返回到此task时,只有根activity会恢复。
如果你想要改变这一点,可以修改以下activity属性:
- alwaysRetainTaskState。如果某个task的根activity的这个属性为true,那么系统就不会在长时间未回到此task后清理task了
- clearTaskOnLaunch。如果某个task的根activity的这个属性设置为true,那么不论用户在离开此task后过了多久,系统都会清理task。换句话说,它就是alwaysRetainTaskState的反面。用户总是会回到此task的初始状态,不论离开此task多久
- finishOnTaskLaunch。这个属性类似于clearTaskOnLaunch,但是它只作用于单个activity,而不是整个task。它会关闭任何activity,包括根activity。如果将此属性设置为true,那么task中的此activity将只用于此次会话,如果用户离开此task,那么当用户返回到此task时,此activity不会显示
3.4 启动task
你可以将一个activity设置为task的入口(添加intent filter:action为“
android.intent.action.MAIN
”,category为“
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 filter后,会在launcher中显示代表这个activity的图标和标签,用户可以:1.通过此图标启动此activity,2.在此activity所在task启动后通过此图标返回到此task。
第二个功能很重要。Android要求:在离开task后,用户必须可以通过某种方式回到此task。因此,launch mode为singleTask或singleInstance的activity应该添加有ACTION_MAIN和CATEGORY_LAUNCHER的filter。想象一下,如果没有此filter会发生什么:某个intent启动了“singleTask”的activity,系统新建了一个task,用户在这个task中有一些动作,然后,用户点击home键,此时task在后台且不可见,那么用户将不能回到此task。
当然,如果你压根不希望用户返回到此activity,可以将
<activity>
的finishOnTaskLaunch设置为true。