启动模式的使用场景#
标准模式#
特性:
- 任何页面,如无必要,默认为标准模式,start启动一次创建一个新的Activity
适合场景:
- 任何页面
singleTop#
特性:
- 如果该实例已有,且位于栈顶(是Stack顶部还是TaskRecord顶部?),start启动一次,不会重新创建
- 如果该实例已有,不位于栈顶(是Stack顶部还是TaskRecord顶部?),start启动一次,会重新创建
适合场景:
- 消息推送: 在单聊页面点击消息通知重新进入一个新的单聊页面
- 商品推荐:在A商品详情页面点击运营推荐进入一个新的B商品详情页面
singleTask#
特性:
- 如果该实例已经存在(存在于同一个TaskRecord的?还是存在于Stack中?)但该实例之上还有别的Activity,start启动一次,不会重新创建,会清除该实例之上的所有Activity(清除Stack中其上面的所有?还是TaskRecord内其上面的所有?)
适合场景:
- 应用中的主页
singleInstance#
特性:
- 创建一个新的栈结构,似的Activity存在于新的TaskRecord中
- 如果ActivityA已经存在,通过start方式启动ActivityA,则不会重新创建ActivityA
适合场景:
- 适用于于App主业务分开的页面
- 大型App的设置页面
- 闹铃App,闹铃提醒页面与闹铃设置页面分离
观察Task#
- 标准Activity1 -> 标准Activity2
- 标准Activity1 -> SingleTask 标记Activity2
- 标准Activity1 -> SingleTask +taskAffinity 标记的Activity2
- 标准Activity1 -> SingleTop 标记的Activity2
- 标准Activity1 -> SingleInstance 标记的Activity2
- 标准Activity1 设置启动方式 Intent.FLAG_ACTIVITY_NEW_TASK-> 标准Activity2
- 标准Activity1 设置启动方式 allowTaskReparenting -> 标准Activity2
Task小知识#
- AMS 分配的taskid 是线性递增的,每次开启一个新的task ,taskid 永远都是 +1 的操作。
- singleTask也是可以做成singleInstance效果的,只要声明taskAffinity 就可以
- 即一个 task 中的 Activities 是可以运行在不同的进程中的
- 一个 Activity 运行时所归属的task,默认是启动它 的那个Activity 所在的 task
- Android 手机的任务列表就是根据不同 task 弹出的,我们可以根据任务管理器有几个 item 图标,来知道我们开启了几个 task
- 如果 activity 组件没有声明 taskAffinity 的话,该 activity 的 taskAffinity 属性也是有默认值的。
- 如果 application 指定了 taskAffinity 值,默认值就是 application 指定的 taskAffinity 值;
- 如果 application 未指定的话,默认值就是 manifest 中声明的包名(package 对应的字符串)。
研究手段#
- adb命令查看Activity信息、Task信息、Stack信息,观察Activity顺序、Task顺序、Stack内Activity与Task的顺序
- 设计app,至少包括4个Activity,为Activity设置一种启动模式,单独查看每一种启动模式对Activity展示的影响、Task顺序的影响,研究finsh与moveTaskToBack的对比
- 总结启动模式的场景
观察SingleTask#
现在我们手里有三个Activity,Activity1 、Activity2、Activity3,启动顺序如下:
Activity1 -> Activity2
Activity2 -> Activity3
Activity1 、Activity2、Activity3分别进行三次配置,观察每一次的启动结果
配置1:
是标准启动模式
1 | <activity |
配置2:
仅配置启动模式
1 | <activity |
配置3:
启动模式+taskAffinity属性
1 | <activity |
第一种配置Activity的launchMode为标准模式
1个TaskRecord,TaskRecord持有3个ActivityRecord,TaskRecord的taskid为#78
1 | Running activities (most recent first): |
第二种配置Activity的launchMode为singleTask
1个TaskRecord,TaskRecord持有3个ActivityRecord,TaskRecord的taskid为#77
1 | Running activities (most recent first): |
第三种配置Activity的launchMode为singleTask且配置了taskAffinity
3个TaskRecord,每个TaskRecord持有一个ActivityRecord,每个TaskRecord持有一个新的taskid,共有三个taskid分别是#75 #72 #71
1 | Running activities (most recent first): |
为了更清楚的演示Singletask的作用,引入第四种配置,与配置3、配置4比较
1 | <activity |
从Activity3 -> Activity4 时
1 | Running activities (most recent first): |
从Activity4 -> Activity2 时
1 | Running activities (most recent first): |
由配置3与配置2、配置4相比,可以看到Activity仅配置Launchemode属性为SingleTask,是不会新建TaskRecord的;换言之,只有当标记为SingleTask且 标记一个新的taskAffinity值时,才会创建一个TaskRecord;
由配置4与配置2相比,可以看到Activity仅仅配置SingleTask,可以达到的效果是:当一个标记为SingleTask的Activity2被重新启动后,该TaskRecord里面位于Activity2之上的ActivityRecord会被清除;
当没有配置taskAffinity,SingleTask标记的Activity仅拥有清除特性;
当配置taskAffinity,SingleTask标记的Activity才会处于一个新的TaskRecord
引入配置5
1 | <activity |
从Activity3 -> Activity4 时
1 | Running activities (most recent first): |
从Activity4 -> Activity2 时
1 | Running activities (most recent first): |
可以看到,当Activity、Activity2、Activity3分别创建了三个TaskRecord时,Activity3启动的Activity4,两者都在TaskId为#85的TaskRecord里。
当Activity4启动标记为SingleTask的Activity2时,Activity2所在的#85 Task上移到Stack顶。
启发,SingleInstance、SingleTop的TaskRecord、ActivityRecord分布关系是怎样的呢?
观察SingleTop#
配置1:引入4个标记为SingleTop的Activity
1 | <activity |
栈顶启动一个处于非栈顶的标记为SingleTop 且未创建的Activity
Activity3 -> Activity4时
1 | Running activities (most recent first): |
测试1:从Activity4 -> Activity2 时,Activity2处于非栈顶且已经被创建,TaskRecord会装入一个新的Activity2,此时#94有两个Activity2
1 | Running activities (most recent first): |
测试2:栈顶启动一个处于栈顶的标记为SingleTop的Activity,
Activity3 -> Activity4时
1 | Running activities (most recent first): |
从Activity4 -> Activity4 时,Activity4已经被创建过,再次启动,因此TaskRecord会重新加载Activity3,并进入onNewIntent方法
1 | Running activities (most recent first): |
并且Activity4的log得到了打印
1 | 2021-10-09 14:45:43.849 12224-12224/com.shaunsheep.ams E/ycf: onNewIntent |
总结SingleTop的小知识:
- 由测试1可知,当启动一个未处于栈顶标记为SingleTop的Activity2时,Activity2会重新创建一遍,此时TaskRecord中存在两个Activity2
- 由测试2可知,当启动一个处于栈顶的标记为SingleTop的Activity4时,Activity4并不会重新创建,而是进入该Activity的onNewIntent方法
- 由测试1、测试2可知,SingleTop并不会新创建一个TaskRecord,仍然处于创建它的Activity所处的TaskRecord内
观察SingleInstance#
配置1:4个SingleInstance#
1 | <activity |
Activity3 -> Activity4时
1 | Running activities (most recent first): |
Activity4 -> Activity2时
1 | Running activities (most recent first): |
Activity4 -> Activity4时
执行了log
1 | 2021-10-09 16:25:30.499 32395-32395/com.shaunsheep.ams E/ycf: onNewIntent |
1 | Running activities (most recent first): |
可以看到,当所有Activity都配置成SingleInstance 时候,
Activity4 在栈顶启动自己,Activity4没有重新创建
Activity4 在栈顶启动了Activity2,Activity2 没有重新创建,Activity2 重新置于栈顶
配置2:1个SingleInstance#
Activity2为singleInstance
1 | <activity |
测试0:栈列表
一共有两个TaskRecord,标记启动模式为SingleIntance的Activity2位于Task#135中,Activity1、3、4位于Task#134中
1 | Running activities (most recent first): |
测试1:启动App,Activity4 -> Activity2时
打印了log
1 | 2021-10-09 16:39:16.192 4704-4704/com.shaunsheep.ams E/ycf: MainActivity2 onNewIntent |
栈列表有两个#135 和#134,#135有Activity2,#134有Activity4、Activity3、Activity1、LaunchActivity
1 | Running activities (most recent first): |
测试2:重启App,Activity2->Activity3时
有两个Task #136和 #137,#136有Activity1、LaunchActivity和Activity3,#137有Activity2
1 | Running activities (most recent first): |
测试3:重新启动App,Activity2->Activity3时,重新刷新一遍栈列表,方便下一轮测试,此时TaskId会自增,不再是原来的#137 和#136,而是#138 和#139
在Activity3按下moveTaskToBack(true);
,跳回了Activity2
1 | Running activities (most recent first): |
此时再次Activity2->Activity3时,重新创建Activity3,注意此时# 138有两个Activity3
1 | Running activities (most recent first): |
在第二个Activity3按下finsh或者backPress,直接跳回了第一个Activity3,而非预期的Activity2
1 | Running activities (most recent first): |
在第一个Activity3按下finsh或者backPress,直接跳回了Activity1,而非预期的Activity2
1 | Running activities (most recent first): |
总结SingleInstance的小知识:
由测试0可知,SingleInstance标记的Activity2,必然会独立存在于一个TaskRecord中,它自己启动的新的Activity3,会存在于默认的Task中,默认的Task是启动Activity2的Activity1所在的TaskRecord
由测试1可知,SingleInstance所标记的Activity,如果是首次创建,一定会新创建一个TaskRecord,并置于其中;已经创建,则会使得TaskRecord置于Stack顶部
由测试2和测试3可知标准Activity 1 -> SingleInstance Activity2 -> 标准Activity 3 ,在Activity3 finsh或backPress会跳入Activity1;在Activity3触发moveTaskToBack会跳入Activity2
根据网络文章如果位于Activity2,按下手机home键,重新进入App后,会显示Activity1,可知SingleInstance标记的Activity也影响home键之后的页面展示
由以上三点,可知SingleInstance标记的Activity会影响finsh、backPress、home三类操作之后的页面展示顺序;
进阶测试——activity回退的小bug:#
继续使用配置2:
1 | <activity |
? 在下文中,将moveTaskToBack简写moveback
几个关键的步骤流
①
1…4的TaskRecord结果如下:,Task#611 存放Activity1、3、4, Task#612存放Activity2,Task#611在栈顶,此时屏幕上展示的是Activity4,符合TaskRecord中的顺序
1 | Running activities (most recent first): |
②
4..moveback的结果如下:
Task#611 存放Activity1、3、4, Task#612存放Activity2,Task#612在栈顶,此时屏幕上展示的是Activity2,符合TaskRecord中的顺序
思考1Tips:此处产生一个小问题,结果不符合我们期望的顺序——程序只moveback了Activity4,没有moveBack Activity3,为什么moveBack Activity4之后,看不到Activity3而是看到Activity2呢?
1 | Running activities (most recent first): |
③
2..4的结果如下:
Task#611 存放Activity1、3、4、3、4, Task#612存放Activity2,Task#611在栈顶,此时屏幕上展示的是Activity4,符合TaskRecord中的顺序
1 | Running activities (most recent first): |
④
4…moveBack-2结果如下:与步骤2相比,仅多了1个Activity4、1个Activity3,伴随着思考1,moveBack的特性越发引人关注了
1 | Running activities (most recent first): |
⑤
2…3结果如下:
可以看到Task#611有Activity1、3、4、3、4、3,Task#612有Activity2,此时屏幕上看到的是Activity3,符合TaskRecord的顺序
1 | Running activities (most recent first): |
⑥
在步骤6我们暂停一下,梳理一下逻辑,在这里我们假定实际开发中有一个需求,退出Activity3,显示启动它的Activity2。我们可以通过哪些方式来实现?最简洁的方式就是finsh 销毁Activity3,那我们来实际操作一下看看结果:
finsh 3结果如下:
可以看到Task#611有Activity1、3、4、3、4,Task#612有Activity2,此时屏幕上看到的是Activity4,非常令人震惊,明明在展示Activity3之前,我们看到的Activity2,为什么Activity3销毁,我们看不到Activity2呢?当然下面的TaskRecord已经给出答案,Activity3销毁之后,默认将与Activity3同一TaskRecord的Activity4顶了上来,这也符合TaskRecord列表显示的数据:
Activity3销毁之后看到的是同一TaskRecord的Activity4 而不是启动Activity3的Activity2
1 | Running activities (most recent first): |
位于栈顶的Activity3如果通过moveTaskToBack方式回退,则可以实现Activity3消失、显示Activity2的效果
以上问题引发了思考:
startActivity、finsh、moveTaskToBack三者之间有何联系
finsh与moveTaskToBack对Activity显示有何区别
finsh与moveTaskToBack的对比:
操作上:
- 两者都会操作
WindowList<E> mChildren
中子元素顺序- moveToBack是一个移动数组末尾元素至首位的操作
- finsh是一个移除数组末尾元素的操作
显示上:
- 两者最终呈现在界面上的效果,都是将当前顶部Activity隐藏
- 两者最明显的区别是,隐藏顶部Activity之后,操作之后,屏幕锁显示的下一个Activity的区别
- moveTaskToBack会隐藏当前Activity所在的TaskRecord,展示Stack中的下一个TaskRecord
- finsh会销毁当前Activity,并展示当前Activity所在的TaskReord中的下一个Activity,而非实际的Stack存储的ActivityRecord顺序
帮助文档#
adb#
查看Window#
adb shell "dumpsys window"
主要关注
- WINDOW MANAGER ANIMATOR STATE
- WINDOW MANAGER SESSIONS (dumpsys window sessions)
- WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)
- WINDOW MANAGER TOKENS (dumpsys window tokens)
截取部分段落
1 | WINDOW MANAGER ANIMATOR STATE (dumpsys window animator) |
查看Activity#
adb shell dumpsys activity
主要关注
Running activities:该stack最近的Activity
TaskRecord:该stack最近的taskrecord
TaskId:#63 是com.shaunsheep.ams所在Task的TaskId
A:APP包名,com.shaunsheep.ams
StackId:Task所在的Stack栈
SZ:TaskRecord里面有几个Activity
如Running activities有两个TaskRecod,#63有3个Activity,#62有2个Activity
ActivityRecord的顺序从上往下看,MainActivity3、MainActivity2、MainActivity、HomeFirstSetWifiActivity、HomeUserProtocolActivity;
TaskRecord的顺序从上往下看 #63 #62;
Stack的存储顺序,就是从上往下看的ActivityRecord顺序,ActivityRecord中包含了TaskRecord信息,因此TaskRecord的展示是可以不连续的
1
2
3
4
5
6
7
8
9Running activities (most recent first):
TaskRecord{dbd02ff #63 A=com.shaunsheep.ams U=0 StackId=1 sz=3}
Run #4: ActivityRecord{f8b5f48 u0 com.shaunsheep.ams/.MainActivity3 t63}
Run #3: ActivityRecord{618c5c4 u0 com.shaunsheep.ams/.MainActivity2 t63}
Run #2: ActivityRecord{40ee444 u0 com.shaunsheep.ams/.MainActivity t63}
TaskRecord{9e28cc #62 A=com.unilife.fridge.haierbase.tft U=0 StackId=1 sz=2}
Run #1: ActivityRecord{512920 u0 com.unilife.fridge.haierbase.tft/com.unilife.fridge.haiercommon.ui.activity.guide.HomeFirstSetWifiActivity t62}
Run #0: ActivityRecord{4b852ec u0 com.unilife.fridge.haierbase.tft/com.unilife.fridge.haiercommon.ui.activity.guide.HomeUserProtocolActivity t62}
mResumedActivity:该stack栈顶的Activity
ResumedActivity:该stack栈顶的Activity
截取部分段落
1 | ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities) |
Activity#
onBackPressed() | ||
finish() | ||
getTaskId | 查看TaskId |
UsageStatsManager#
Android获取栈顶程序
https://blog.csdn.net/ballonge/article/details/51085953
Android UsageStatsManager的简单使用
https://blog.csdn.net/shanfuming/article/details/58603450
http://blog.sina.com.cn/s/blog_7d95a2e70102vna4.html
task\stack#
task与stack有什么关系?
深入理解Android卷二 全文-第六章]深入理解ActivityManagerService_Innost的专栏-CSDN博客
Understand Android Activity’s launchMode: standard, singleTop, singleTask and singleInstance
Application、Activity Stack 和 Task的区别
Android Stack与Task - 简书 (jianshu.com)
启动模式#
好问题#
1.Stack、Task中,哪些因素影响Activity的展示顺序?
- 启动模式
- finsh、backPress
- moveTaskToBack
2.SingleInstance使用越多越好吗?每个Activity都设置会怎么样?