天天看点

Tasks and Back Stack-Android 6.0开发者文档一、说明二、保存activity状态三、管理task

原文地址: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

一、说明

一个应用通常会包含多个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描述了不同动作对于回退栈的影响。

Tasks and Back Stack-Android 6.0开发者文档一、说明二、保存activity状态三、管理task

如果用户连续按返回键,那么回退栈中的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。

Tasks and Back Stack-Android 6.0开发者文档一、说明二、保存activity状态三、管理task
注意:后台可以存在多个task。如果用户在后台运行了太多task,系统可能会为了回收内存销毁一些后台activity,从而导致activity状态丢失。具体信息见下文。

由于回退栈中的activity不会重排,所以如果你启动一个已经存在的activity,那么会创建一个新的此activity实例并加入到栈顶(而不是将已存在的activity移到栈顶)。因此,你的应用的activity可能会被实例化多次,如下图3。用户点击返回键时,activity实例会按照打开的顺序依次显示。如果你不希望你的activity被实例化多次,你可以通过某种方式做到,具体方法见下文。

Tasks and Back Stack-Android 6.0开发者文档一、说明二、保存activity状态三、管理task

下面对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显示了当这种情况发生时系统的动作

Tasks and Back Stack-Android 6.0开发者文档一、说明二、保存activity状态三、管理task

注意:通过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。

继续阅读