区块链技术博客
www.b2bchain.cn

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈求职学习资料

本文介绍了Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

前言

很高兴见到你!👋

Android 中 Activity 任务,任务栈,返回栈是很重要的概念,正确理解这些概念及其职责对我们开发和理解系统行为有很大的帮助。

但官方文档上关于这部分内容只有一个简单的模型,即使理解了这个模型,仍有小伙伴觉得对这部分内容的理解不够清晰。

造成这种现象我认为有如下原因:

  • 移动操作系统与桌面操作系统的交互逻辑有相似的部分,但也有本质的不同。因此了解移动操作系统的设计理念十分重要。
  • Android 中 Activity 任务,任务栈,返回栈等概念在不同版本没有很大变化,但不同版本对其管理有着较大的不同,这造成仅通过 log 观察现象很难总结出规律,甚至在不同版本上出现了与官方文档描述矛盾的结果
  • 概念较多且稍显复杂,不抓住本质的情况下只能死记硬背各种场景应该出现什么现象。

因此完全掌握这部分内容的最佳方式是:

  • 官方文档仅做参考,建立概念性的认知,理解相关概念存在的意义
  • 阅读相关源码,可以通过打断点,查看 log 等方式理清源码内部的逻辑(不必拘泥于细节)
  • 对比不同版本源码,查看其代码变化,思考做这些改变的原因

这样即使这部分内容的源码又出现了变化,你也可以快速掌握。

本文是 Android Detail 专栏的第一篇分析类文章。

阅读本文,你将了解:

  • Android 多任务的设计理念
  • 不同版本 Activity 任务,返回栈等内容的差异
  • 一些让人困扰的问题,Recents 界面究竟是什么?
  • 阅读源码的方式,如何断点调试系统进程

为了更好的阅读体验,本文将极力避免不负责任的「贴代码」行为。取而代之使用更多动画和示例来阐述相关概念,对于源码相关内容「点到为止」,并介绍阅读相关源码的思路以及方法。如果阅读本文后您能对文中提到的概念有一个较为清晰的认识,并对感兴趣的内容进行深入分析,那么我的目的也就达到了。😉

文章目录一览

  • 前言

  • 前置知识

  • Android 多任务的设计理念

  • 很多小伙伴不熟悉的 Launcher

  • 让我们来设计一个管理 Activity 的结构

  • 不同 Android 版本 ActivityStack 与 TaskRecord 的管理方式

  • 如何管理 Task
    • lauchMode
    • Intent Flag
    • Flag 的状态管理(强烈建议阅读)
    • 你可能不熟悉的 Flag
    • lauchMode 与 Intent Flag 的关系
    • taskAffinity
  • 几个有趣的问题
    • Recents 界面显示的是什么?
    • 按返回键退出应用后,Recent 中还存在?重启设备也存在?
    • 一个 app 可以有几个 ActivityStack?
    • SingleTask 或 SingleInstance 只能有一个实例?
      • 特殊场景
      • 原理
      • 为何如此设计?
    • SingleInstance 一定独处一个 TaskRecord?
  • 阅读相关源码的正确姿势

    • 断点调试
    • 借助 dumpsys 命令
    • 带着问题阅读源码
    • 如果还想更深入的了解
  • 本文 demo 地址

  • 推荐阅读

前置知识

为了更好的阅读体验,阅读本文前您应理解以下问题

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

来自重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈

重学 Android 的读者可移步 重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈 以了解 Activity 任务、任务栈、返回栈的概念,职责以及本质。

Android 多任务的设计理念

移动设备具有台式机或 Web 系统中不存在的技术限制和用户体验要求,因此设计 Android 多任务处理时要遵循的四个关键约束:

  • 我们不希望用户在「完成」时关闭应用程序,在移动设备的环境中,用户的使用过程往往涉及与各种应用程序反复而又短暂的操作
  • 移动设备没有 swap space 的条件。(简单讲就是将硬盘视为虚拟内存,这样在内存空间不够时可将部分内存数据保存到硬盘上,缺点就是慢)
  • 移动设备上应能够快速切换应用,例如我们在看视频时切换到微信,稍后返回视频界面。明显的等待会降低用户体验
  • 所有应用应使用一套可用的 API 开发,all applications are created equal。这意味着无论是系统应用还是第三方应用,在背景音乐播放,数据同步,GPS 导航等必须使用相同的 API

前两个约束产生了「时间」与「空间」的冲突,我们不希望用户关闭应用程序,想让所有应用始终处于运行状态,同时移动设备对内存有着严格的限制。

Android 多任务就是在这种背景下设计的。

在 Android 中,Activity 是一个非常重要的概念。可以这样说:

一个 App 是 它创建的从其它 app 复用的 Activity 集合

👆 划重点

这里分为两部分:

  1. app 自己创建的 Activity

  2. 从其它 app 复用的 Activity

app 自己创建的很好理解,为什么会有从其它 app 复用的 Activity 呢? 这是从用户的连贯性体验考虑而设计的。例如,我们在相册中发现一张好看的图片并想分享给微信里的好友,此时用户点击了分享到微信的按钮,跳转到了微信的「选择联系人」的界面,选择了要发送的好友后并返回到相册。整个过程十分流畅,给用户的感觉就像微信的「选择联系人」和相册 app 是同一个 app 一样。这个微信的「选择联系人」界面所在的 Activity 就是相册 app 从微信复用的 Activity

而照片分享到微信这一动作,是一个原子性操作,被称为一个 Task。

Task 是用户完成特定目标的一组 Activity,它可以移动到前台或后台。Task 内的 Activity 保持着相同的顺序(栈的概念)。

就像上面提到的将相册照片分享到微信中的操作,这就是一个 Task。

而为了更好的用户体验,Android 被设计成多任务(Multitasking),并且其实现逻辑与 iOS 和其它桌面操作系统不同。

ipadOS 目前已支持多任务管理,熟悉 iOS 的小伙伴可以在评论区补充 iOS 中多任务管理的相关内容。

很多小伙伴不熟悉的 Launcher

有部分小伙伴可能不熟悉 Launcher,已经了解的小伙伴可跳过这节。

在这里我简单介绍一些系统 app ,理解这些概念对理解后续的内容很有帮助。

Android 中的桌面(Launcher)是一个 app,不仅如此, systemui(状态栏,通知,Recents),settings(设置)都是 app。

它们和普通 app 没什么区别,为了方便管理,Android 会将 Launcher 和 Recents 放置到一个单独的 Stack 管理。因此我们会有从 Launcher 启动一个 app,点击返回键会回到 Launcher 界面这样的体验。

也许你会发现不同版本 Recents 所属不同,有的版本中它属于 systemui,有的版本中属于 Launcher。是这样的,后文中会有详细介绍。

让我们来设计一个管理 Activity 的结构

那么如果让你设计管理 Activity 的结构,你会怎样设计?

最简单的暴力解法,一把梭哈🤪 ,直接用一个栈管理。例如 List<ActivityRecord> 这种形式:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

一把梭哈

但是使用这种结构我们无法快速地切换应用,那既然每个应用应该是相互独立的,可以抽象个 AppStack 的概念,然后我们用 List<AppStack> 管理,每个 AppStack 内部管理着应用内的所有 ActivityRecord:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

抽象出 AppStack

这样好像满足了基础的使用场景了。但前文我有提到,Android 是被设计为支持多任务的。在桌面操作系统我们会遇到这样的场景,在文件编辑类的软件中,我们可以同时打开多个文件,并能够随时在不同文件间切换,如下图,展示了 mac 上幕布的三个窗口。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

mac 下的多任务

在移动端我们也会有类似的场景,例如 WPS,这里显示了两个文档和主窗口,用户可以快速在这三个窗口间切换。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

WPS 在 Android 中的多任务

既然每个 app 都可能有多个 Task,那么我们可以在 AppStack 的基础上抽象出一个中间层,Task。每个 AppStack 可以多个 Task,同一个应用或不同应用间的 Task 可以快速切换。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

多任务模型

这样我们便设计出一个支持多任务管理 Activity 的结构。

订阅了 重学安卓 小伙伴知道,ActivityRecord 是 Activity 管理的最小单位

在 Android 中,上图的 Activity 对应着源码中的 ActivityRecord,Task 对应着 TaskRecord,AppStack 对应着 ActivityStack

👆 划重点

前面的 AppStack 只是我为了引出概念而设计的,实际上 ActivityStack 与 app 无直接的联系,Android 的多任务管理弱化了 app 的概念,因此小伙伴们最好站在 Task 的维度去理解。

不同 Android 版本 ActivityStack 与 TaskRecord 的管理方式

不同版本的源码对管理 ActivityStack 与 TaskRecord 的方式稍有不同,这也是造成很多小伙伴困扰的原因。

我翻阅了从 6.0 到 10.0 的源码,将其规律总结如下:

  • 6.0:launcher 与 recent(属于 systemui)被分配到 stackId 为 HOME_STACK_ID(值为 0)的 ActivityStack,一般情况下,其它 app 被分配到 stackId 为 1 的 ActivityStack 中。

  • 7.0,7.1,8.0,8.1:ActivityManager.java 中出现了一个 静态内部类 StackId,其内部将 stackId 分为静态 id 与动态 id:

    • INVALID_STACK_ID = -1 无效的 stack id
    • FIRST_STATIC_STACK_ID = 0 第一个静态 stask id
    • HOME_STACK_ID = 0 Home Activity stack id,Launcher 所在 stack
    • FULLSCREEN_WORKSPACE_STACK_ID = 1 全屏 Activity 通常所处的 stack
    • FREEFORM_WORKSPACE_STACK_ID = 2 可调整大小 Activity 通常所处的 stack
    • DOCKED_STACK_ID = 3
    • PINNED_STACK_ID = 4 置顶 stack(可理解为点击 windows 操作系统那个「图钉」按钮)
    • RECENTS_STACK_ID = 5(8.0 加入)Recent Activity所在 stack(6.0 ,7.0 时 与 Launcher 在一个 stack)
    • ASSISTANT_STACK_ID = 6(8.0 加入)
    • LAST_STATIC_STACK_ID = 6
    • FIRST_DYNAMIC_STACK_ID = 7 第一个动态 stack id

    从 0 开始依次递增,第一个动态 id 从 5 开始

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    8.1 ActivityManager#StackId 源码

  • 9.0:移除 StackId 中的所有属性和方法,只保留 INVALID_STACK_ID

  • 10.0:移除 StackId 静态内部类

在 9.0 及以后,app 在创建 TaskRecord 时会创建一个新的 ActivityStack 作为新 Task 的容器,那么真实的结构可以这样表示:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

9.0 及以后模型

我们在使用的桌面操作系统是支持多屏幕输出的,Android 也是支持的。为此加入了 Display 的概念,源码中对应着 ActivityDisplay。默认情况下全局只有一个 ActivityDisplay

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

多屏幕输出模型

有的小伙伴可能知道,Android 中还有一个 ActivityStackSupervisor 的概念,望文知义,即 ActivityStack 的管理者。ActivityStackSupervisor 就像创业公司的主管,十分全能,任劳任怨。随着公司的发展,ActivityStackSupervisor 的责任越来越重,于是便将自己的工作分别交与特定的同事去做,自己只做全局统筹规划(高薪摸鱼 🤓)。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

ActivityStackSupervisor 逻辑抽离 commit log

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

ActivityStackSupervisor 头部注释

  • 将有关层次结构的内容移至 RootWindowContainer
  • 将有关 Activity 生命周期的内容移至一个大概叫 ActivityLifeCycler 的类中
  • 将接口类的内容移至 ActivityTaskManagerService
  • 剩余的内容移至其它文件

这是一个相对长期的计划,目前大部分有关层次结构的内容被移至 RootActivityContainer ,不过它也是一个临时工,未来会和 RootWindowContainer 合并。

有时候在阅读源码时会觉得很难看懂,这时候不要轻易怀疑自己的能力,因为官方源码有很多待改进的地方 😆

相较于传统的 ActivityStackSupervisor,新版本(9.0 后)引入了 Container 的概念,并使用 RootActivityContainer 接替了 ActivityStackSupervisor 之前有关 Activity 层次结构的管理工作。在 Android 9 和 Android 10 的设备上,我们可以使用 adb shell dumpsys activity containers 命令来查看Activity 的层次结构。

Android 11 这部分内容做了较大更改,待开放源码后再对这部分内容进行补充。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

adb shell dumpsys activity containers (9.0及之后支持)

为什么同时存在栈和 Task 的设计?

这是很多开发者的疑惑,为什么会存在栈和 Task 的设计?

前文我们介绍了 Android 多任务的设计理念。因此我们很容易理解 Task 的设计。

无论是官方文档还是网上众多的博客都在讲返回栈或者任务栈的概念,其实这些概念很抽象,而且我认为没必要一定要将这些概念对应到源码上,例如 xxx 类是返回栈,xxx 是任务栈。

不妨从几个方向理解栈的设计:

  • Task 内的每个界面(ActivityRecord)被一个栈的结构管理,它有着严格的「后进先出」的顺序,每个 ActivityRecord 之间的顺序无法改变。(这也比较符合用户直觉)

    其实桌面操作系统也有类似的概念,例如 windows 的文件管理器,每个界面有着严格的「后进先出」的顺序,当该任务所有界面都关闭后,底部的 Task 便显示出来。

    而有时候我们想改变 逐个入栈和出栈 的行为,因此引入 FLAG_ACTIVITY_CLEAR_TOP 来控制批量出栈以及使用 launchMode 来控制 ActivityRecord 的复用。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    Windows Files

    这个行为可以理解为 任务栈。虽然是一个栈的概念,但源码中, TaskRecord 中是通过 final ArrayList<ActivityRecord> mActivities; 这种形式持有其内部的 ActivityRecord 的。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    TaskRecord 模型

  • Task 和 Task 之间也有栈的概念,比如展示给用户的 Task 被关闭后,其下层的 Task 便会展示给用户,但 Task 之间的顺序可以改变,例如某个 Task 可以从中间位置切换到前台。

    Android 中使用 ActivityStack 表示这种概念,而且为了方便处理 Launcher,将其单独放置在一个 ActivityStack 中,其它 TaskRecord 放置到另一个 ActivityStack 中。7.0 后又对 ActivityStask 进行细化,分成了静态 Stack 和动态 Stack。9.0 开始索性放开 ActivityStack 的限制,通过创建新的 ActivityStack 来管理新创建的 TaskRecord。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    9.0 之前模型

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    9.0 及之后模型

了解这些,Android 为我们提供管理 Task 的方式便很容易理解了。

如何管理 Task

前面我们反复强调 Android 是基于多任务的理念设计的,因此在管理 Activity 时我们还要关心 Activity 如何与 Task 关联。例如在启动某个 Activity 时我们希望它被放到一个新的 Task 中;或者在启动某个 Activity 时,我们希望使用它的已存在的实例,而不是直接创建一个新的;亦或者我们希望在用户离开 Task 后清除所有 Activity。

从使用形式上看, Android 为开发者提供了两种管理 Task 的方式:

  • 使用 Manifest 中 <activity> 标签中的属性来管理
    • taskAffinity
    • launchMode
    • allowTaskReparenting
    • clearTaskOnLaunch
    • alwaysRetainTaskState
    • finishOnTaskLaunch
  • 在调用 startActivity() 时通过配置 intent 中的 flag 来管理
    • FLAG_ACTIVITY_NEW_TASK
    • FLAG_ACTIVITY_CLEAR_TOP
    • FLAG_ACTIVITY_SINGLE_TOP

launchMode

为了解决 Activity 在 Task 中的复用问题,Android 设计了 launchMode。

对于不同 launchMode 是基于何种场景设计的内容, 重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈 中已经介绍,此处不再赘述。

启动模式是 standardsingleTop 的 Activity 可以被多次实例化,其实例可以属于任何 Task,而且可以在栈中的任何位置。

通常情况下它们会被放入调用 startActivity 所在的 Task 中(除非 Intent 中包含 FLAG_ACTIVITY_NEW_TASK 的 flag,此种情况下与 taskAffinity 的配置有关)。

启动模式是 singleTasksingleInstance 的 Activity 只能被实例化一次(销毁后再次打开并创建不计,这里侧重同一时间只能有一个实例),换句话说,全局一次只会有一个这样的 Activity 实例(虽然官方文档如此描述,但该行为有例外)。singleTasksingleInstance 的唯一区别是 singleTask 允许其他 Activity 与其共享 Task,而 singleInstance 是其 Task 的唯一成员(官方文档如此描述,但该行为有例外,后文有提及)。

这两种 launchMode 在源码中会自动添加 FLAG_ACTIVITY_NEW_TASK,因此这种模式启动的 Activity 应处于哪个 Task 也与 taskAffinity 的配置有关。

else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {     // 位运算看不懂?后文会讲     mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; }

Intent Flag

除了 lauchMode,Android 还允许在调用 startActivity 时配置 Intent Flag 来对任务进行管理。

Flag 的状态管理

Android 中 Intent Flag 被定义为 16 进制数

public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000; public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000; public static final int FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000; public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000; public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000; //省略...

使用 16 进制数配合位运算做状态管理的缘由以及优势见 就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳实践!

👆 强烈建议阅读,不仅对阅读源码有很大帮助,而且在开发过程中借助 16 进制数配合位运算处理多状态管理会十分简洁高效!

这里举例源码中常见的操作,以 Intent Flag 为例:

  • 使用 | 运算添加 flag,例如上文中的 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;,为 mLaunchFlags 添加 flag

  • 使用 & 运算 和 ~ 运算配合使用以移除 flag,例如 launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK

前言

很高兴见到你!👋

Android 中 Activity 任务,任务栈,返回栈是很重要的概念,正确理解这些概念及其职责对我们开发和理解系统行为有很大的帮助。

但官方文档上关于这部分内容只有一个简单的模型,即使理解了这个模型,仍有小伙伴觉得对这部分内容的理解不够清晰。

造成这种现象我认为有如下原因:

  • 移动操作系统与桌面操作系统的交互逻辑有相似的部分,但也有本质的不同。因此了解移动操作系统的设计理念十分重要。
  • Android 中 Activity 任务,任务栈,返回栈等概念在不同版本没有很大变化,但不同版本对其管理有着较大的不同,这造成仅通过 log 观察现象很难总结出规律,甚至在不同版本上出现了与官方文档描述矛盾的结果
  • 概念较多且稍显复杂,不抓住本质的情况下只能死记硬背各种场景应该出现什么现象。

因此完全掌握这部分内容的最佳方式是:

  • 官方文档仅做参考,建立概念性的认知,理解相关概念存在的意义
  • 阅读相关源码,可以通过打断点,查看 log 等方式理清源码内部的逻辑(不必拘泥于细节)
  • 对比不同版本源码,查看其代码变化,思考做这些改变的原因

这样即使这部分内容的源码又出现了变化,你也可以快速掌握。

本文是 Android Detail 专栏的第一篇分析类文章。

阅读本文,你将了解:

  • Android 多任务的设计理念
  • 不同版本 Activity 任务,返回栈等内容的差异
  • 一些让人困扰的问题,Recents 界面究竟是什么?
  • 阅读源码的方式,如何断点调试系统进程

为了更好的阅读体验,本文将极力避免不负责任的「贴代码」行为。取而代之使用更多动画和示例来阐述相关概念,对于源码相关内容「点到为止」,并介绍阅读相关源码的思路以及方法。如果阅读本文后您能对文中提到的概念有一个较为清晰的认识,并对感兴趣的内容进行深入分析,那么我的目的也就达到了。😉

文章目录一览

  • 前言

  • 前置知识

  • Android 多任务的设计理念

  • 很多小伙伴不熟悉的 Launcher

  • 让我们来设计一个管理 Activity 的结构

  • 不同 Android 版本 ActivityStack 与 TaskRecord 的管理方式

  • 如何管理 Task
    • lauchMode
    • Intent Flag
    • Flag 的状态管理(强烈建议阅读)
    • 你可能不熟悉的 Flag
    • lauchMode 与 Intent Flag 的关系
    • taskAffinity
  • 几个有趣的问题
    • Recents 界面显示的是什么?
    • 按返回键退出应用后,Recent 中还存在?重启设备也存在?
    • 一个 app 可以有几个 ActivityStack?
    • SingleTask 或 SingleInstance 只能有一个实例?
      • 特殊场景
      • 原理
      • 为何如此设计?
    • SingleInstance 一定独处一个 TaskRecord?
  • 阅读相关源码的正确姿势

    • 断点调试
    • 借助 dumpsys 命令
    • 带着问题阅读源码
    • 如果还想更深入的了解
  • 本文 demo 地址

  • 推荐阅读

前置知识

为了更好的阅读体验,阅读本文前您应理解以下问题

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

来自重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈

重学 Android 的读者可移步 重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈 以了解 Activity 任务、任务栈、返回栈的概念,职责以及本质。

Android 多任务的设计理念

移动设备具有台式机或 Web 系统中不存在的技术限制和用户体验要求,因此设计 Android 多任务处理时要遵循的四个关键约束:

  • 我们不希望用户在「完成」时关闭应用程序,在移动设备的环境中,用户的使用过程往往涉及与各种应用程序反复而又短暂的操作
  • 移动设备没有 swap space 的条件。(简单讲就是将硬盘视为虚拟内存,这样在内存空间不够时可将部分内存数据保存到硬盘上,缺点就是慢)
  • 移动设备上应能够快速切换应用,例如我们在看视频时切换到微信,稍后返回视频界面。明显的等待会降低用户体验
  • 所有应用应使用一套可用的 API 开发,all applications are created equal。这意味着无论是系统应用还是第三方应用,在背景音乐播放,数据同步,GPS 导航等必须使用相同的 API

前两个约束产生了「时间」与「空间」的冲突,我们不希望用户关闭应用程序,想让所有应用始终处于运行状态,同时移动设备对内存有着严格的限制。

Android 多任务就是在这种背景下设计的。

在 Android 中,Activity 是一个非常重要的概念。可以这样说:

一个 App 是 它创建的从其它 app 复用的 Activity 集合

👆 划重点

这里分为两部分:

  1. app 自己创建的 Activity

  2. 从其它 app 复用的 Activity

app 自己创建的很好理解,为什么会有从其它 app 复用的 Activity 呢? 这是从用户的连贯性体验考虑而设计的。例如,我们在相册中发现一张好看的图片并想分享给微信里的好友,此时用户点击了分享到微信的按钮,跳转到了微信的「选择联系人」的界面,选择了要发送的好友后并返回到相册。整个过程十分流畅,给用户的感觉就像微信的「选择联系人」和相册 app 是同一个 app 一样。这个微信的「选择联系人」界面所在的 Activity 就是相册 app 从微信复用的 Activity

而照片分享到微信这一动作,是一个原子性操作,被称为一个 Task。

Task 是用户完成特定目标的一组 Activity,它可以移动到前台或后台。Task 内的 Activity 保持着相同的顺序(栈的概念)。

就像上面提到的将相册照片分享到微信中的操作,这就是一个 Task。

而为了更好的用户体验,Android 被设计成多任务(Multitasking),并且其实现逻辑与 iOS 和其它桌面操作系统不同。

ipadOS 目前已支持多任务管理,熟悉 iOS 的小伙伴可以在评论区补充 iOS 中多任务管理的相关内容。

很多小伙伴不熟悉的 Launcher

有部分小伙伴可能不熟悉 Launcher,已经了解的小伙伴可跳过这节。

在这里我简单介绍一些系统 app ,理解这些概念对理解后续的内容很有帮助。

Android 中的桌面(Launcher)是一个 app,不仅如此, systemui(状态栏,通知,Recents),settings(设置)都是 app。

它们和普通 app 没什么区别,为了方便管理,Android 会将 Launcher 和 Recents 放置到一个单独的 Stack 管理。因此我们会有从 Launcher 启动一个 app,点击返回键会回到 Launcher 界面这样的体验。

也许你会发现不同版本 Recents 所属不同,有的版本中它属于 systemui,有的版本中属于 Launcher。是这样的,后文中会有详细介绍。

让我们来设计一个管理 Activity 的结构

那么如果让你设计管理 Activity 的结构,你会怎样设计?

最简单的暴力解法,一把梭哈🤪 ,直接用一个栈管理。例如 List<ActivityRecord> 这种形式:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

一把梭哈

但是使用这种结构我们无法快速地切换应用,那既然每个应用应该是相互独立的,可以抽象个 AppStack 的概念,然后我们用 List<AppStack> 管理,每个 AppStack 内部管理着应用内的所有 ActivityRecord:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

抽象出 AppStack

这样好像满足了基础的使用场景了。但前文我有提到,Android 是被设计为支持多任务的。在桌面操作系统我们会遇到这样的场景,在文件编辑类的软件中,我们可以同时打开多个文件,并能够随时在不同文件间切换,如下图,展示了 mac 上幕布的三个窗口。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

mac 下的多任务

在移动端我们也会有类似的场景,例如 WPS,这里显示了两个文档和主窗口,用户可以快速在这三个窗口间切换。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

WPS 在 Android 中的多任务

既然每个 app 都可能有多个 Task,那么我们可以在 AppStack 的基础上抽象出一个中间层,Task。每个 AppStack 可以多个 Task,同一个应用或不同应用间的 Task 可以快速切换。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

多任务模型

这样我们便设计出一个支持多任务管理 Activity 的结构。

订阅了 重学安卓 小伙伴知道,ActivityRecord 是 Activity 管理的最小单位

在 Android 中,上图的 Activity 对应着源码中的 ActivityRecord,Task 对应着 TaskRecord,AppStack 对应着 ActivityStack

👆 划重点

前面的 AppStack 只是我为了引出概念而设计的,实际上 ActivityStack 与 app 无直接的联系,Android 的多任务管理弱化了 app 的概念,因此小伙伴们最好站在 Task 的维度去理解。

不同 Android 版本 ActivityStack 与 TaskRecord 的管理方式

不同版本的源码对管理 ActivityStack 与 TaskRecord 的方式稍有不同,这也是造成很多小伙伴困扰的原因。

我翻阅了从 6.0 到 10.0 的源码,将其规律总结如下:

  • 6.0:launcher 与 recent(属于 systemui)被分配到 stackId 为 HOME_STACK_ID(值为 0)的 ActivityStack,一般情况下,其它 app 被分配到 stackId 为 1 的 ActivityStack 中。

  • 7.0,7.1,8.0,8.1:ActivityManager.java 中出现了一个 静态内部类 StackId,其内部将 stackId 分为静态 id 与动态 id:

    • INVALID_STACK_ID = -1 无效的 stack id
    • FIRST_STATIC_STACK_ID = 0 第一个静态 stask id
    • HOME_STACK_ID = 0 Home Activity stack id,Launcher 所在 stack
    • FULLSCREEN_WORKSPACE_STACK_ID = 1 全屏 Activity 通常所处的 stack
    • FREEFORM_WORKSPACE_STACK_ID = 2 可调整大小 Activity 通常所处的 stack
    • DOCKED_STACK_ID = 3
    • PINNED_STACK_ID = 4 置顶 stack(可理解为点击 windows 操作系统那个「图钉」按钮)
    • RECENTS_STACK_ID = 5(8.0 加入)Recent Activity所在 stack(6.0 ,7.0 时 与 Launcher 在一个 stack)
    • ASSISTANT_STACK_ID = 6(8.0 加入)
    • LAST_STATIC_STACK_ID = 6
    • FIRST_DYNAMIC_STACK_ID = 7 第一个动态 stack id

    从 0 开始依次递增,第一个动态 id 从 5 开始

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    8.1 ActivityManager#StackId 源码

  • 9.0:移除 StackId 中的所有属性和方法,只保留 INVALID_STACK_ID

  • 10.0:移除 StackId 静态内部类

在 9.0 及以后,app 在创建 TaskRecord 时会创建一个新的 ActivityStack 作为新 Task 的容器,那么真实的结构可以这样表示:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

9.0 及以后模型

我们在使用的桌面操作系统是支持多屏幕输出的,Android 也是支持的。为此加入了 Display 的概念,源码中对应着 ActivityDisplay。默认情况下全局只有一个 ActivityDisplay

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

多屏幕输出模型

有的小伙伴可能知道,Android 中还有一个 ActivityStackSupervisor 的概念,望文知义,即 ActivityStack 的管理者。ActivityStackSupervisor 就像创业公司的主管,十分全能,任劳任怨。随着公司的发展,ActivityStackSupervisor 的责任越来越重,于是便将自己的工作分别交与特定的同事去做,自己只做全局统筹规划(高薪摸鱼 🤓)。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

ActivityStackSupervisor 逻辑抽离 commit log

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

ActivityStackSupervisor 头部注释

  • 将有关层次结构的内容移至 RootWindowContainer
  • 将有关 Activity 生命周期的内容移至一个大概叫 ActivityLifeCycler 的类中
  • 将接口类的内容移至 ActivityTaskManagerService
  • 剩余的内容移至其它文件

这是一个相对长期的计划,目前大部分有关层次结构的内容被移至 RootActivityContainer ,不过它也是一个临时工,未来会和 RootWindowContainer 合并。

有时候在阅读源码时会觉得很难看懂,这时候不要轻易怀疑自己的能力,因为官方源码有很多待改进的地方 😆

相较于传统的 ActivityStackSupervisor,新版本(9.0 后)引入了 Container 的概念,并使用 RootActivityContainer 接替了 ActivityStackSupervisor 之前有关 Activity 层次结构的管理工作。在 Android 9 和 Android 10 的设备上,我们可以使用 adb shell dumpsys activity containers 命令来查看Activity 的层次结构。

Android 11 这部分内容做了较大更改,待开放源码后再对这部分内容进行补充。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

adb shell dumpsys activity containers (9.0及之后支持)

为什么同时存在栈和 Task 的设计?

这是很多开发者的疑惑,为什么会存在栈和 Task 的设计?

前文我们介绍了 Android 多任务的设计理念。因此我们很容易理解 Task 的设计。

无论是官方文档还是网上众多的博客都在讲返回栈或者任务栈的概念,其实这些概念很抽象,而且我认为没必要一定要将这些概念对应到源码上,例如 xxx 类是返回栈,xxx 是任务栈。

不妨从几个方向理解栈的设计:

  • Task 内的每个界面(ActivityRecord)被一个栈的结构管理,它有着严格的「后进先出」的顺序,每个 ActivityRecord 之间的顺序无法改变。(这也比较符合用户直觉)

    其实桌面操作系统也有类似的概念,例如 windows 的文件管理器,每个界面有着严格的「后进先出」的顺序,当该任务所有界面都关闭后,底部的 Task 便显示出来。

    而有时候我们想改变 逐个入栈和出栈 的行为,因此引入 FLAG_ACTIVITY_CLEAR_TOP 来控制批量出栈以及使用 launchMode 来控制 ActivityRecord 的复用。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    Windows Files

    这个行为可以理解为 任务栈。虽然是一个栈的概念,但源码中, TaskRecord 中是通过 final ArrayList<ActivityRecord> mActivities; 这种形式持有其内部的 ActivityRecord 的。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    TaskRecord 模型

  • Task 和 Task 之间也有栈的概念,比如展示给用户的 Task 被关闭后,其下层的 Task 便会展示给用户,但 Task 之间的顺序可以改变,例如某个 Task 可以从中间位置切换到前台。

    Android 中使用 ActivityStack 表示这种概念,而且为了方便处理 Launcher,将其单独放置在一个 ActivityStack 中,其它 TaskRecord 放置到另一个 ActivityStack 中。7.0 后又对 ActivityStask 进行细化,分成了静态 Stack 和动态 Stack。9.0 开始索性放开 ActivityStack 的限制,通过创建新的 ActivityStack 来管理新创建的 TaskRecord。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    9.0 之前模型

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    9.0 及之后模型

了解这些,Android 为我们提供管理 Task 的方式便很容易理解了。

如何管理 Task

前面我们反复强调 Android 是基于多任务的理念设计的,因此在管理 Activity 时我们还要关心 Activity 如何与 Task 关联。例如在启动某个 Activity 时我们希望它被放到一个新的 Task 中;或者在启动某个 Activity 时,我们希望使用它的已存在的实例,而不是直接创建一个新的;亦或者我们希望在用户离开 Task 后清除所有 Activity。

从使用形式上看, Android 为开发者提供了两种管理 Task 的方式:

  • 使用 Manifest 中 <activity> 标签中的属性来管理
    • taskAffinity
    • launchMode
    • allowTaskReparenting
    • clearTaskOnLaunch
    • alwaysRetainTaskState
    • finishOnTaskLaunch
  • 在调用 startActivity() 时通过配置 intent 中的 flag 来管理
    • FLAG_ACTIVITY_NEW_TASK
    • FLAG_ACTIVITY_CLEAR_TOP
    • FLAG_ACTIVITY_SINGLE_TOP

launchMode

为了解决 Activity 在 Task 中的复用问题,Android 设计了 launchMode。

对于不同 launchMode 是基于何种场景设计的内容, 重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈 中已经介绍,此处不再赘述。

启动模式是 standardsingleTop 的 Activity 可以被多次实例化,其实例可以属于任何 Task,而且可以在栈中的任何位置。

通常情况下它们会被放入调用 startActivity 所在的 Task 中(除非 Intent 中包含 FLAG_ACTIVITY_NEW_TASK 的 flag,此种情况下与 taskAffinity 的配置有关)。

启动模式是 singleTasksingleInstance 的 Activity 只能被实例化一次(销毁后再次打开并创建不计,这里侧重同一时间只能有一个实例),换句话说,全局一次只会有一个这样的 Activity 实例(虽然官方文档如此描述,但该行为有例外)。singleTasksingleInstance 的唯一区别是 singleTask 允许其他 Activity 与其共享 Task,而 singleInstance 是其 Task 的唯一成员(官方文档如此描述,但该行为有例外,后文有提及)。

这两种 launchMode 在源码中会自动添加 FLAG_ACTIVITY_NEW_TASK,因此这种模式启动的 Activity 应处于哪个 Task 也与 taskAffinity 的配置有关。

else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {     // 位运算看不懂?后文会讲     mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; }

Intent Flag

除了 lauchMode,Android 还允许在调用 startActivity 时配置 Intent Flag 来对任务进行管理。

Flag 的状态管理

Android 中 Intent Flag 被定义为 16 进制数

public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000; public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000; public static final int FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000; public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000; public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000; //省略...

使用 16 进制数配合位运算做状态管理的缘由以及优势见 就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳实践!

👆 强烈建议阅读,不仅对阅读源码有很大帮助,而且在开发过程中借助 16 进制数配合位运算处理多状态管理会十分简洁高效!

这里举例源码中常见的操作,以 Intent Flag 为例:

  • 使用 | 运算添加 flag,例如上文中的 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;,为 mLaunchFlags 添加 flag

  • 使用 & 运算 和 ~ 运算配合使用以移除 flag,例如 launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK

前言

很高兴见到你!👋

Android 中 Activity 任务,任务栈,返回栈是很重要的概念,正确理解这些概念及其职责对我们开发和理解系统行为有很大的帮助。

但官方文档上关于这部分内容只有一个简单的模型,即使理解了这个模型,仍有小伙伴觉得对这部分内容的理解不够清晰。

造成这种现象我认为有如下原因:

  • 移动操作系统与桌面操作系统的交互逻辑有相似的部分,但也有本质的不同。因此了解移动操作系统的设计理念十分重要。
  • Android 中 Activity 任务,任务栈,返回栈等概念在不同版本没有很大变化,但不同版本对其管理有着较大的不同,这造成仅通过 log 观察现象很难总结出规律,甚至在不同版本上出现了与官方文档描述矛盾的结果
  • 概念较多且稍显复杂,不抓住本质的情况下只能死记硬背各种场景应该出现什么现象。

因此完全掌握这部分内容的最佳方式是:

  • 官方文档仅做参考,建立概念性的认知,理解相关概念存在的意义
  • 阅读相关源码,可以通过打断点,查看 log 等方式理清源码内部的逻辑(不必拘泥于细节)
  • 对比不同版本源码,查看其代码变化,思考做这些改变的原因

这样即使这部分内容的源码又出现了变化,你也可以快速掌握。

本文是 Android Detail 专栏的第一篇分析类文章。

阅读本文,你将了解:

  • Android 多任务的设计理念
  • 不同版本 Activity 任务,返回栈等内容的差异
  • 一些让人困扰的问题,Recents 界面究竟是什么?
  • 阅读源码的方式,如何断点调试系统进程

为了更好的阅读体验,本文将极力避免不负责任的「贴代码」行为。取而代之使用更多动画和示例来阐述相关概念,对于源码相关内容「点到为止」,并介绍阅读相关源码的思路以及方法。如果阅读本文后您能对文中提到的概念有一个较为清晰的认识,并对感兴趣的内容进行深入分析,那么我的目的也就达到了。😉

文章目录一览

  • 前言

  • 前置知识

  • Android 多任务的设计理念

  • 很多小伙伴不熟悉的 Launcher

  • 让我们来设计一个管理 Activity 的结构

  • 不同 Android 版本 ActivityStack 与 TaskRecord 的管理方式

  • 如何管理 Task
    • lauchMode
    • Intent Flag
    • Flag 的状态管理(强烈建议阅读)
    • 你可能不熟悉的 Flag
    • lauchMode 与 Intent Flag 的关系
    • taskAffinity
  • 几个有趣的问题
    • Recents 界面显示的是什么?
    • 按返回键退出应用后,Recent 中还存在?重启设备也存在?
    • 一个 app 可以有几个 ActivityStack?
    • SingleTask 或 SingleInstance 只能有一个实例?
      • 特殊场景
      • 原理
      • 为何如此设计?
    • SingleInstance 一定独处一个 TaskRecord?
  • 阅读相关源码的正确姿势

    • 断点调试
    • 借助 dumpsys 命令
    • 带着问题阅读源码
    • 如果还想更深入的了解
  • 本文 demo 地址

  • 推荐阅读

前置知识

为了更好的阅读体验,阅读本文前您应理解以下问题

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

来自重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈

重学 Android 的读者可移步 重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈 以了解 Activity 任务、任务栈、返回栈的概念,职责以及本质。

Android 多任务的设计理念

移动设备具有台式机或 Web 系统中不存在的技术限制和用户体验要求,因此设计 Android 多任务处理时要遵循的四个关键约束:

  • 我们不希望用户在「完成」时关闭应用程序,在移动设备的环境中,用户的使用过程往往涉及与各种应用程序反复而又短暂的操作
  • 移动设备没有 swap space 的条件。(简单讲就是将硬盘视为虚拟内存,这样在内存空间不够时可将部分内存数据保存到硬盘上,缺点就是慢)
  • 移动设备上应能够快速切换应用,例如我们在看视频时切换到微信,稍后返回视频界面。明显的等待会降低用户体验
  • 所有应用应使用一套可用的 API 开发,all applications are created equal。这意味着无论是系统应用还是第三方应用,在背景音乐播放,数据同步,GPS 导航等必须使用相同的 API

前两个约束产生了「时间」与「空间」的冲突,我们不希望用户关闭应用程序,想让所有应用始终处于运行状态,同时移动设备对内存有着严格的限制。

Android 多任务就是在这种背景下设计的。

在 Android 中,Activity 是一个非常重要的概念。可以这样说:

一个 App 是 它创建的从其它 app 复用的 Activity 集合

👆 划重点

这里分为两部分:

  1. app 自己创建的 Activity

  2. 从其它 app 复用的 Activity

app 自己创建的很好理解,为什么会有从其它 app 复用的 Activity 呢? 这是从用户的连贯性体验考虑而设计的。例如,我们在相册中发现一张好看的图片并想分享给微信里的好友,此时用户点击了分享到微信的按钮,跳转到了微信的「选择联系人」的界面,选择了要发送的好友后并返回到相册。整个过程十分流畅,给用户的感觉就像微信的「选择联系人」和相册 app 是同一个 app 一样。这个微信的「选择联系人」界面所在的 Activity 就是相册 app 从微信复用的 Activity

而照片分享到微信这一动作,是一个原子性操作,被称为一个 Task。

Task 是用户完成特定目标的一组 Activity,它可以移动到前台或后台。Task 内的 Activity 保持着相同的顺序(栈的概念)。

就像上面提到的将相册照片分享到微信中的操作,这就是一个 Task。

而为了更好的用户体验,Android 被设计成多任务(Multitasking),并且其实现逻辑与 iOS 和其它桌面操作系统不同。

ipadOS 目前已支持多任务管理,熟悉 iOS 的小伙伴可以在评论区补充 iOS 中多任务管理的相关内容。

很多小伙伴不熟悉的 Launcher

有部分小伙伴可能不熟悉 Launcher,已经了解的小伙伴可跳过这节。

在这里我简单介绍一些系统 app ,理解这些概念对理解后续的内容很有帮助。

Android 中的桌面(Launcher)是一个 app,不仅如此, systemui(状态栏,通知,Recents),settings(设置)都是 app。

它们和普通 app 没什么区别,为了方便管理,Android 会将 Launcher 和 Recents 放置到一个单独的 Stack 管理。因此我们会有从 Launcher 启动一个 app,点击返回键会回到 Launcher 界面这样的体验。

也许你会发现不同版本 Recents 所属不同,有的版本中它属于 systemui,有的版本中属于 Launcher。是这样的,后文中会有详细介绍。

让我们来设计一个管理 Activity 的结构

那么如果让你设计管理 Activity 的结构,你会怎样设计?

最简单的暴力解法,一把梭哈🤪 ,直接用一个栈管理。例如 List<ActivityRecord> 这种形式:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

一把梭哈

但是使用这种结构我们无法快速地切换应用,那既然每个应用应该是相互独立的,可以抽象个 AppStack 的概念,然后我们用 List<AppStack> 管理,每个 AppStack 内部管理着应用内的所有 ActivityRecord:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

抽象出 AppStack

这样好像满足了基础的使用场景了。但前文我有提到,Android 是被设计为支持多任务的。在桌面操作系统我们会遇到这样的场景,在文件编辑类的软件中,我们可以同时打开多个文件,并能够随时在不同文件间切换,如下图,展示了 mac 上幕布的三个窗口。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

mac 下的多任务

在移动端我们也会有类似的场景,例如 WPS,这里显示了两个文档和主窗口,用户可以快速在这三个窗口间切换。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

WPS 在 Android 中的多任务

既然每个 app 都可能有多个 Task,那么我们可以在 AppStack 的基础上抽象出一个中间层,Task。每个 AppStack 可以多个 Task,同一个应用或不同应用间的 Task 可以快速切换。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

多任务模型

这样我们便设计出一个支持多任务管理 Activity 的结构。

订阅了 重学安卓 小伙伴知道,ActivityRecord 是 Activity 管理的最小单位

在 Android 中,上图的 Activity 对应着源码中的 ActivityRecord,Task 对应着 TaskRecord,AppStack 对应着 ActivityStack

👆 划重点

前面的 AppStack 只是我为了引出概念而设计的,实际上 ActivityStack 与 app 无直接的联系,Android 的多任务管理弱化了 app 的概念,因此小伙伴们最好站在 Task 的维度去理解。

不同 Android 版本 ActivityStack 与 TaskRecord 的管理方式

不同版本的源码对管理 ActivityStack 与 TaskRecord 的方式稍有不同,这也是造成很多小伙伴困扰的原因。

我翻阅了从 6.0 到 10.0 的源码,将其规律总结如下:

  • 6.0:launcher 与 recent(属于 systemui)被分配到 stackId 为 HOME_STACK_ID(值为 0)的 ActivityStack,一般情况下,其它 app 被分配到 stackId 为 1 的 ActivityStack 中。

  • 7.0,7.1,8.0,8.1:ActivityManager.java 中出现了一个 静态内部类 StackId,其内部将 stackId 分为静态 id 与动态 id:

    • INVALID_STACK_ID = -1 无效的 stack id
    • FIRST_STATIC_STACK_ID = 0 第一个静态 stask id
    • HOME_STACK_ID = 0 Home Activity stack id,Launcher 所在 stack
    • FULLSCREEN_WORKSPACE_STACK_ID = 1 全屏 Activity 通常所处的 stack
    • FREEFORM_WORKSPACE_STACK_ID = 2 可调整大小 Activity 通常所处的 stack
    • DOCKED_STACK_ID = 3
    • PINNED_STACK_ID = 4 置顶 stack(可理解为点击 windows 操作系统那个「图钉」按钮)
    • RECENTS_STACK_ID = 5(8.0 加入)Recent Activity所在 stack(6.0 ,7.0 时 与 Launcher 在一个 stack)
    • ASSISTANT_STACK_ID = 6(8.0 加入)
    • LAST_STATIC_STACK_ID = 6
    • FIRST_DYNAMIC_STACK_ID = 7 第一个动态 stack id

    从 0 开始依次递增,第一个动态 id 从 5 开始

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    8.1 ActivityManager#StackId 源码

  • 9.0:移除 StackId 中的所有属性和方法,只保留 INVALID_STACK_ID

  • 10.0:移除 StackId 静态内部类

在 9.0 及以后,app 在创建 TaskRecord 时会创建一个新的 ActivityStack 作为新 Task 的容器,那么真实的结构可以这样表示:

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

9.0 及以后模型

我们在使用的桌面操作系统是支持多屏幕输出的,Android 也是支持的。为此加入了 Display 的概念,源码中对应着 ActivityDisplay。默认情况下全局只有一个 ActivityDisplay

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

多屏幕输出模型

有的小伙伴可能知道,Android 中还有一个 ActivityStackSupervisor 的概念,望文知义,即 ActivityStack 的管理者。ActivityStackSupervisor 就像创业公司的主管,十分全能,任劳任怨。随着公司的发展,ActivityStackSupervisor 的责任越来越重,于是便将自己的工作分别交与特定的同事去做,自己只做全局统筹规划(高薪摸鱼 🤓)。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

ActivityStackSupervisor 逻辑抽离 commit log

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

ActivityStackSupervisor 头部注释

  • 将有关层次结构的内容移至 RootWindowContainer
  • 将有关 Activity 生命周期的内容移至一个大概叫 ActivityLifeCycler 的类中
  • 将接口类的内容移至 ActivityTaskManagerService
  • 剩余的内容移至其它文件

这是一个相对长期的计划,目前大部分有关层次结构的内容被移至 RootActivityContainer ,不过它也是一个临时工,未来会和 RootWindowContainer 合并。

有时候在阅读源码时会觉得很难看懂,这时候不要轻易怀疑自己的能力,因为官方源码有很多待改进的地方 😆

相较于传统的 ActivityStackSupervisor,新版本(9.0 后)引入了 Container 的概念,并使用 RootActivityContainer 接替了 ActivityStackSupervisor 之前有关 Activity 层次结构的管理工作。在 Android 9 和 Android 10 的设备上,我们可以使用 adb shell dumpsys activity containers 命令来查看Activity 的层次结构。

Android 11 这部分内容做了较大更改,待开放源码后再对这部分内容进行补充。

Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

adb shell dumpsys activity containers (9.0及之后支持)

为什么同时存在栈和 Task 的设计?

这是很多开发者的疑惑,为什么会存在栈和 Task 的设计?

前文我们介绍了 Android 多任务的设计理念。因此我们很容易理解 Task 的设计。

无论是官方文档还是网上众多的博客都在讲返回栈或者任务栈的概念,其实这些概念很抽象,而且我认为没必要一定要将这些概念对应到源码上,例如 xxx 类是返回栈,xxx 是任务栈。

不妨从几个方向理解栈的设计:

  • Task 内的每个界面(ActivityRecord)被一个栈的结构管理,它有着严格的「后进先出」的顺序,每个 ActivityRecord 之间的顺序无法改变。(这也比较符合用户直觉)

    其实桌面操作系统也有类似的概念,例如 windows 的文件管理器,每个界面有着严格的「后进先出」的顺序,当该任务所有界面都关闭后,底部的 Task 便显示出来。

    而有时候我们想改变 逐个入栈和出栈 的行为,因此引入 FLAG_ACTIVITY_CLEAR_TOP 来控制批量出栈以及使用 launchMode 来控制 ActivityRecord 的复用。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    Windows Files

    这个行为可以理解为 任务栈。虽然是一个栈的概念,但源码中, TaskRecord 中是通过 final ArrayList<ActivityRecord> mActivities; 这种形式持有其内部的 ActivityRecord 的。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    TaskRecord 模型

  • Task 和 Task 之间也有栈的概念,比如展示给用户的 Task 被关闭后,其下层的 Task 便会展示给用户,但 Task 之间的顺序可以改变,例如某个 Task 可以从中间位置切换到前台。

    Android 中使用 ActivityStack 表示这种概念,而且为了方便处理 Launcher,将其单独放置在一个 ActivityStack 中,其它 TaskRecord 放置到另一个 ActivityStack 中。7.0 后又对 ActivityStask 进行细化,分成了静态 Stack 和动态 Stack。9.0 开始索性放开 ActivityStack 的限制,通过创建新的 ActivityStack 来管理新创建的 TaskRecord。

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    9.0 之前模型

    Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈

    9.0 及之后模型

了解这些,Android 为我们提供管理 Task 的方式便很容易理解了。

如何管理 Task

前面我们反复强调 Android 是基于多任务的理念设计的,因此在管理 Activity 时我们还要关心 Activity 如何与 Task 关联。例如在启动某个 Activity 时我们希望它被放到一个新的 Task 中;或者在启动某个 Activity 时,我们希望使用它的已存在的实例,而不是直接创建一个新的;亦或者我们希望在用户离开 Task 后清除所有 Activity。

从使用形式上看, Android 为开发者提供了两种管理 Task 的方式:

  • 使用 Manifest 中 <activity> 标签中的属性来管理
    • taskAffinity
    • launchMode
    • allowTaskReparenting
    • clearTaskOnLaunch
    • alwaysRetainTaskState
    • finishOnTaskLaunch
  • 在调用 startActivity() 时通过配置 intent 中的 flag 来管理
    • FLAG_ACTIVITY_NEW_TASK
    • FLAG_ACTIVITY_CLEAR_TOP
    • FLAG_ACTIVITY_SINGLE_TOP

launchMode

为了解决 Activity 在 Task 中的复用问题,Android 设计了 launchMode。

对于不同 launchMode 是基于何种场景设计的内容, 重学安卓:你丢了 offer,只因拎不清 Activity 任务和返回栈 中已经介绍,此处不再赘述。

启动模式是 standardsingleTop 的 Activity 可以被多次实例化,其实例可以属于任何 Task,而且可以在栈中的任何位置。

通常情况下它们会被放入调用 startActivity 所在的 Task 中(除非 Intent 中包含 FLAG_ACTIVITY_NEW_TASK 的 flag,此种情况下与 taskAffinity 的配置有关)。

启动模式是 singleTasksingleInstance 的 Activity 只能被实例化一次(销毁后再次打开并创建不计,这里侧重同一时间只能有一个实例),换句话说,全局一次只会有一个这样的 Activity 实例(虽然官方文档如此描述,但该行为有例外)。singleTasksingleInstance 的唯一区别是 singleTask 允许其他 Activity 与其共享 Task,而 singleInstance 是其 Task 的唯一成员(官方文档如此描述,但该行为有例外,后文有提及)。

这两种 launchMode 在源码中会自动添加 FLAG_ACTIVITY_NEW_TASK,因此这种模式启动的 Activity 应处于哪个 Task 也与 taskAffinity 的配置有关。

else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {     // 位运算看不懂?后文会讲     mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; }

Intent Flag

除了 lauchMode,Android 还允许在调用 startActivity 时配置 Intent Flag 来对任务进行管理。

Flag 的状态管理

Android 中 Intent Flag 被定义为 16 进制数

public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000; public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000; public static final int FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000; public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000; public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000; //省略...

使用 16 进制数配合位运算做状态管理的缘由以及优势见 就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳实践!

👆 强烈建议阅读,不仅对阅读源码有很大帮助,而且在开发过程中借助 16 进制数配合位运算处理多状态管理会十分简洁高效!

这里举例源码中常见的操作,以 Intent Flag 为例:

  • 使用 | 运算添加 flag,例如上文中的 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;,为 mLaunchFlags 添加 flag

  • 使用 & 运算 和 ~ 运算配合使用以移除 flag,例如 launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » Android Detail:官方文档不是圣经,老生常谈的 Activity 任务,返回栈求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们