这是我参与「第四届青训营」笔记创作的第 3 天
此篇笔记是 Android 基础中 Activity 的第三部分 —— 深入 Activity 启动模式
本篇笔记分为 5 部分
- Avtivity 的出入栈
- 在配置文件中指定启动模式
- 启动模式在实际中的应用
- Activity 的四种启动模式
- 在Java文件中动态设置启动模式
一、Avtivity 活动
Avtivity 的出入栈
我们知道,从第一个活动跳到第二个活动,接着结束第二个活动就能返回第一个活动,可是为什么不会直接返回桌面呢?
这要从Android的内核设计说起了,系统给每个正在运行的App都分配了活动栈,栈里面容纳着已经创建且尚未销毁的活动信息。
鉴于栈是一种先进后出、后进先出的数据结构,所以后面入栈的活动总是先出栈,假设 3 个活动的入栈顺序为:活动 A → 活动 B → 活动 C,则它们的出栈顺序将变为:活动 C → 活动 B → 活动 A ,可见活动 C 结束之后会返回活动 B,而不是返回活动 A 或者别的地方。 假定某个 App 分配到的活动栈大小为 3,该 App 先后打开两个活动,此时活动栈的变动情况如图
然后按下返回键,依次结束已打开的两个活动,此时活动栈的变动情况如图
前述的出入栈情况仅是默认的标准模式,实际上 Android 允许在创建活动时指定该活动的启动模式,通过启动模式控制活动的出入栈行为。
App 提供了两种办法用于设置活动页面的启动模式,其一是修改 AndroidManifest.xml ,在指定的 activity 节点添加属性 android:launchMode ,表示本活动以哪个 启动模式运行。其二是在代码中调用 Intent 对象的 setFlags 方法,表明后续打开的活动页面采用该启动标志。下面将分别予详细说明。
在配置文件中指定启动模式
打开 AndroidManifest.xml ,给 activity 节点添加属性 android:launchMode ,属性值填入 standard 表示采取标准模式,当然不添加属性的话默认就是标准模式。见代码第 3 行
<activity
android:name=".ActStartActivity"
android:exported="true"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
这里仅作为指定方法的演示,所以使用标准模式,其他几种模式将在下面进行详解
启动模式在实际中的应用
接下来举两个例子阐述启动模式的实际应用:在两个活动之间交替跳转、登录成功后不再返回登录页 面,分别介绍如下。
1.在两个活动之间交替跳转
假设活动 A 有个按钮,点击该按钮会跳到活动 B ;同时活动 B 也有个按钮,点击按钮会跳到活动 A ;从首页打开活动 A 之后,就点击按钮在活动 A 与活动 B 之间轮流跳转。
此时活动页面的跳转流程为:首页 → 活动A → 活动 B → 活动 A → 活动 B → 活动 A → 活动 B → …… 多次跳转之后想回到首页。
正常的话返回流程是这样的:…… → 活动 B → 活动 A → 活动 B → 活动 A → 活动 B → 活动 A → 首页,注意每个箭头都代表按一次返回键,可见要按下许多次返回键才能返回首页。
其实在活动 A 和活动 B 之间本不应该重复返回,因为回来回去总是这两个页面有什么意义呢?照理说每个活动返回一次足矣,同一个地方返回两次已经是多余的了,再返回应当回到首页才是。
也就是说,不管过去的时候怎么跳转,回来的时候应该按照这个流程: …… → 活 动 B → 活动 A → 首页,或者按照这个流程: …… → 活动 A → 活动 B → 首页
总之已经返回了的页面,决不再返回第二次。 对于不允许重复返回的情况,可以设置启动标志 FLAG_ACTIVITY_CLEAR_TOP ,即使活动栈里面存在待跳转的活动实例,也会重新创建该活动的实例,并清除原实例上方的所有实例,保证栈中最多只有该活动的唯一实例,从而避免了无谓的重复返回。于是活动A内部的跳转代码就改成了下面这般:
java
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, JumpSecondActivity.class);
// 当栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 设置启动标志
startActivity(intent); // 跳转到意图对象指定的活动页面
当然活动B内部的跳转代码也要设置同样的启动标志
这下两个活动的跳转代码都设置了 FLAG_ACTIVITY_CLEAR_TOP ,运行测试 App 发现多次跳转之后,每个活动仅会返回一次而已。
2.登录成功后不再返回登录页
很多App第一次打开都要求用户登录,登录成功再进入App首页,如果这时按下返回键,发现并没有回到上一个登录页面,而是直接退出App了,这又是什么缘故呢?
当用户登录成功后,App便记下用户的登录信息,接下来默认该用户是登录状态,自然不必重新输入用户名和密码了。既然用户已经登录,就不需要回到登录页面了。
不光登录页面,登录之前的其他页面包括获取验证码、找回密码等页面都不应回去。
对于登录页面的情况,可以设置启动标志 FLAG_ACTIVITY_CLEAR_TASK ,该标志会清空当前活动栈里的所有实例。不过全部清空之后,意味着当前栈没法用了,必须另外找个活动栈才行,也就是同时设置启动标志 FLAG_ACTIVITY_NEW_TASK ,该标志用于开辟新任务的活动栈。于是离开登录页面的跳转代码变成下面这样:
java
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, LoginSuccessActivity.class);
// 设置启动标志:跳转到新页面时,栈中的原有实例都被清空,同时开辟新任务的活动栈
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // 跳转到意图指定的活动页面
运行测试 App ,登录成功进入首页之后,点击返回键不会回到登录页面
Activity 的四种启动模式
默认启动模式 standard
该模式可以被设定,不在 manifest 设定时候,Activity 的默认模式就是 standard。在该模式下,启动 的 Activity 会依照启动顺序被依次压入 Task 栈中:
栈顶复用模式 singleTop
在该模式下,如果栈顶 Activity 为我们要新建的 Activity(目标Activity),那么就不会重复创建新的 Activity。
应用场景:
适合开启渠道多、多应用开启调用的 Activity,通过这种设置可以避免已经创建过的 Activity 被重复创 建,多数通过动态设置使用。
栈内复用模式 singleTask
与 singleTop 模式相似,只不过 singleTop 模式是只是针对栈顶的元素,而 singleTask 模式下,如果 task 栈内存在目标 Activity 实例,则将 task 内的对应 Activity 实例之上的所有 Activity 弹出栈,并将对 应 Activity 置于栈顶,获得焦点。
应用场景
程序主界面:我们肯定不希望主界面被创建多次,而且在主界面退出的时候退出整个 App 是最好的效果。
耗费系统资源的Activity:对于那些及其耗费系统资源的 Activity,我们可以考虑将其设为 singleTask 模式,减少资源耗费。
全局唯一模式 singleInstance
在该模式下,我们会为目标 Activity 创建一个新的 Task 栈,将目标 Activity 放入新的 Task,并让目标 Activity获得焦点。新的 Task 有且只有这一个 Activity 实例。 如果已经创建过目标 Activity 实例,则 不会创建新的 Task,而是将以前创建过的 Activity 唤醒。
在Java文件中动态设置启动模式
在上述所有情况,都是我们在Manifest中通过 launchMode 属性设置的,这个被称为静态设置,动态设置是通过 Java 代码设置的。
通过 Intent 动态设置 Activity 启动模式
intent.setFlags();
如果同时有动态和静态设置,那么动态的优先级更高。接下来我们来看一下如何动态的设置 Activity 启动模式。
FLAG_ACTIVITY_NEW_TASK
对应 Flag:
Intent.FLAG_ACTIVITY_NEW_TASK
此 Flag 跟 singleInstance 很相似,在给目标 Activity 设立此 Flag 后,会根据目标 Activity 的 affinity 进行匹配,如果已经存在与其 affinity 相同的 task,则将目标 Activity 压入此 Task。
反之没有的话,则新 建一个 task,新建的 task 的 affinity 值与目标 Activity 相同,然后将目标 Activity 压入此栈。
但它与 singleInstance 有不同的点,两点需要注意的地方:
- 新的 Task 没有说只能存放一个目标 Activity,只是说决定是否新建一个 Task,而 singleInstance 模式下新的 Task 只能放置一个目标 Activity。
- 在同一应用下,如果 Activity 都是默认的 affinity,那么此 Flag 无效,而 singleInstance 默认情况也会创建新的 Task。
FLAG_ACTIVITY_SINGLE_TOP
该模式比较简单,对应 Flag :
Intent.FLAG_ACTIVITY_SINGLE_TOP
此 Flag 与静态设置中的 singleTop 效果相同
FLAG_ACTIVITY_CLEAR_TOP
对应的Flag
Intent.FLAG_ACTIVITY_CLEAR_TOP
当设置此 Flag 时,目标 Activity 会检查 Task 中是否存在此实例,如果没有则添加压入栈。如果有,就将位于 Task 中的对应 Activity 其上的所有 Activity 弹出栈,此时有以下两种情况:
- 如果同时设置 Flag_ACTIVITY_SINGLE_TOP ,则直接使用栈内的对应 Activity。
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_SINGLE_TOP);
- 没有设置,则将栈内的对应 Activity 销毁重新创建。