Tasks and Back Stack 概述 所有的 activities 都属于特定的 task; 一个 task 中包含一系列的 activities ,用户可以与之交互; Tasks 可以被移到后台,并保存它所包含的 activity 的状态信息,这样用户在执行其它操作时,之前的操 作状 态就不会丢失;
一. 本章主要内容如下 保存 Activity 的状态 管理 Tasks 定义 Activity 的 launch modes(启动模式) 处理 affinities 清空 back stack 启动一个 task 一个应用往往包含多个 activities ,每个 activities 都应该围绕一种特定的用户操作来设计,而且它们都可以启动别的 activities 。例如,一个 email app 可以用一个 activity 来显示 email 列表,当用户选择其中一个具体的 email 时,用另一个 activity 来显示这个 email 的内容 。 一个 app 中的 activity 甚至可以启动别的 app 中的 activity 。如你的 app 本身没有发 email 的功能,但是你想让它能够通过其它的应用把 email 发出去,那么你可以定义一个 action 为 send 的 intent ,并在这个 intent 中包含你所在 send 出去的 data (数据),如地址或信息等 。当别的 app 中有可以执行 intent 的 activity 时,它们在收到这种 intent 后,就会启动相应的 activity 来执行这个 intent 。如果系统中有多个 app 可以执行这个 intent ,它就会让用户选择一个来执行 。当 email 发送完成后,你的 activity 又恢复到 resumes 状态了,好像 email activity 就是你 app 中的一部分一样 。虽然这个发送 email 可能是别的 app 中的 activities ,Android 为了良好的用户体验,会用同一个 task 来维护这些 activities 。 task 是一个 activities 的集合,当用户执行一个特定的操作时,其实是在和 task 里面的 activities 交互 。activities 会按它们的启动顺序被保存在一个 back stack(后进先出的回退栈) 中 ,后启动的 activity 成为在栈顶的正在活动的 activity 。 设备的 Home 屏幕是大多数 tasks 的开始位置 。当用户触摸某个 app 的图标时,那个 app 的 task 就会来到前台 。如果用户所触摸的 app 还没有 task (即这个 app 之前还没有被启动),那么系统就会给它创建一个全新的 task ,并且启动那个 app 的主 activity ,这个主 activity 将成为该 app task 中的第一个 activity 。 如果当前的 activity 启动了另一个 activity ,那么新启动的 activity 就会被压入栈顶并获取用户焦点,而之前的 activity 还会保存在 stack 中 ,只是它处于 stopped 状态而已 。在 activity 被 stop 的时候,系统会保存其当前的界面状态 。当用户按下回退按钮时,当前的 activity 就会被弹出栈顶(即这个 activity 就会被销毁) ,而紧临它之前的那个 activity 则恢复 resumes 状态(注意之前的 activity 在被 stop 的时候,系统保存了它的界面状态)。在 stack 中的 activities 的顺序是永远不会被重新排序的,这和栈的操作是一样的,只能出栈或入栈,而且都是活动在栈顶的(即只能压入到栈顶,或弹出栈顶元素) 。所以当用户打开新的 activity 时,这个新打开的 activity 会成为栈顶元素,而当用户按下回退按钮时,栈顶的 activity 出栈,同时被销毁掉 。其实 back stack 就是个后进先出的数据结构 ,下面的这张图形象的描述了各 activities 在启动和被销毁时 back stack 的状态 。
用户每按下回退键一次,栈顶的 activity 就会被弹出,而栈顶之下的那个 activity 就成为新的栈顶,当所有的 activities 都被从 stack 弹出后,这个 task 也就不复存在了 。 task 是个 有粘着力的的单元,当用户开启一个新的 task 或通过 Home 键返回 Home 界面的时候,task 会被移到后台 ,该 task 中的所有 activities 都会被 stopped 掉 。但是这个 task 还是保持完整的,它只是失去焦点而已 。如下图就形象的描述了这一点:Task B 成为活动的 task ,获取到用户焦点并与用户交互,而 Task A 被移到后台,暂时失去与用户交互的能力 。
在后台的 task 可以再次回到前台,而且回到前台时,它会恢复退至后台前的状态 。例如,当前的 task A 是活动的,它的 stack 中有三个 activities 。用户按下 Home 键,然后启动一个新的 app ,当 Home 屏幕出现的时候,task A 就马上进入后台了 。而当新的 app 启动时,系统为这个新的 app 创建一个 task ,假设为 task B 吧 ,那么 task B 就会维护自己的 stack 中的 activities 。在对这个 app 进行一些操作后,用户再次回到 Home 界面,然后再打开 task A 对应的那个 app 。现在 task A 就又回到前台了,而且它的 stack 中的所有 activities 都是完整的,栈顶的 activity 也会恢复 resumes 。这时,用户也可以通过这样的方式再切回到 task B 中 。这个实例描述了 Android 实现多任务的基础 。 注意:在执行多任务时,tasks 能在后台保存的时间可能是很短暂的,因为在执行多任务时,系统内存资源可能会比较紧张 。在内存资源不足的情况下,系统会销毁一些后台的 activities 以回收内存资源给正在 resume 的 activity 使用 。这样会导致暂存在后台 activity 的状态会丢失 。 因为在 back stack 中的 activities 是永远不会被重排序的,所以如果你的 app 允许用户多次启动一个特定的 activity ,那么每次启动这个 activity 时都会创建一个新的实例并压入栈顶(而不是把栈中已经存在的实例重新放到栈顶)。这样,一个 app 中的某个 activity 可能被实例化多次,而且它们都有各自的 UI 状态,如下图所示的一样
如果你不想让一个 activity 被多次实例化,你可以改变这种默认的行为,后面会介绍这一点 。 总结一下 activities 和 tasks 的默认行为: (1)当 Activity A 启动 Activity B 的时候,Activity A 被停止掉,但是系统会保存其状态(如滚动位置和文本输入的内容等)。如果用户在操作 Activity B 的过程中按下回退键,那么 Activity B将被销毁,而 Activity A 则恢复 resumes 并且恢复它被 stop 前的状态 。 (2)当用户通过 Home 键离开一个 task 的时候,这个 task 的栈顶 activity 将会被 stop 且整个 task 会被移到后台 。系统会保存这个 task 中所有 activity 的状态,当用户通过这个 launcher 返回到这个 task ,那么这个 task 就会回到前台,且其栈顶的 activity 会恢复 resumes 。 (3)如果用户按下回退键,那么当前的 activity 将会被弹出栈顶并被销毁,而之前在这个栈顶之下的那个 activity 将恢复 resumes 。注意:被销毁的 activity ,系统是不会保存其状态的 。 (4)同一个 activity 可以被多次实例化 。 For more about how app navigation works on Android, read Android Design's Navigation guide.
二. 保存 activity 的状态 正如上面所提到的,当一个 activity 被 stop 时,系统默认会保存其状态 。这样,当用户再回到这个 activity 时,它就能恢复到原来的状态 。但是,通常情况下,你应该主动地在 activity 的回调方法中保存其状态,防止它切到后台之后,可能因为系统资源不足而被销毁,从而无法恢复状态 。 当一个 activity 被 stop 后,系统可能会在内存资源紧张的情况下把它销毁 。如果发生这样的事,那么这个 activity 的状态也就丢失了 。但是系统并不一定会销毁这个 activity 所在的 back stack ,而且系统也知道该 activity 是属于某个 back stack 的 。但是当 activity 被重新放到 back stack 的顶端的时候,系统要重新创建它,而不是简单地 resume 它 。为了防止丢失用户的工作,你可以实现 activity 的 onSavaInstanceState() 方法,在这个方法中保存用户操作 。
三. 管理 Tasks Android 系统把所有的 activities 放到各自对应的 task 的 back stack 中,这样就能够对它们进行统一地管理 。对于大多数的 app 来说,系统对 activity 的这种默认管理方式已经满足需求了,所以你不用关心 activity 如何与 tasks 交互,也没必要关心它如何被压到 back stack 中 。当然,有时候你可能想打破这种默认的行为 。例如,你可能希望 app 中的某个 activity 在新的 task 中执行,而不是在默认的当前 task 中执行,或者当你启动一个 activity 的时候,你希望把一个已存在的该 activity 的实例重新放到栈顶,而不是在栈顶重新创建一个实例 ,又或者你想在用户离开 task 的时候,把 root activity 之外的 activities 都销毁 ...... 所有这些需求都是可以实现的,你可以在 manifest 的 <activity> 元素中通过相关的属性来进行设置,也可以在传递给 startActivity() 的 intent 中进行设备 。 1. 通过 <activity> 元素进行设置,该元素提供的相关属性大致如下 taskAffinity launchMode allowTaskReparenting clearTaskOnLaunch alwaysRetainTaskState finishOnTaskLaunch 2. Intent 的相关 flags 有 FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_ CLEAR_TOP FLAG_ACTIVITY_SINGLE_TOP 后面会介绍这些属性和 frag 的用法(你可以通过这些属性和 flag 来定义 activity 如何与 tasks 关联,及定义 back stack 的行为)。 提醒:对于大多数的 app 而言,完全没有必要改变 activities 和 tasks 的默认行为,如果你真的有必要这么做,那么请小心处理之 。
四. 定义 Activity 的启动模式(launch modes) 可以通过定义 Activity 的启动模式来决定当它被启动时,对应的 activity 实例与当前的 task 有怎样的关联 。定义 launch modes 的方式有两种: (1) 通过 manifest 文件来定义 在 manifest 文件中声明一个 activity 的时候,可以指定当该 activity 启动的时候,它如何与 tasks 进行关联; (2)通过 Intent 的 flags 来定义 在通过 startActivity() 方法来启动一个 activity 时,可以在其参数 intent 中声明新的 activity 与当前的 tasks 有怎样的关联 。 如 activity A 启动 activity B ,可以在 activity B 的 manifest 声明中指定它的启动模式,也可以在 activity A 的 intent 中声明 activity B 的启动模式 。如果同时定义了这两种方式,那么通过 intent 的设置会起作用,而在 manifest 中的定义就不起作用 。 注意:有些 launch mode 只能通过 manifest 来定义,而不能在 intent 中定义;同样的,有的 launch mode 只能在 intent 中定义,而不能在 manifest 中定义 。
1. 通过 manifest 来定义 launch mode 在 manifest 中声明一个 activity 时,可以定义其 launch mode 。<activity> 元素有一个 launchMode 属性,就是用来定义 launch mode 的 。通过它可以定义四种不同的 launch mode (1)standard(这是默认的 launch mode) activity 的默认 launch mode 就是 standard,在这种模式下,系统会在启动这个 activity 的 task 中直接创建该 activity 的实例并把 intent 传递给它 。activity 可以被多次实例化,每个实例可以属于不同的 tasks ,而且一个 task 可以有多个该 activity 的实例 。 (2)singleTop 如果当前的 task 顶端已经有一个该 activity 的实例,那么系统就会把 intent 直接通过 onNewIntent() 传递给这个实例,而不是重新创建一个该 activity 的实例 。在这种模式下,同一个 activity 也是可以被多次实例化的,每个实例也可以属于不同的 tasks 。而且如果当前的 task 顶端的实例不是要启动的 activity 的实例,那么系统还是会实例化这个 activity ,而不管 task 顶下面有没有该 activity 的实例,即同一个 task 还是有可能存在多个同一 activity 的实例的 。 例如:假设一个 task 中已经有四个 activity 的实例,按顺序从 task 底到 task 顶分别是 A, B, C, D ,即 A 是 root activity ,而 D 是 task 顶的 activity 。这时候有个 intent 来了,它要启动一个 activity D 。如果 activity D 的 launch mode 为 standard ,那么系统将会创建一个新的 activity D,并把它放在当前 task 的顶端,这样 task 中的 activity 实例将变成 A, B, C, D, D 。但是如果 activity D 的 launch mode 为 singleTop ,那么当前 task 顶的 activity D 就会直接接收这个 intent (通过 onNewIntent() 方法),所以该 task 中的 activity 仍保持为 A, B, C, D 。但是如果 intent 要启动的是 activity B,那么由于栈顶的实例不是 activity B 类型,所以系统会启动一个新的 activity B ,这时 task 中的实例就变成 A, B, C, D, B 。 (3)singleTask 这种模式下,系统会实例化 activity 的实例,并把这个实例放到一个独立的新的 task 中。但是如果系统中已有一个独立的 task ,并且该 task 中只有一个该 activity 的实例,那么系统就会直接通过 onNewIntent() 把 intent 传递给这个实例,而不是重新创建一个实例 。所以在系统中,这种模式的 activity 只有一个实例 。 注意:虽然 activity 在新的 task 启动,但是当用户按下回退键时,仍回到之前的 activity 上 。 (4)singleInstance 这种模式的 activity 在系统中也只会有一个实例,当要启动这样的一个 activity 时,系统会先查看系统里面有没有这么一个 activity 实例 ;如果有,那么直接把该实例所在的 task 调到前台,并把该实例所在的 task 上边的 activity 都弹出,从而让该 activity 成为它所在栈的栈顶 ;如果没有,那么系统就创建一个该 activity 的实例 。 例如:Android 的 Browser app 声明为 singleTask ,所以如果你的 app 有个要打开 Android Browser 的 Intent ,那么 Browser 对应的 activity 是不会运行在你的 app 的 task 上的 。系统是这样处理的:如果已经有某个 task 运行了 Browser ,但是它处于后台,那么系统就把这个 task 调到前台并让它处理 intent ;如果没有,那么就启动一个新的 task 来打开 Browser 。 不管新的 activity 是在新的 task 执行或者是在当前的 task 中执行,按下回退键时,总能回到前一个 activity 中 。但是,如果某个 activity 的 launch mode 为 singleTask ,而且它已有一个实例在后台的 task 中,那么要启动这个 activity 时,它所在的 task 就会被调到前台,而在该 task 中的该 activity 下面,可能还有些别的 activity 。这样,当按下回退键时,就会先回到该 task 中的之前的 activity 里面 ,而不是前一个 task 的上一个 activity 里面,下图描述了这种过程:
注意:在 intent 中定义的 launch mode 的优先级比在 manifest 中定义的 launch mode 的优先级高,所以 intent 中的定义会覆盖 manifest 中的定义 。
2. 通过 Intent 的 flags 来定义 launch mode 除了在 manifest 中定义 activity 的 launch mode 之外,你还可以在传递给 startActivity() 的 intent 中定义相应 activity 的 launch mode 。有如下的 flag 可用: (1)FLAG_ACTIVITY_NEW_TASK 在一个新的 task 中启动 activity ,如果已经有这样一个 task ,那么就把它调到前台,并在其 onNewIntent() 方法中接收对应的 intent 。其实这和在 manifest 中定义为 singleTask 是一样的 。 (2)FLAG_ACTIVITY_SINGLE_TOP 和在 mainfest 中定义为 singleTop 一样 。 (3)FLAG_ACTIVITY_CLEAR_TOP 如果要启动的 activity 已经存在于当前的 task 中,那么就把该 activity 之上的所有其它的 activities 都弹出栈并销毁,从而让该 activity 成为当前 task 的栈顶,并把 intent 传递给它,让它恢复 resumed 。注意这种模式在 manifest 的 launchMode 中是没有的,所以也无法通过 manifest 来定义这样的模式 。 FLAG_ACTIVITY_CLEAR_TOP 和 FLAG_ACTIVITY_NEW_TASK 经常联合使用,它们联合使用时,就能够把一个已存在的 activity 放到另一个新的独立的 task 中 ,而且相关 intent 还是会传递给它的 。 注意:如果 intent 中声明启动模式为 FLAG_ACTIVITY_CLEAR_TOP ,并且要启动的 activity 的 launch mode 为 standard ,那么即使该 activity 已经存在并且在栈顶,它也会被销毁,系统会再创建一个新的该 activity 的实例来接收这个 intent 。因为 launch mode 为 standard ,就意味着系统总要创建新的实例来接收新的 intent 。
五. affinities 的作用 默认情况下,一个应用程序中的所有activity都有一个affinity–这让它们属于同一个task。然而,每个activity可以通过<activity>中的taskAffinity属性设置单独的affinity。不同应用程序中的activity可以共享同一个affinity,同一个应用程序中的不同activity也可以设置成不同的affinity。affinity属性在2种情况下起作用:当启动activity的Intent对象包含FLAG_ACTIVITY_NEW_TASK标记,或当activity的allowTaskReparenting被设置成true。 lFLAG_ACTIVITY_NEW_TASK标记

当传递给startActivity()的Intent对象包含FLAG_ACTIVITY_NEW_TASK标记时,系统会为需要启动的activity寻找与当前activity不同的task。如果要启动的activity的affinity属性与当前所有的task的affinity属性都不相同,系统会新建一个带那个affinity属性的task,并将要启动的activity压到新建的task栈中;否则将activity压入那个affinity属性相同的栈中。

allowTaskReparenting属性

如果一个activity的allowTaskReparenting属性为true,那么它可以从一个task(TASK1)移到另外一个有相同affinity的task(TASK2)中(TASK2带到前台时)。


六. 清除 back stack 如果用户离开一个 task 很长时间,那么系统就会把这个 task 中除 root activity 之外的所有 activities 都销毁 。这样用户再回到这个 task 的时候,只有 root activity 被恢复了 。系统这样做是因为用户离开一个 task 的时间太长,很可能是因为他们放弃了当前的操作,而且他们可能也忘了自己所做的操作,所以他们再回到这个 task 的时候,就应该开始新的操作了 。 当然你可以通过一些属性来改变这种默认的行为: (1)alwaysRetainTaskState 如果 root activity 的这个属性设置为 true ,那么上面那种默认的系统行为就不会发生 。系统会让 task 保存所有 activities 的状态,即使用户离开这个 task 的时间很长 。 (2)clearTaskOnLaunch 如果 root activity 的这个属性设置为 true ,那么只要用户离开这个 task ,系统都会清除 root activity 以外的所有 activities,而不管用户离开的时间有多短,该属性和 alwaysRetainTaskState 属性的作用刚好相反 。 (3)finishOnTaskLaunch 这个属性和 clearTaskOnLaunch 属性类似,但是它只是对单独的一个 activity 起作用,即哪个 activity 设置了这个属性,它才对那个 activity 起作用,而不是整个 task 中的所有 activities 。可以对任一 activity 设置这个属性,包括 root activity 。设置了这个属性为 true 的 activity ,只保存与当前会话有关的东西,用户一旦离开这个 task ,该 activity 也就被销毁了 。
七. 启动一个 task 可以通过 intent filter 把一个 activity 设置成整个 task 的起始 activity (root activity),如下
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
为 activity 设置这种类型的 intent filter 后,在 app launcher 中就会有一个代表该 activity 的 icon 和 label 。这样用户就呆以通过点击对应的图标来启动对应的 task ,当这个 task 被切换到后台后,也可以通过这个 icon 返回到这个 task 中 。 有一点非常重要:用户必须能够返回到一个已经打开并被切换到后台的 task 中,所以如果一个 activity 的 launch mode 被设置为 singleTask 或 singleInstance ,那么就应该把它设置成 ACTION_MAIN 和 CATEGORY_LAUNCHER 。想想看,对于 launch mode 为 singleTask 的 activity ,如果不把它设置成 ACTION_MAIN 和 CATEGORY_LAUNCHER ,那么当用户启动它进行一些操作,然后再把它切到后台,那用户就再也没法回到这个它对应的 task 了,因为它总是要在新的 task 中执行 。这显然是不对的,如果你想让用户不能再回到一个 activity ,那么就应该把它的 finishOnTaskLaunch 属性设置为 true 。

更多相关文章

  1. Android之OpenGL里FBO理解测试实例
  2. android activity的4种启动模式
  3. onStartCommand 的返回值
  4. 高通msm8994启动流程简介
  5. Android(安卓)CountDownTimer
  6. 安卓启动流程
  7. Android(安卓)手机Root 原理解析
  8. Task和Activity相关
  9. Android(安卓)开发艺术探索笔记(18)

随机推荐

  1. 谷歌、脸书、魔兽世界都在用!InnoDB是什么
  2. 从1997投稿到2021年被Top5刊接受, 从不惑
  3. 加入其他控制变量后, 估计系数的符号相反
  4. 封禁TikTok?这次美国政府恐怕不能再为所欲
  5. 估计工具变量回归时, 是否必须将所有外生
  6. 如何教AI吃鸡?
  7. 寻找真相, 中国的经济增长真的可以用晋升
  8. 什么是数字孪生体?来自西门子、PTC、北航
  9. CNN、RNN、GAN都是什么?终于有人讲明白了
  10. 阿里粗排技术体系与最新进展