Android 开发艺术探索 学习 day01
Activity
1.1.1典型情况下的生命周期分析
(1)onCreate:
表示Activity正在被创建,这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。
(2)onRestart:
表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会暂停,也就是onPause和onStop被执行了,接着用户又回到了这个Activity,就会出现这种情况。
(3)onStart:
(角度 :是否可见)
表示Activity正在被启动,即将开始,这时Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候其实可以理解为Activity已经显示出来了,但是我们还看不到。 (可见不代表立马显示出来)
(4)onResume:
(角度 :是否位于前台)
表示Activity已经可见了,并且出现在前台并开始活动。要注意这个和onStart的对比,onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
(5)onPause:
(角度 :是否位于前台)
表示Activity正在停止,正常情况下,紧接着onStop就会被调用。此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示
onPause必须先执行完,新Activity的 onCreate onStart onResume才会执行。
(6)onStop:
(角度 :是否可见)
表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。
(7)onDestroy:
表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。
activity启动流程
1.1.2异常情况下的生命周期分析
我们知道,Activity除了受用户操作所导致的正常的生命周期方法 调度,还有一些异常情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。
1.情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在onStop之前,需要强调的一点是,这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。
当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。
因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState的调用时机在onStart之后。
如图所示,Activity被销毁了以后调用了onSavelnstanceState来保存数据,重新创建以后在onCreate和onRestorelnstanceState中都能够正确地恢复我们之前存储的字符串。
这个例子很好地证明了上面我们的分析结论。
2.情况2:资源内存不足导致低优先级的Activity被杀死
Activity的优先级情况。
Activity按照优先级从高到低,可以分为如下三种:
- 前台Activity--正在和用户交互的Activity,优先级最高。
- 可见但非前台Activity—--比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
- 后台Activity-y—已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后 续通过onSavelnstanceState和onRestorelnstanceState来存储和恢复数数据。
当系统配置发生改变后,有没有办法不重新创建呢?
系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity,可以给Activity指定configChanges属性。比如不想让Activity在主题模式发生改变的时候重建,就可以给configChanges属性添加uiMode这个值,如下所示。
android:configChanges="uiMode′
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" />
<activity
android:name="com.ryg.chapter_1.MainActivity"
android:configChanges="orientation|uiMode"
android:label="@string/app_name"
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
1.2.1 Activity的LaunchMode
1. taskAffinity
taskAffinity 决定 Activity 更“倾向”进入哪个 Task
- 默认:包名
singleTask/NEW_TASK时特别重要
<activity
android:name=".HomeActivity"
android:launchMode="singleTask"
android:taskAffinity="com.yaxon.home" />
效果:
- 可能被拉到 指定 Task
- 即使从 Service / 系统进程启动
2. 任务栈
任务栈(Task)到底是什么?
- 用来“装 Activity 的栈结构”, Activity 永远运行在某一个 Task 里
- 一个后进先出(LIFO)的 Activity 栈
特点:
- 栈顶 Activity 是 当前正在显示的页面
- 按 Back 键 → 栈顶 Activity 出栈
- 栈空了 → Task 结束(App 回到后台或桌面)
Activity 和 Task 的基本规则(重点)
1️⃣ Activity 必须属于某一个 Task
- 不存在“游离的 Activity”
- 启动 Activity 时,系统要先决定: 进哪个 Task?要不要新建 Task?
2️⃣ 一个 Task 可以包含多个 Activity
典型结构:
Task1:
A → B → C
Back 行为:
C → B → A → 桌面
3️⃣ 一个 Activity 可以被创建多次,存在于多个 Task
比如 standard:
Task1: A → B → A
Task2: A
👉 所以 Activity 是类,实例是可以有多个的
为什么不能“一个进程一个栈”?
你说得非常对: “我一个进程里有很多 Activity,为啥还要多个任务栈?”
因为 Activity 的来源不同、用户意图不同。
举个最真实的例子
用户正在:
- 在 微信 聊天(Task A)
- 点击一个 网页链接 → 打开浏览器(Task B)
- 从浏览器点 分享 → 又回微信(Task A)
如果 只有一个栈:
微信 → 浏览器 → 微信
Back 键会变成:
微信 ← 浏览器 ← 微信 ← …
完全不符合用户直觉。
Task 的真实语义 : “一次完整的用户操作链”
Task ≈ 用户的一次操作任务
- 点桌面图标 → 一个 Task
- 点通知 → 可能是另一个 Task
- 从别的 App 分享进来 → 新 Task 或已有 Task
👉 Task 是从“用户视角”设计的,不是程序员视角
系统选择 Task 的决策流程(重点)**
当你 startActivity() 时,系统会依次判断:
第一步:Intent 里有没有明确要求?
1️⃣ FLAG_ACTIVITY_NEW_TASK
含义
- 不进当前 Task
- 去找一个“合适的 Task”
- 找不到就新建
合适 = taskAffinity 匹配
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Service / Broadcast 启 Activity 必须用它
第二步:Activity 的 launchMode 是什么?
singleInstance
- 直接去它独占的 Task
- 不用纠结了
singleTask
- 系统先找有没有包含它的 Task
- 找到就复用
- 找不到就新建 Task
第三步:taskAffinity(任务亲和性)
这是 系统选 Task 的“名字标签”
- 每个 Task 都有 affinity
- 每个 Activity 也有 affinity
系统规则
- affinity 相同 → 倾向进同一个 Task
- 不同 → 分栈
android:taskAffinity="com.yaxon.home"
第四步:默认行为(最常见)
如果以上都没命中:
👉 进当前 Task
也就是:
startActivity()
3. Activity 四个启动模式
一、一句话总览
standard 👉 不管在哪、有没有,永远新建一个 Activity,放进当前任务栈
singleTop 👉 只有当它已经在当前任务栈栈顶时才复用,否则照样新建
singleTask 👉 系统中只允许存在一个实例, 👉 如果已存在,整个任务栈被拉到前台,并清掉它上面的页面
singleInstance 👉 系统中唯一实例 + 独占一个任务栈, 👉 这个任务栈里永远只有它自己
二、从“系统到底干了什么”来总结(本质版)
standard
- 系统行为: new Activity → 压入当前 Task
- 从不检查历史
- 不会触发 onNewIntent
singleTop
-
系统行为:
- 如果 栈顶就是它 → 复用
- 否则 → new
-
只关心当前 Task 的栈顶
-
可能存在多个实例
singleTask (重要)
-
系统行为:
- 全系统查找该 Activity
- 找到 → 清栈 + bring Task to front
- 找不到 → new(可能新建 Task)
-
该 Activity 在系统中只有一个实例
-
一定会触发 onNewIntent(复用时)
singleInstance
-
系统行为:
- 全系统查找
- 有就直接切到它的 Task
- 没有就新建 独占 Task
-
不允许其他 Activity 和它共栈
-
强制打断当前任务
三、怎么使用?
如何给Activity指定启动模式呢?
方式一:xml配置
第一种是通过AndroidMenifest为Activity指定启动模式
<activity
android:name="com.ryg.chapter_1.SecondActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"
android:label="@string/app_name" />
方式二:intent.addFlags
另一种情况是通过在lntent中设置标志位来为Activity指定启动模式
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
这两种方式都可以为Activity指定启动模式,但是二者还是有区别的。
- 第二种方式的优先级要高于第一种,当两种同时存在时,以第二种方式为准;
- 第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,
- 而第二种方式无法为Activity指定singlelnstance模式。
4. Activity的Flags
一、车机里最核心的 5 个 Activity Flag(必须记)
1️⃣ FLAG_ACTIVITY_NEW_TASK(车机第一重要)
作用
- 允许在“没有任务栈的上下文”里启动 Activity
- 会让系统去 选择 / 新建一个 Task
为什么车机必须用
- MCU / Service / SystemServer 没有 Activity 栈
- 不加它 → 直接 crash
典型场景
- MCU 按键拉起 Home
- Service 拉起 Settings / Bluetooth
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
⚠️ 车机铁律
Service 启 Activity,不加 NEW_TASK = 必死
2️⃣ FLAG_ACTIVITY_CLEAR_TOP(回到已有页面)
作用
- 如果目标 Activity 已存在于当前 Task
- 清掉它上面的所有 Activity
- 自身不会重建 常见组合(配合 CLEAR_TOP + singleTop (activity 启动模式))
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
真实场景: 返回设置首页
Task:
Home → Settings → Bluetooth → DeviceDetail
现在你在 DeviceDetail,想 “回到 Settings 首页”
目标 Activity:Settings
只用
CLEAR_TOP(很多人以为这样就够了)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
系统真实行为
- 在当前 Task 里找到
Settings - 清掉它上面的 Activity 👉
Bluetooth、DeviceDetail出栈
此时栈变成:
Home → Settings
⚠️ 关键问题来了
Settings 会不会重建?
👉 不确定,取决于它的 launchMode
-
如果是
standard- Settings 会被销毁再重新创建
- 走完整生命周期
-
如果是
singleTop- 不会重建
- 直接复用,走
onNewIntent()
3️⃣ FLAG_ACTIVITY_SINGLE_TOP(Flag 版 singleTop)
作用
- 和 launchMode singleTop 行为一致
- 只是“临时规则”
典型场景
- 通知 / 物理按键 / 重复点击
- 不想改 Manifest
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
一般可以配合FLAG_ACTIVITY_CLEAR_TOP 一起使用
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK
);
4️⃣ FLAG_ACTIVITY_CLEAR_TASK(清空整个 Task)
作用
- 清空目标 Task 里的所有 Activity
- 通常和 NEW_TASK 一起用
典型车机场景
- 用户切换账号
- 系统恢复出厂
- 模式切换(司机 / 乘客)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
⚠️ 极度危险
用之前一定想清楚 Back / Recent 行为
5️⃣ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED(Launcher 专用)
作用
- 系统决定是否重置 Task
- 常见于 Launcher 启动 App
车机场景
- Launcher 点第三方 App
一般你不用手动写,系统会加。
Launcher 启 App,本质是:
“把这个 App 的 主任务栈 恢复成一个干净、可预期的状态”
举个真实例子
App 上次退出时任务栈是:
Main → Detail → SubDetail
用户现在点 Launcher 图标,期望看到的是:
Main
而不是:
SubDetail
RESET_TASK_IF_NEEDED 干了什么
当系统发现:
- 这个 Task 是从 Launcher 启动
- 且 Task 状态“不干净”
系统就会:
- 清理非 root Activity
- 或重建 root Activity
👉 让用户“感觉像刚打开 App”
二、车机里常见“黄金组合”(直接照抄)
🔹 MCU / Service 拉起 Home
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
(Home 本身用 singleTask + affinity)
🔹 回到设置首页(不新建)
intent.addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_SINGLE_TOP
);
🔹 强制回 Home(清干净)
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK
);
🔹 通知 / 物理键重复打开同一页面
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);