1.前言
- 启动模式在面试中经常被问到,实际开发中应用广泛,所以是必须掌握的一种技能
- 下面,我将详细介绍启动模式的相关知识,希望你们会喜欢
- 第一篇文章,若有纰漏,欢迎指出,必改
2.目录
-
Activity启动模式
-
ActivityStack,TaskRecord,ActivityRecord,Activity关系
-
taskAffinity
-
allowTaskReparenting
-
实例
-
总结
3.Activity启动模式
3.1 standard(标准模式)
- 不复用Activity,startActivity会启动一个新的Activity实例并且置于栈顶,即使栈内已经有这个Activity实例
- 举例:
当前Activity栈已经存在ABC三个Activity,需要新启动C,则Activity栈变成ABCC,并不会对原来的C进行复用
3.2 singleTop(栈顶复用模式)
- 栈顶复用,startActivity启动一个新的Activity时,当前栈顶已经存在这个Activity实例,不会启动新的Actiivty,直接复用当前栈顶Activity,并且回调Activity.onNewIntent方法,如果栈内已有这个Activity但是不在栈顶,则不复用直接启动一个新的Activity实例
- 举例:
1. 当前Activity栈已经存在ABC三个Activity,需要新启动D(singleTop),则Activity栈变成ABCD
2. 当前Activity栈已经存在ABC三个Activity,需要新启动C(singleTop),则Activity栈还是ABC,C被复用并且回调了C的onNewIntent方法
3. 当前Activity栈已经存在ABC三个Activity,需要新启动B(singleTop),则Activity栈变成ABCB,不会对B进行复用
3.3 singleTask(栈内复用模式)
- 栈内复用,startActivity启动一个新的Activity时,当前栈内已经存在这个Activity实例,不会启动新的Actiivty,直接复用当前栈内Activity,将这个Activity之上的Activity出栈,并且回调Activity.onNewIntent方法
- 举例:
0. 前提是taskAffinity相同
1. 当前Activity栈已经存在ABC三个activity,需要新启动D(singleTask),则Activity栈变成ABCD
2. 当前Activity栈已经存在ABC三个activity,需要新启动C(singleTask),则Activity栈还是ABC,C被复用并且回调了C的onNewIntent方法
3. 当前Activity栈已经存在ABC三个activity,需要新启动B(singleTask),则Activity栈变成AB,B被复用并且回调了B的onNewIntent方法,C被出栈(也就是执行了onPause,onStop,onDestory生命周期)
3.4 singleInstance(单例模式)
- 不复用Activity,startActivity会启动一个新的Activity栈并且启动一个新的Activity实例,也就是一个栈内有且仅有一个Activity实例
- 举例:
当前Activity栈已经存在ABC三个activity,需要新启动D(singleInstance),则新建Activity栈,并将D放入新的Activity栈
4 ActivityStack,TaskRecord,ActivityRecord,Activity关系
-
4.1 源码
package android.content.pm;
public class ActivityInfo extends ComponentInfo implements Parcelable {
...//省略无关代码
public String taskAffinity;
...//省略无关代码
}
package com.android.server.wm;
/**
* An entry in the history stack, representing an activity.
*历史堆栈中的一个条目,代表Activity
*/
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
...//省略无关代码
// activity info provided by developer in AndroidManifest
final ActivityInfo info;
...//省略无关代码
}
package com.android.server.wm;
class Task extends WindowContainer<WindowContainer> {
...//省略无关代码
// The affinity name for this task, or null; may change identity.
String affinity;
/** ActivityRecords that are exiting, but still on screen for animations. */
final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
...//省略无关代码
}
package com.android.server.wm;
/**
* State and management of a single stack of activities.
* 负责一个activity栈的状态和管理
*/
class ActivityStack extends Task {
...//省略无关代码
// The affinity name for this task, or null; may change identity.
String affinity;
/** ActivityRecords that are exiting, but still on screen for animations. */
final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
...//省略无关代码
}
package com.android.server.wm;
/**
* State and management of a single stack of activities.
* 负责一个activity栈的状态和管理
*/
class RecentTasks {
...//省略无关代码
// List of all active recent tasks
private final ArrayList<Task> mTasks = new ArrayList<>();
/** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */
private final ArrayList<Task> mHiddenTasks = new ArrayList<>();
...//省略无关代码
}
-
4.2 源码分析
- ActivityInfo拥有taskAffinity属性,也就是说每个Activity都有taskAffinity属性
- ActivityRecord持有一个ActivityInfo对象,也就是说一个ActivityRecord对应一个Activity
- Task拥有taskAffinity属性,也就是说Task对应一种taskAffinity。同时Task持有一个ActivityRecord列表,也就是说一个Task对应多个ActivityRecord
- ActivityStack继承自Task,与Task具有同样的功能和属性,负责ActivityRecord || Activity栈的状态和管理,一个ActivityStack对应一个Task对应多个ActivityRecord
- RecentTasks持有Task列表,也就是我们常见的最近任务列表
-
4.3 关系图
5 taskAffinity
-
5.1 源码
//package android.content.pm;
public class ActivityInfo extends ComponentInfo implements Parcelable {
...//省略无关代码
/**
* The affinity this activity has for another task in the system. The
* string here is the name of the task, often the package name of the
* overall package. If null, the activity has no affinity. Set from the
* {@link android.R.attr#taskAffinity} attribute.
* 大概翻译一下,该属性表明任务相关性,默认是应用包名
*/
public String taskAffinity;
...//省略无关代码
}
//package android.content.pm;
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
...//省略无关代码
/**
* Default task affinity of all activities in this application.
* 当前应用所有activity的默认taskAffinity
*/
public String taskAffinity;
...//省略无关代码
}
-
5.2 概念
- 物以类聚,人以群分,该属性表明Activity之间的相关性
- ApplicationInfo.taskAffinity默认是包名(com.xxx.xxx)
- 当前应用的Activity.taskAffinity默认是ApplicationInfo.taskAffinity
- 该属性主要和SingleTask启动模式或者allowTaskReparenting属性配对使用
6 allowTaskReparenting
-
6.1 源码
//package android.content.pm;
public class ActivityInfo extends ComponentInfo implements Parcelable {
...//省略无关代码
/**
* Bit in {@link #flags} that indicates that the activity can be moved
* between tasks based on its task affinity. Set from the
* {@link android.R.attr#allowTaskReparenting} attribute.
* 不同Task之间的Activity可以移动,依赖于taskAffinity属性
*/
public static final int FLAG_ALLOW_TASK_REPARENTING = 0x0040;
...//省略无关代码
}
-
6.2 概念
- 一个flag
- allowTaskReparenting设置为true代表该Activity可以在ActivityStack/Task之间移动
- 需要和taskAffinity属性配对使用
-
6.3 举例
- 路上捡了流浪狗狗,回家养了几天,如果狗狗是原主人抛弃的(allowTaskReparenting = false),这个狗狗就属于你了,如果狗狗是原主人依然需要的( allowTaskReparenting = true),这个狗狗得还别人。
- 应用A启动应用B的ActivityC,ActivityC的allowTaskReparenting 设置为true,点击Home键返回桌面,点击应用B,会发现启动的并不是应用B的主Activity,而是ActivityC,此时在ActivityC点击返回键,页面会回退到应用B的主Activity。先记住结论,后面会有实验验证
7 实例
-
7.1 前言
- 上面的知识点记住了吗?那开始做题吧
- Standard启动模式受taskAffinity影响吗?
- SingleTop启动模式受taskAffinity影响吗?
- SingleTask启动模式与taskAffinity关系是怎样的?
- SingleInstance与taskAffinity有什么关联?
- allowTaskReparenting作用是什么?
- Activity之间的回退栈逻辑是什么?
- ...
-
7.2 探究Standard与taskAffinity的关系
-
7.2.1 代码
open class BaseActivity : AppCompatActivity() {
val tag: String = "qwq"
open lateinit var tv:TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv = findViewById<TextView>(R.id.tv)
tv.text = this.localClassName
Log.i(tag,"${this.localClassName}任务id:$taskId")
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Log.i(tag, "${this.localClassName}onNewIntent被调用,接收到传递的数据:${intent?.getStringExtra("data")}");
}
}
class FirstActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
tv.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("data", "${this.localClassName}传递的数据")
startActivity(intent)
}
}
}
class SecondActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
tv.setOnClickListener {
val intent = Intent(this, ThirdActivity::class.java)
intent.putExtra("data", "${this.localClassName}传递的数据")
startActivity(intent)
}
}
}
<activity
android:name=".FirstActivity"
android:taskAffinity="com.xgz.kwa1"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity"
android:taskAffinity="com.xgz.kwa1"/>
-
7.2.2结果
日志打印
2021-12-18 03:56:59.245 7436-7436/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:43
2021-12-18 03:57:02.521 7436-7436/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:43
adb shell dumpsys activity activities 输出
Running activities (most recent first):
TaskRecord{97b4e66 #43 A=com.xgz.kwa1 U=0 StackId=1 sz=2}
Run #1: ActivityRecord{f80978d u0 com.xgz.kwa/.SecondActivity t43}
Run #0: ActivityRecord{40a33e1 u0 com.xgz.kwa/.FirstActivity t43}
-
7.2.3 结论
taskAffinity对Standard启动模式没有影响
-
7.3 探究SingleTop与taskAffinity的关系
-
7.3.1 代码
<activity
android:name=".FirstActivity"
android:launchMode="standard"
android:taskAffinity="com.xgz.kwa1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleTop"
android:taskAffinity="com.xgz.kwa2" />
-
7.3.2结果
日志打印
2021-12-18 04:05:33.818 7752-7752/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:45
2021-12-18 04:05:36.684 7752-7752/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:45
adb shell dumpsys activity activities 输出
TaskRecord{f341410 #45 A=com.xgz.kwa1 U=0 StackId=1 sz=2}
Run #1: ActivityRecord{968b86a u0 com.xgz.kwa/.SecondActivity t45}
Run #0: ActivityRecord{aef1362 u0 com.xgz.kwa/.FirstActivity t45}
-
7.3.3 结论
taskAffinity对SingleTop启动模式没有影响
-
7.4 探究SingleTask在taskAffinity不同的情况下表现
-
7.4.1 代码
<activity
android:name=".FirstActivity"
android:launchMode="standard"
android:taskAffinity="com.xgz.kwa1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleTask"
android:taskAffinity="com.xgz.kwa2" />
-
7.4.2结果
启动SecondActivity日志打印
2021-12-18 04:13:06.458 8071-8071/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:48
2021-12-18 04:13:25.430 8071-8071/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:49
adb shell dumpsys activity activities 输出
TaskRecord{cc73108 #49 A=com.xgz.kwa2 U=0 StackId=1 sz=1}
Run #1: ActivityRecord{8efb19f u0 com.xgz.kwa/.SecondActivity t49}
TaskRecord{c1e02c6 #48 A=com.xgz.kwa1 U=0 StackId=1 sz=1}
Run #0: ActivityRecord{9c48ae1 u0 com.xgz.kwa/.FirstActivity t48}
系统最近任务栏视图
在SecondActivity页面按下Home键返回桌面,重新点击桌面图标进入应用,会进入FirstActivity(主Activity)而不是SecondActivity
-
7.4.3 结论
taskAffinity不同的情况下,启动SingleTask模式的Activity会新开一个任务栈,系统最近任务栏会新开一个栈视图,从桌面返回会返回主Activity所在栈栈顶Activity
-
7.4.4 其他
-
7.4.4.1 实验1:
- 条件:如果A是主Activity,B与A的taskAffinity相等,C与A的taskAffinity不相等。A启动模式Standard,B和C启动模式SingleTask
- 操作:A启动B,B启动C
- Q1:此时最近任务有几个任务栈
- Q2:此时C可以通过返回键返回B吗
- Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
- A1:两个
- A2:可以
- A3:B
-
7.4.4.2 实验2:
- 条件:如果A是主Activity,B与A的taskAffinity不相等,C与A的taskAffinity相等。A启动模式Standard,B和C启动模式SingleTask
- 操作:A启动B,B启动C
- Q1:此时最近任务有几个任务栈
- Q2:此时C可以通过返回键返回B吗
- Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
- Q4:Q3操作回到应用之后,C回退会回退到哪个Activity
- Q5:Q4操作之后,A启动B,B会回调onNewIntent还是回调onCreate方法
- A1:两个
- A2:可以
- A3:C
- A4:A
- A5:onNewIntent
-
7.4.4.3 实验3:
- 条件:A是主Activity,C和A的taskAffinity相等,B和D的taskAffinity相等并且和AC不相等。ABCD启动模式均是SingleTask
- 操作:A启动B,B启动C,C启动D
- Q1:此时最近任务有几个任务栈
- Q2:此时D可以通过返回键返回C吗
- Q3:此时按下Home键,通过桌面应用图标返回应用,会返回哪个Activity
- Q4:Q3操作回到应用之后,C回退会回退到哪个Activity
- Q5:Q4操作之后,A启动B,B会回调onNewIntent还是回调onCreate方法
- Q6:Q3操作回到应用之后,C启动D,D会回调onNewIntent还是onCreate方法
- Q7:Q6操作之后,D能返回C吗
- A1:两个
- A2:不可以,返回栈是D->B->C->A
- A3:C
- A4:A
- A5:onNewIntent
- A6:onNewIntent
- A7:不可以,返回栈是D->B->C->A
-
7.4.3 总结
- 在启动模式为SingleTask的情况下,具有相同taskAffinity属性的Activity被分配在同一ActivityStack中
- 如果这个ActivityStack不存在则新建一个ActivityStack并将Actiivty入栈
- 每个ActivityStack对应一个最近任务栏视图
- 一个应用可能存在多个ActivityStack
- 在没有按下Home键的情况下回退栈按ActivityStack顺序返回,ActivityStack内部按照Activity顺序返回
- 从桌面返回后,显示Activity为主Activity所在ActivityStack栈顶Activity,回退栈只会根据主Activity所在任务栈进行返回
-
7.5 探究SingleTask在taskAffinity不同的情况下表现
-
7.5.1 代码
<activity
android:name=".FirstActivity"
android:launchMode="singleInstance"
android:taskAffinity="com.xgz.kwa1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleInstance"
android:taskAffinity="com.xgz.kwa2" />
-
7.4.2结果
启动SecondActivity日志打印
2021-12-18 05:52:11.259 9985-9985/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:90
2021-12-18 05:52:13.838 9985-9985/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:91
adb shell dumpsys activity activities 输出
TaskRecord{5a0a68c #91 A=com.xgz.kwa2 U=0 StackId=1 sz=1}
Run #1: ActivityRecord{fc6b473 u0 com.xgz.kwa/.SecondActivity t91}
TaskRecord{3889dea #90 A=com.xgz.kwa1 U=0 StackId=1 sz=1}
Run #0: ActivityRecord{143e0e7 u0 com.xgz.kwa/.FirstActivity t90}
系统最近任务栏视图
在SecondActivity页面按下Home键返回桌面,重新点击桌面图标进入应用,会进入FirstActivity(主Activity)而不是SecondActivity
-
7.5.3 结论
taskAffinity不同的情况下,启动SingleInstance模式的Activity会新开一个任务栈,系统最近任务栏会新开一个栈视图,从桌面返回会返回主Activity所在栈栈顶Activity -
7.5.4 其他
-
7.5.4.1 实验1:
- 条件:如果A是主Activity,B,C与A的taskAffinity相等,A,B,C启动模式均为SingleInstance
- 操作:A启动B,B启动C
- Q1:此时最近任务有几个任务栈
- Q2:此时C可以通过返回键返回B吗
- Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
- A1:一个
- A2:可以
- A3:A
-
7.5.4.2 实验2:
- 条件:如果A是主Activity,B与A的taskAffinity相等,A启动模式均为SingleInstance,B启动模式均为SingleTask
- 操作:A启动B
- Q1:此时最近任务有几个任务栈
- Q2:此时B可以通过返回键返回A吗
- Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
- Q4:Q3操作之后A会回调onNewIntent还是onCreate方法
- A1:一个
- A2:可以
- A3:A
- A4:onNewIntent
-
7.5.4.3 实验3:
- 条件:A是主Activity,AC的taskAffinity相等,BD的taskAffinity相等并且和AC不相等。AC启动模式均是SingleInstance,BD启动模式均是SingleTask
- 操作:A启动B,B启动C,C启动D
- Q1:此时最近任务有几个任务栈
- Q2:此时D可以通过返回键返回C吗
- Q3:此时按下Home键,通过桌面应用图标返回应用,会返回哪个Activity
- Q4:Q3操作之后,A会回调onNewIntent还是onCreate方法
- A1:两个
- A2:不可以,返回栈是D->B->C->A
- A3:A
- A4:onNewIntent
-
7.5.3 总结
- 在启动模式为SingleInstance的情况下,一个Activity对应一个ActivityStack
- 具有相同taskAffinity的Activity在最近任务栏只会显示一个视图
- 一个最近任务栏可能会对应多个视图
- 在没有按下Home键的情况下回退栈按ActivityStack顺序返回,ActivityStack内部按照Activity顺序返回
- 从桌面返回后,显示Activity为主Activity所在ActivityStack栈顶Activity,回退栈只会根据主Activity所在任务栈进行返回
- 从桌面返回后,如果主Activity启动模式是SingleInstance,会回调onNewIntent方法
8 总结
- SingleTop和Standard模式不受taskAffinity影响
- 在SingleTask模式下不同taskAffinity对应不同的ActivityStack,一个ActivityStack对应一个最近任务视图
- 在SingleTask模式下不同Activity对应不同的ActivityStack,一个具有相同taskAffinity的多个ActivityStack对应一个最近任务视图
- 任意模式下,在没有按下Home键的情况下,回退栈按ActivityStack顺序返回,ActivityStack内部按照Activity顺序返回
- 从桌面返回后,显示Activity为主Activity所在ActivityStack栈顶Activity,回退栈只会根据主Activity所在任务栈进行返回
- 从桌面返回后,如果显示的Activity启动模式是SingleInstance或者SingleTask,会回调onNewIntent方法