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

前言

听坚守在本部的同事说,杭州这边,这段时间面了不下 30 个 因公司倒闭、而被迫离职的 Android 开发。

由于僧多肉少的缘故,同事便从进阶基础开始问,就比如 Activity 任务、返回栈、启动模式。没想到这 30 个人里面,仅有 1 个勉强过关。

是因为网上介绍 任、返、启 的文章不够多吗?不是的,恰恰相反,网上的文章多如牛毛,却没有一篇是摸着良心、实证检验过,都是人云亦云,书上说什么就信什么,未经过大脑的思考和动手的检验,便发布到网上。

考虑到接下来,还会有不少人会因为 如此基本的进阶知识都掌握不透 而面试遭拒,我便烦着自己就 任务、返回栈、启动模式 写一个专题,并开源了配套的 13 个代码测试案例(不要慌,文末链接给出)。所以 即使不看别的文章,也请 务必 务必 务必 收藏好这一篇!

提出了正确的问题,事情就解决了一半

在开始今天的话题之前,我像往常一样,首先提出 7 个基于深度思考得出的问题。

如果以下 7 个问题中,有超过 2 个你无法准确地回答,那么你需要注意了,以下介绍的每一小节内容,都是你在网上轻易搜不到的,因为这些都是我通过深度思考、实证检验,而得出的,绝非以讹传讹、照本宣科之流。

爱因斯坦说,提出了正确的问题,事情就解决了一半。因而毫不夸张地说,光是看了以下提出的这 7 个问题,你就没白来!

Activity 任务、返回栈、启动模式的 7 个扪心自问

  • 你知道任务的概念、职责和本质吗?
  • 你知道返回栈的概念、职责和本质吗?
  • 你知道任务和返回栈 并不是网文讹传的同一个概念吗?
  • 你知道启动模式和任务的关系吗?(网文只答了一半,让人误以为 启动模式是指导 Activity 和任务内 Activity 的关系)
  • 你知道 singleTask 和 standard 外加 singleTop 真正本质上的区别吗?
  • 你知道清单声明和动态 FLAG 的区别吗?
  • 你知道多个 App 之间 Activity 异常的连贯回退是怎么引起的吗?
    ……

除了解答上述 7 个问题,本文还在文末额外赠送了:

  • 通过一项实验,来证实返回栈的独立存在。
  • 介绍 taskAffinity 与 standard、singleTop、singleTask 三者真实的关系。
  • 介绍 singleTask 与任务的特殊关系。
  • 介绍由于 singleTask 与任务的特殊关系而导致的异常状况及处理。

文章目录一览

  • 前言
    • 提出了正确的问题,事情就解决了一半
    • Activity 任务、返回栈、启动模式的 7 个扪心自问
  • 任务和返回栈
    • 任务和返回栈 的概念分别为何?
    • 为何分别存在任务和返回栈?
  • 启动模式
    • 为何存在 启动模式 的设计?
    • 4 种启动模式,表面上的特点分别为何?
    • 为何存在 多种启动模式 的设计?
    • 如何设置启动模式?
    • SingleTask 如何指定与哪个任务关联?
  • 任务清空或保留 Activity 的几种方式?
  • 综合案例(证实了 返回栈 相对于 任务 的独立存在,全程动图和截图为证)
  • 福利时间到!魔鬼就在细节中 —— 全网独家的、严密测试的 5 个结论:
    • taskAffinity 真实的适用范围
    • standard 和 singleTop 的真实本质
    • singleTop 的真实本质
    • singleTask 的真实本质及实验佐证
    • singleTask 和 singleInstance 与最近访问列表的关系

任务和返回栈 的概念分别为何?

ActivityRecord

描述 Activity 的相关信息,对应着一个用户界面,是 Activity 管理的最小单位。

TaskRecord

是一个栈结构,管理着多个 ActivityRecord,栈顶的 ActivityRecord 表示当前获得焦点的窗口。

ActivityStack

是一个栈结构,管理着多个 TaskRecord,栈顶的 TaskRecord 表示当前获得焦点的任务。

对 焦点、前景、可见 等概念不熟悉的朋友,建议先阅读一下这篇短文 《重学安卓:Activity 生命周期的 3 个辟谣》,文中通过介绍 RemixOS 等安卓桌面操作系统,来方便你无障碍地理解 前景和可见 的区别,以及何谓“获得焦点”😉

ActivityStackSupervisior

管理着多个 ActivityStack。当前只会有一个获得了焦点的 ActivityStack

划重点 👆 👆 👆 这一段全是重点。人们通常将 TaskRecord 和返回栈混为一谈,实际上 ActivityStack 才是传说中的返回栈。

在下文的实例中,我将亲手证实返回栈的存在,及忽视其存在所带来的后果。

为何分别存在任务和返回栈?

我们通常说的任务和返回栈,分别指的是 TaskRecord 和 ActivityStack,它们的存在是为了维护“页面跳转的连贯性”体验

划重点 👆 👆 👆

比如用户在日记软件中添加照片,于是点击添加按钮,跳转到系统相册的选择器模式,选取照片后,又跳回日记软件。虽然日记软件和系统相册是两个 App,但这一系列的操作给用户的感觉,就好像是在同一个 App 中完成的。

换言之,即使是来自不同 App 的 Activity,也能存在于同一个任务中,完成连贯的跳转和回退操作。至于为何有了任务还要有返回栈,我们暂且按住不表、先往下阅读~ 😉

为何存在 启动模式 的设计?

启动模式是用于定义 Activity 与任务的关联方式

划重点 👆 👆 👆

为适应不同场景的需要,总共设计了 4 种方式:
standardsingleTopsingleTasksingleInstance

4 种启动模式,表面上的特点分别为何?

standard - 标准模式

创建 Activity 的实例,并 添加到启动它的源 Activity 所在的任务的栈顶。不管栈内或栈顶是否已存在该 Activity 的实例。

singleTop - 栈顶复用模式

启动它的源 Activity 所在的任务的<栈顶> 已存在该 Activity 的实例,那么不创建该 Activity 的新实例 —— 而是走该 Activity 实例的 onNewIntent 回调,注入新的 intent,并执行 onResume(也就是不走 onCreate、onStart)。
否则就在栈顶创建一个新实例。

singleTask - 栈内复用模式

该 Activity 所属的任务中 已存在该 Activity 的实例,那么不创建该 Activity 的新实例 —— 而是首先将任务中该 Activity 实例之上的 Activity 全都出栈,并且走该 Activity 实例的 onNewIntent 回调,注入新的 intent,并执行 onResume。
否则就在栈顶创建一个新实例。
(singleTask Activity 的所属任务,取决于清单中配置的 taskAffinity,如果没有用 taskAffinity 指定任务名,默认是 Activity 所属 App 的默认任务。下文会介绍 taskAffinity 的作用。)

singleInstance - 单例模式

会新建一个任务和返回栈,并且 独享这个任务和返回栈。也即 整个系统有且只有这么一个 Activity 的实例,多个 App 可共享该实例。

为何存在多种启动模式的设计?

top Created with Sketch.