Activity启动模式&Task栈

简介: 在AMS中,ActivityRecord对应一个Activity,TaskRecord对应一个Task,每个TaskRecord中保存了若干ActivityRecord,TaskRecord由taskId标识,通过getTaskId()可以获取Activity所属的Task。

在AMS中,ActivityRecord对应一个Activity,TaskRecord对应一个Task,每个TaskRecord中保存了若干ActivityRecord,TaskRecord由taskId标识,通过getTaskId()可以获取Activity所属的Task。ActivityStack中的TaskRecord列表保存了AMS中所有的Task信息,ActivityStack、Task、Activity三者的对应关系如下:

img_a30eb7e53471476d48a9dccbf4560d45.jpe
Activity Task ActivityStack对应关系

其中ActivityStack处于ASM所属进程,ActivityThread在APP所属进程。可以看到ActivityStack中保存了TaskRecord列表,TaskRecord中保存了Activityrecord列表。在创建或者回退Activity的时候,就是对ActivityStack中TaskRecord的ActivityRecord进行出栈和入栈操作。

Task图示法:

img_7402b2546d47870d6bd862ff350a34b8.png

上图是本文Task的图示法,Task标记为“Task_task名(TaskAffinity)”,Task的TaskAffinity属性是其根Activity的TaskAffinity值;Task中的Activity标记为"应用名_Activity名(TaskAffinity)”,Activity的TaskAffinity通过AndroidManifest.xml中配置。中间的黑线表示Home,黑线下方是点home键退到后台的Task。

taskAffinity:

AndroidManifest.xml中可以给应用配置taskAffinity属性,不设置默认会使用应用的包名作为taskAffinity,taskAffinity只有在FLAG_ACTIVITY_NEW_TASK、singleTask、singleInstance的时候有效。桌面打开应用,由于Launcher使用了FLAG_ACTIVITY_NEW_TASK来启动应用,所以会根据主界面设置的taskAffinity或者默认的taskAffinity来查找或创建新的Task,具有相同taskAffinity的Task推到前台。

也就是说,应用没有启动是点击桌面应用图标会启动一个新的Task,如果应用已经启动,退到home后台后,再次点击图标,会将home后台的task原封不动重新推到前台,如果rootActivity是singleTask的话,就会将rootActivity上面的Activity对象出栈,然后再将Task推到前台。

Activity启动模式

1、standard

img_c9782daeb3762434bf5b40c32dc3bd8a.png
入栈

A_b、B_c都是standard模式,A_a启动了A_b,A_b启动了B_c,B_c又启动了A_b,可以得出如下结论:被启动的Activity如果是standard模式(即使启动者和被启动者位于不同的APP),那么被启动的Activity会保存在启动者所属的Task中(不论是否设置了TaskAffinity属性),且不论该Task中是否存在被启动实例,都会创建一个新的实例压入栈中。在按back键时也是一个一个的出栈。

2、singleTop

img_48eab37f892f2dee51f97022f2cd3c14.png

应用A启动应用B的B_b Activity,B_b是singleTop启动模式,那么再次启动B_b时,如果栈顶元素是B_b,就不再创建B_b实例。不论启动者和被启动者是否属于同一个应用,被启动的Activity和启动的Activity都会在同一个Task中。

singleTop属性和allowTaskReparenting混合使用会出现什么场景?

allowTaskReparenting

假设所有的Activity启动模式都是standard的,这里没有加上TaskAffinity,表明该属性不影响Task的归属。有如下运行流程:

img_506063c18c6c2c7c60d83865347bee7f.png

这里A_b设置了allowTaskReparenting=true;

1)、首先启动了应用A的主界面A_a,然后A_a启动了A_b,A_a和A_b都在Task_a中,这时按下home键

2)、启动应用B的主界面B_c,B_c启动了B_d,B_d又启动了A_b,由于A_b是standard的,所以B_c、B_d和A_b在同一个Task_b中,这时再按下home键

3)、再次点击桌面的A应用,系统会把Task列表中Task_a推到前台,由于A_b设置了allowTaskReparenting属性,所以Task_b中的A_b实例也被转移到了Task_a中。这就是allowTaskReparenting属性的作用,该属性在task从后台切换到前台的时候起作用。

如果设置A_b的启动模式为singleTop,上面的执行流程依然正确,但是却发现Task_a的栈顶有两个A_b实例。

3、singleTask

当一个singleTask 模式的activity 被启动时,AMS会检查是否存在与该activity 的taskAffinity 相同的task。

1)如果存在这样的task,那么检查该activity 是否已经创建,如果已创建,那么销毁在该Activity 以上的activity 并调用onNewIntent。如果activity 尚未实例化,那么创建该activity 的实例,并压入该task 栈。最后然后将该task 推到最上面。

2)如果不存在这样的task,那么就重新创建task,然后创建该activity 的实例,并入栈,然后将该task 推到最上面。

img_a513197a40f7ef2e00453b583b42f076.png
没有Home键参与

在没有Home键参与的场景下,A_b和B_b是SingleTask的,首先启动A应用的主界面A_a,然后启动A_a启动B应用的B_b,由于B_b是singleTask的,所以创建了新的Task_b。B_b启动应用A的A_b,由于A_b是singleTask的,所以没有在Task_b中创建A_b实例,而是先查找到A_b对应的Task_a,然后在Task_a中创建A_b实例,并将Task_a推到最顶层。

img_78f02f984c59e8f5bdeb760121898414.png
home场景1

启动A应用的主界面A_a,A_a是singleTask的,然后启动了A_b、A_c,这时按下home键,接着从前应用A,发现A_a上面的Activity都被弹出栈了。

img_fbcc8af484a474bea511d29a9fc2d6ac.png
home场景2

场景2中A_b是singleTask的,由于A_b不是rootActivity,所以没有出现Activity出栈操作。

img_0a073626fb2d3812db6498f8e3f0f70d.png
home场景3

首先启动应用A的主界面A_a,然后启动A_b,A_b是singleTask的,且taskAffinity和A_a一致,按下home键之后再启动B应用的主界面B_b,生成可新的Task_b,这时候B_b启动A_b,由于A_b是singleTask的,所以B_b从Task_a中移除了,Task_a位于顶部了。

img_7325064b9220299e9315e49a817e7428.png
home场景4

启动A应用的主界面A_a,位于Task_a中,然后按下home键,启动应用B的主界面B_a,B_a位于Task_b中,这时候B_a启动B_b,由于B_b是singleTask的,且taskAffinity和A_a的taskAffinity一致,所以Task_a会推到前台,然后将B_b的实例塞入Task_a中。

4、singleInstance

该启动模式的activity被启动时,如果activity没有被实例化,就创建一个新的task来保存activity实例;如果已经被实例化过,就调用其onNewIntent函数,该activity所在的Task中不允许存在其余activity。

img_aa1e61be25d7b8742c6b06ec722c0961.png

先确定应用A的主界面A_a,然后启动singleInstance的A_b,这里就创建了新的Task_b来保存A_b,然后A_b启动A_c,由于A_b具有独占Task的特性,所以需要先查找A_c的taskAffinity(不设置默认使用包名作为Taskname)对应的task是否存在,如果不存在就创建新的Task,如果存在就将Task推到前台,这里将Task_a推到了前台,并将A_c压入。

5、FLAG_ACTIVITY_NEW_TASK

启动Activity如果使用这个属性,AMS会尝试查找被启动的Activity的taskAffinity相同的Task,如果有,就直接将Task移动到前台,然后将启动的Activity压入栈;如果没有,则创建一个新的栈来保存实例。

img_bca41590fb5fa1d0d9840574d38c816a.png
目录
相关文章
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
1097 77
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
263 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
583 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
382 11
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
534 7
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1307 10
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
420 59
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
369 9

热门文章

最新文章