fix: delete agent 500 error + dynamic personality + deployment guide

- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions,
  schedules, executions, team_members) and unbind goals/tasks before delete
- Remove hardcoded personality templates in Android, replace with dynamic
  system prompt generation from name + description
- Set promptSectionsEnabled=false to bypass PromptComposer for personality
- Add Tencent Cloud Linux deployment guide (Docker Compose)
- Accumulated backend service updates, frontend UI fixes, Android app changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 01:17:21 +08:00
parent 86b98865e3
commit beff3fac8d
1084 changed files with 117315 additions and 1281 deletions

View File

@@ -0,0 +1,334 @@
# 阶段③·模块A 第1课AOSP构建系统概述 —— Soong、Blueprint与Ninja的关系
> **阅读时间**约10分钟
> **前置知识**Linux基础、C语言基础、了解AOSP源码目录结构
> **学习目标**理解AOSP构建系统的三层架构能说出Soong/Blueprint/Ninja各自的角色
---
## 🎯 本课目标
学完本课后,你将能够:
| 序号 | 能力 |
|:---:|:-----|
| 1 | 说出AOSP构建系统经历了哪三个阶段Make → Soong → ? |
| 2 | 画出Soong/Blueprint/Ninja三层调用关系图 |
| 3 | 解释 `Android.bp``Android.mk` 的本质区别 |
| 4 | 在源码树中找到构建系统的核心入口文件 |
---
## 1. 为什么需要专门的构建系统?
假设你有 **4000+ 个模块**,每个模块有自己的源码、依赖、编译选项。如果手动逐个编译:
```
$ gcc -IlibA/include -IlibB/include -c my_module.c
$ gcc my_module.o libA.a libB.so -o my_binary
```
模块一多,噩梦就来了:
```
libA ──→ libC ──→ libF
↘ ↘ ↗
my_app ──→ libB ──→ libD ─┘
libE
...4000个模块互相交织
```
**构建系统的核心使命**:自动管理依赖关系、并行编译、增量构建、产物管理。
---
## 2. AOSP构建系统的三代演进
```
2008 2016 2020+
| | |
Android.mk Android.bp ???
(GNU Make) (Blueprint+Soong) (Bazel?)
| | |
基于Makefile 像JSON的声明式 Google内部趋势
大量宏函数 语法,自动转 (但目前Soong
难以维护 Ninja构建文件 仍是主流)
```
### 第一代Make2008-2016
```
[Android.mk] ──→ [GNU Make] ──→ [编译产物]
(人写) (解析执行) (out/target/...)
```
- 本质上是 Makefile用大量 GNU Make 宏函数封装
- 典型写法:`include $(BUILD_SHARED_LIBRARY)` 背后展开为几百行Make规则
- **痛点**:语法晦涩,错误信息可读性差,扩展困难
### 第二代Soong + Blueprint + Ninja2016-至今)
```
[Android.bp] ──→ [Blueprint] ──→ [Soong] ──→ [build.ninja] ──→ [Ninja] ──→ [编译产物]
声明式描述 语法解析+ 模块规则 Ninja构建文件 高速并行
依赖分析 生成引擎 执行器
```
> 🔑 **关键转变**:从「告诉系统怎么做」变成「告诉系统我要什么」
### 第三代Bazel探索中
Google内部大规模使用BazelBlaze的开源版AOSP也在逐步试验。但目前Android 14/15Soong仍是绝对主流。
---
## 3. 三层架构深度解析
```
┌─────────────────────────────────────────────────────┐
│ 开发者编写 │
│ Android.bp / Android.mk 约4000+个文件) │
│ "我需要编译 libutils.so依赖 liblog" │
└──────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Blueprint 层 │
│ │
│ · 解析 Android.bp 语法类JSON
│ · 构建模块依赖图DAG
│ · 生成中间表示IR传给 Soong │
│ │
│ 位置: build/blueprint/ │
└──────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Soong 层 │
│ │
│ · 用 Go 语言编写(替代 Make 的宏函数) │
│ · 每个模块类型对应一个 Go 结构体 │
│ · 生成实际的编译规则 │
│ │
│ 核心模块类型: │
│ cc_library, cc_binary, java_library, │
│ android_app, prebuilt_binary, ... │
│ │
│ 位置: build/soong/ │
└──────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Ninja 层 │
│ │
│ · 产物: out/combined-<product>.ninja │
│ · 精简化 Make只处理文件级依赖 │
│ · 极快AOSP全量编译生成约150万行 .ninja 文件 │
│ · 并行调度自动利用多核CPU │
│ │
│ 特点: 无变量、无函数、无条件,纯粹的文件依赖图 │
└──────────────────────┬──────────────────────────────┘
[编译产物]
out/target/product/<device>/
```
---
## 4. Android.bp vs Android.mk —— 新旧对比
### Android.mkMake风格
```makefile
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libutils
LOCAL_SRC_FILES := \
String8.cpp \
Unicode.cpp \
VectorImpl.cpp
LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
include $(BUILD_SHARED_LIBRARY)
```
### Android.bp声明式
```json
cc_library_shared {
name: "libutils",
srcs: [
"String8.cpp",
"Unicode.cpp",
"VectorImpl.cpp",
],
shared_libs: [
"liblog",
"libcutils",
],
export_include_dirs: ["include"],
}
```
> **一眼就能看出区别**`Android.bp` 不需要 `CLEAR_VARS`、不需要 `include` 宏展开、不需要关心变量作用域。它就是纯粹的数据描述。
### 关键差异表
| 维度 | Android.mk | Android.bp |
|:-----|:-----------|:-----------|
| 语言 | GNU Make | 类JSON声明式 |
| 变量作用域 | 全局污染,需手动清理 | 模块级隔离 |
| 条件编译 | Make ifeq/ifneq | soong_config_module_type |
| 类型安全 | 无,运行时报错 | 解析阶段即可发现语法错误 |
| 可读性 | 差(宏嵌套) | 好(结构化) |
| 迁移 | — | 可用 `androidmk` 工具自动转换 |
---
## 5. 源码位置速查
```
AOSP源码根目录/
├── build/
│ ├── soong/ ← SoongGo语言写的构建引擎
│ │ ├── cmd/
│ │ │ └── soong_ui/ ← 用户入口 soong_ui
│ │ ├── android/ ← Android.bp 模块类型定义
│ │ └── cc/ ← C/C++ 模块规则
│ │
│ ├── blueprint/ ← Blueprint语法解析器
│ └── make/ ← 旧 Make 系统(兼容层)
├── out/
│ ├── soong/
│ │ └── build.ninja ← 中间 Ninja 文件
│ └── combined-<product>.ninja ← 最终 Ninja 构建文件
└── <模块目录>/
└── Android.bp ← 每个模块的构建描述文件
```
> 📌 **记忆口诀**`build/soong` 是引擎,`build/blueprint` 是语法,`Android.bp` 是原料,`out/*.ninja` 是施工图Ninja 是施工队。
---
## 6. 一次构建的完整调用链
当你在AOSP根目录执行 `m libutils`
```
Step 1: soong_ui (build/soong/cmd/soong_ui/)
│ 用户入口脚本,收集参数
Step 2: 扫描所有 Android.bp 文件
│ Blueprint 解析语法,构建模块名→定义的映射表
Step 3: Soong 处理 "libutils" 这个模块
│ 找到它的模块类型cc_library_shared
│ 调用对应的 Go 代码生成编译规则
Step 4: 生成 Ninja 文件片段
│ rule cc_compile { command = clang ... }
│ build String8.o: cc_compile String8.cpp
Step 5: 合并所有 Ninja 片段
│ → out/combined-<product>.ninja
Step 6: Ninja 执行
│ 读取 .ninja 文件
│ 检查依赖 → 只编译改动过的文件(增量)
│ 并行调度 → 充分利用CPU
Step 7: 产物输出
out/target/product/<device>/system/lib64/libutils.so
```
---
## 7. 💡 AOSP场景加油站
### 场景1你修改了 `frameworks/native/libs/binder/Binder.cpp`
```bash
# 你只需要执行:
$ m libbinder
# Ninja 会:
# ✓ 检测到 Binder.cpp 的 mtime 变化
# ✓ 重新编译 Binder.o
# ✓ 重新链接 libbinder.so
# ✓ 跳过所有未改动的依赖(可能节省几十分钟)
```
### 场景2你想知道 `libutils` 被哪些模块依赖
```bash
# 检查反向依赖
$ grep -r "libutils" --include="Android.bp" frameworks/ system/
# 或者用 Soong 自带的分析工具
$ build/soong/soong_ui.bash --dumpvar-mode --all-modules | grep libutils
```
### 场景3旧 mk 转新 bp
```bash
# AOSP 内置转换工具
$ androidmk Android.mk > Android.bp
# 大多数简单模块能一键转换,复杂宏可能需要手动调整
```
---
## 8. 常见误区澄清
| 误区 | 真相 |
|:-----|:-----|
| ❌ "Soong 替代了 Ninja" | ✅ Soong 生成 Ninja 文件Ninja 负责执行 |
| ❌ "Android.bp 是 YAML/JSON" | ✅ 是类JSON的自定义语法不是标准JSON |
| ❌ "Android.mk 已经废弃" | ✅ 仍可混用,但新模块应使用 Android.bp |
| ❌ "Blueprint 是 Google 发明的" | ✅ 基于 Google 内部 Blueprint 框架,专为 AOSP 定制 |
| ❌ "m 命令直接用 Make" | ✅ `m` 最终调用的是 `soong_ui`Make 只是兼容层 |
---
## 📝 本课检查清单
- [ ] 能画出 Soong → Blueprint → Ninja 的三层调用图
- [ ] 理解 Android.bp 声明式语法 vs Android.mk 宏式语法的区别
- [ ] 知道 `build/soong/``build/blueprint/` 的职责分工
- [ ] 能说出 `m <module>` 命令背后发生了什么
- [ ] 在源码中找到至少 3 个不同模块的 Android.bp 文件
---
## 🧪 动手练习
1. **浏览构建系统源码**
```bash
cd $AOSP_ROOT
ls build/soong/android/ # 查看所有模块类型定义
ls build/blueprint/ # 查看 Blueprint 解析器
```
2. **找找身边的 Android.bp**
```bash
find frameworks/native -name "Android.bp" | head -5
cat frameworks/native/libs/binder/Android.bp # 选一个阅读
```
3. **尝试 androidmk 转换**(如果你有旧的 Android.mk
```bash
androidmk old_Android.mk
```
---
> **下节课预告**第2课《Android.bp 文件语法详解》—— 深入模块类型、变量作用域、条件编译等核心语法。
---
*本课完成时间约10分钟 | 属于 AOSP 8阶段学习路线 · 阶段③ 模块A*

View File

@@ -0,0 +1,630 @@
# 阶段⑤ 模块A 第1课AMS 进程管理 —— 从 Zygote fork 到 OOM Adj 全链路
> **课程定位**:阶段⑤ System Server 核心服务深度解析 · 模块A AMS深度 第1课
> **难度等级**:⭐⭐⭐⭐⭐(深度源码级)
> **预计阅读**25~30 分钟
> **前置课程**阶段③模块B第8课Zygote与SystemServer、阶段③模块C第12课AMS入门
> **源码基准**AOSP 14.0.0_r50(兼容 Android 13/15 核心逻辑)
---
## 🎯 课程目标
学完本课后,你将能够:
| 序号 | 能力目标 | 检验方式 |
|:---:|:---------|:--------|
| ① | 画出「点击图标→进程创建→应用启动」的完整时序图 | 默写8步链路 |
| ② | 解释 ProcessRecord 中 6 个关键字段的含义与作用 | 源码标注 |
| ③ | 手动计算任意进程的 oom_score_adj并说明依据 | 终端实操 |
| ④ | 解释 lmkd 的五级杀进程策略与阈值配置 | 画表对比 |
| ⑤ | 使用 `dumpsys activity processes` 诊断进程状态 | 实战输出 |
---
## 📐 前置知识检查
在进入本课前,确认你已经掌握:
- [x] Zygote 的 socket 通信机制(`/dev/socket/zygote`
- [x] Binder IPC 基本原理(进程间调用链路)
- [x] Linux `/proc` 文件系统基础(`/proc/[pid]/oom_score_adj`
- [x] AMS 的启动流程SystemServer → startBootstrapServices
- [ ] ⚠️ 如果不熟悉以上内容请先复习阶段③模块B/C 对应课程
---
## 🧱 一、整体架构:进程创建全链路概览
Android 应用进程的诞生,是一条跨越 **4 个进程、3 种 IPC 机制** 的精密流水线:
```
用户点击图标
┌──────────────────────────────────────────────────────────┐
│ ① Launcher (App进程) │
│ startActivity(intent) │
│ └─ Binder IPC ──────────────────────────────────┐ │
└─────────────────────────────────────────────────────│─────┘
┌──────────────────────────────────────────────────────────┐
│ ② system_server (SystemServer进程) │
│ ActivityManagerService.startActivity() │
│ │ │
│ ├─ 解析 Intent / 权限检查 / 栈管理 │
│ │ │
│ └─ ProcessList.startProcessLocked() │
│ │ │
│ ├─ 构造 ProcessRecord 对象 │
│ ├─ 计算 oom_adj (默认 HOME_APP_ADJ=6) │
│ └─ ZygoteProcess.start() │
│ └─ Socket IPC ────────────────────┐ │
└───────────────────────────────────────────────────│───────┘
┌──────────────────────────────────────────────────────────┐
│ ③ Zygote (Zygote进程PID=1的子进程) │
│ ZygoteServer.runSelectLoop() 收到命令 │
│ │ │
│ ├─ ZygoteArguments 解析 (uid/gid/类名/参数) │
│ ├─ Zygote.forkAndSpecialize() │
│ │ └─ nativeForkAndSpecialize() │
│ │ └─ fork() 系统调用 ★ 此刻新进程诞生 │
│ │ │
│ └─ specializeAppProcess() │
│ ├─ setgroups/setuid/setgid (设置进程身份) │
│ ├─ mount 命名空间隔离 │
│ └─ RuntimeInit.applicationInit() │
│ └─ ActivityThread.main() ◄────────┐ │
└───────────────────────────────────────────────────│───────┘
┌──────────────────────────────────────────────────────────┐
│ ④ 新应用进程 (com.example.app) │
│ ActivityThread.main() │
│ │ │
│ ├─ Looper.prepareMainLooper() │
│ ├─ ActivityThread.attach(false, startSeq) │
│ │ └─ Binder IPC ──► AMS.attachApplication() │
│ │ │
│ └─ Looper.loop() (进入消息循环) │
│ │
│ AMS 回调链路: │
│ AMS.attachApplication() │
│ ├─ ApplicationThread.bindApplication() │
│ │ └─ makeApplication() → onCreate() │
│ └─ realStartActivityLocked() │
│ └─ Activity.onCreate() ★ UI 显示 │
└──────────────────────────────────────────────────────────┘
```
> **关键观察**:从 `fork()` 到 `Activity.onCreate()`,一条调用链跨了 **8 个主要步骤**,涉及 **Socket + Binder** 两种 IPC中间任意一环出问题都会导致 ANR 或启动崩溃。
### 1.1 三种 IPC 在进程创建中的角色
| IPC 类型 | 使用场景 | 传输内容 | 性能特点 |
|:---------|:--------|:--------|:--------|
| **Binder** | Launcher→AMS、App→AMS | Intent、Binder 对象 | 同步调用,有返回值 |
| **Socket** | AMS→Zygote | fork 命令、args 字符串 | 单向命令,无需返回 |
| **fork()** | Zygote→子进程 | 整个进程空间CoW | 内核级,极快 |
---
## 🔬 二、ProcessRecord 数据结构深度解析
`ProcessRecord` 是 AMS 中描述一个进程的"档案袋",位于:
```
frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java
```
### 2.1 核心字段分类6大类
```
ProcessRecord
├─ 📛 [身份标识]
│ ├─ pid : int ← Linux 进程 ID
│ ├─ uid : int ← Android 用户 ID
│ ├─ processName : String ← 如 "com.android.phone"
│ ├─ userId : int ← 多用户场景的用户 ID
│ └─ info : ApplicationInfo ← 从 PM 查询的 App 信息
├─ 📊 [状态标志]
│ ├─ curAdj : int ← 当前 OOM Adj 值 ★核心★
│ ├─ setAdj : int ← 被设定的目标 Adj
│ ├─ curSchedGroup : int ← 调度组(前台/后台)
│ ├─ adjType : String ← Adj 来源类型(如 "activity"
│ ├─ adjSource : Object ← Adj 来源对象
│ └─ pendingUiClean: boolean ← 是否有待清理 UI
├─ 📞 [Binder 通信]
│ ├─ thread : IApplicationThread ← App 端 Binder 句柄
│ ├─ procLock : Object ← 进程级锁
│ └─ renderThreadTid: int ← 渲染线程 TID
├─ 📦 [组件容器]
│ ├─ activities : ArrayList<ActivityRecord>
│ ├─ services : ArraySet<ServiceRecord>
│ ├─ providers : ArraySet<ContentProviderRecord>
│ ├─ receivers : ArraySet<BroadcastFilter>
│ └─ pkgList : ArraySet<String> ← 该进程加载的包名
├─ ⏱️ [时间戳]
│ ├─ startTime : long ← 进程创建时间
│ ├─ lastActivityTime : long ← 最后活跃时间
│ ├─ lastPss : long ← 最近一次 PSS 内存值
│ └─ whenUnimportant : long ← 变为非关键进程的时刻
└─ 🧠 [内存与状态]
├─ maxAdj : int ← 该进程允许的最大 Adj
├─ curProcState : int ← 当前进程状态PROCESS_STATE_*
├─ setProcState : int ← 目标进程状态
└─ mProfile : ProfilerInfo ← 性能分析配置
```
### 2.2 curAdj 与 setAdj 的"双值设计"
这是理解进程管理的关键设计模式:
```java
// ProcessRecord.java (简化逻辑)
int curAdj; // ★ 内核中 /proc/[pid]/oom_score_adj 的当前值
int setAdj; // ★ AMS 期望设定的值(尚未同步到内核)
// 同步发生在:
void applyOomAdjLocked() {
if (curAdj != setAdj) {
// 通过 lmkd socket 或直接写 /proc 同步
PlatformState.setOomAdj(pid, uid, setAdj);
curAdj = setAdj; // 同步完成
}
}
```
> **设计精髓**`setAdj` 是"意图"`curAdj` 是"现实"。AMS 可以在一个事务中多次修改 `setAdj`,最后只做一次内核同步,大幅减少系统调用。
### 2.3 进程状态枚举 (PROCESS_STATE_*)
`curAdj` 对应的是 `curProcState`,它比 Adj 更细粒度:
| 常量名 | 值 | 含义 | 对应 Adj |
|:-------|:---|:-----|:--------|
| `PROCESS_STATE_PERSISTENT` | -3 | 系统持久进程(永不杀) | -800 |
| `PROCESS_STATE_PERSISTENT_UI` | -2 | 系统持久UI进程 | -800 |
| `PROCESS_STATE_TOP` | 2 | 前台拥有焦点Activity | 0 |
| `PROCESS_STATE_BOUND_TOP` | 1 | 绑定到TOP进程的服务 | 50 |
| `PROCESS_STATE_FOREGROUND_SERVICE` | 3 | 前台服务 | 200 |
| `PROCESS_STATE_BOUND_FOREGROUND_SERVICE` | 4 | 绑定到前台服务 | 250 |
| `PROCESS_STATE_IMPORTANT_FOREGROUND` | 5 | 对前台重要的进程 | 300 |
| `PROCESS_STATE_IMPORTANT_BACKGROUND` | 6 | 重要后台进程 | 400 |
| `PROCESS_STATE_TRANSIENT_BACKGROUND` | 7 | 短暂后台 | 500 |
| `PROCESS_STATE_BACKUP` | 8 | 备份进程 | 600 |
| `PROCESS_STATE_SERVICE` | 9 | 有Service在运行 | 700 |
| `PROCESS_STATE_RECEIVER` | 10 | 正在接收广播 | 700 |
| `PROCESS_STATE_TOP_SLEEPING` | 11 | 休眠的TOP | 325 |
| `PROCESS_STATE_HEAVY_WEIGHT` | 12 | 重量级进程 | 800 |
| `PROCESS_STATE_HOME` | 13 | Launcher进程 | 900 |
| `PROCESS_STATE_LAST_ACTIVITY` | 14 | 上一个Activity | 900 |
| `PROCESS_STATE_CACHED_ACTIVITY` | 15 | 已缓存的Activity | 906 |
| `PROCESS_STATE_CACHED_RECENT` | 16 | 最近使用的缓存 | 906 |
| `PROCESS_STATE_CACHED_ACTIVITY_CLIENT` | 17 | 缓存Activity客户端 | 946 |
| `PROCESS_STATE_CACHED_EMPTY` | 18 | 空进程(最容易被杀) | 1001 |
| `PROCESS_STATE_NONEXISTENT` | 19 | 不存在 | 1001 |
---
## 🎚️ 三、OOM Adj 体系与调整机制
### 3.1 OOM Adj 的全景分级
Android 定义了完整的 OOM Adj 范围,从 -1000绝对不杀到 1001随时可杀
```
Adj 值
1001 ┤ CACHED_EMPTY (空进程) ← 首先被杀
906 ┤ CACHED_ACTIVITY (已缓存Activity)
900 ┤ LAST_ACTIVITY / HOME_APP
800 ┤ HEAVY_WEIGHT (重量级)
700 ┤ SERVICE / RECEIVER
600 ┤ BACKUP (备份)
500 ┤ TRANSIENT_BACKGROUND
400 ┤ IMPORTANT_BACKGROUND
300 ┤ IMPORTANT_FOREGROUND
250 ┤ BOUND_FOREGROUND_SERVICE
200 ┤ FOREGROUND_SERVICE (前台服务)
50 ┤ BOUND_TOP (绑定到TOP)
0 ┤ TOP (前台焦点App) ← 最高优先级
-10 ┤ PERCEPTIBLE (可感知)
-100 ┤ VISIBLE (可见但非焦点)
-700 ┤ SYSTEM (系统进程)
-800 ┤ PERSISTENT (持久进程) ← 永不杀
-1000 ┤ NATIVE (Native守护进程)
```
### 3.2 Adj 调整的核心方法:`computeOomAdjLocked()`
这是 AMS 中 **最重要、最复杂** 的方法之一,位于:
```
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
```
核心逻辑提炼:
```java
// === OomAdjuster.computeOomAdjLocked() 核心流程图 ===
//
// 输入ProcessRecord app
// │
// ▼
// ① 检查是否前台 (isForeground)
// ├─ 有 TOP Activity → curAdj=0, procState=TOP
// │ └─ return (最快路径)
// │
// ▼
// ② 检查前台服务 (foregroundServices)
// ├─ startForeground() 调用过?
// │ └─ curAdj=FOREGROUND_SERVICE_ADJ (200)
// │
// ▼
// ③ 检查绑定连接 (connections)
// ├─ 被前台进程绑定?
// │ └─ curAdj=BOUND_FOREGROUND_SERVICE (250)
// │
// ▼
// ④ 检查 Provider 客户端
// ├─ 被前台进程通过 ContentProvider 依赖?
// │ └─ curAdj 按连接数加权
// │
// ▼
// ⑤ 检查 Backup
// ├─ 正在执行备份?
// │ └─ curAdj=BACKUP_APP_ADJ (600)
// │
// ▼
// ⑥ 检查 Service (非前台)
// ├─ 有 Service 在运行?
// │ └─ curAdj=SERVICE_ADJ (700)
// │
// ▼
// ⑦ 检查广播接收
// ├─ 正在接收广播?
// │ └─ curAdj=SERVICE_ADJ (700)
// │
// ▼
// ⑧ 检查 ContentProvider
// ├─ 有 Provider 被客户端使用?
// │ └─ curAdj 按最严重客户端计算
// │
// ▼
// ⑨ 缓存分类
// ├─ 有 Activity (已stop) → CACHED_ACTIVITY (906)
// ├─ 有 Activity Client → CACHED_ACTIVITY_CLIENT (946)
// └─ 空进程 → CACHED_EMPTY (1001)
```
### 3.3 实战:用 dumpsys 观察 Adj 变化
```bash
# 终端1观察所有进程的 Adj
adb shell dumpsys activity processes | grep -E "Proc #|oom:"
# 输出示例:
# Proc #0: fg TOP/A/TPS trm: 0 31526:com.android.chrome/u0a282 (top-activity)
# oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0 ← Adj=0前台
# Proc #1: vis FGS/A/TPS trm: 0 31872:com.spotify.music/u0a303 (fg-service)
# oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200 ← Adj=200前台服务
# Proc #2: svc SVC/A/TPS trm: 0 32101:com.android.music/u0a123 (service)
# oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700 ← Adj=700Service
# Proc #3: cch CEM/A/TPS trm: 0 32055:com.example.app/u0a456 (cch-empty)
# oom: max=1001 curRaw=1001 setRaw=1001 cur=1001 set=1001 ← Adj=1001空缓存
# 终端2直接读取 /proc 验证
adb shell cat /proc/31526/oom_score_adj # 应输出 0
adb shell cat /proc/32055/oom_score_adj # 应输出 1001
```
> **提示**`curRaw`/`setRaw` 是 AMS 内部值(乘以 1000`cur`/`set` 是映射后的 lmkd 值。两者通常一致,但在某些 OEM 定制 ROM 中可能不同。
---
## 🗡️ 四、lmkd —— 低内存杀手守护进程
### 4.1 lmkd 的架构定位
```
用户空间 内核空间
───────── ─────────
AMS (system_server)
│ applyOomAdjLocked()
│ 写入 /proc/[pid]/oom_score_adj
├──────────────────────────────────────────┐
│ │
▼ ▼
lmkd (native daemon) Linux OOM Killer
│ socket: /dev/socket/lmkd │ (内核原生)
│ │
├─ 监听 memory pressure event │
│ (来自内核 PSI/vmpressure) │
│ │
├─ 策略评估:是否需要杀进程? │
│ ├─ 按 adj 从高到低选目标 │
│ └─ 按内存占用排序 │
│ │
└─ kill(pid, SIGKILL) ──────────────► 进程被杀
```
> **历史演进**Android 10 之前使用内核 LMKLow Memory Killer驱动Android 10+ 逐步迁移到用户空间 lmkd。Android 12+ 全面使用 lmkd + PSIPressure Stall Information监控。
### 4.2 lmkd 的五级杀进程策略
```
内存压力等级 (由 PSI 提供)
═══════════════════════════════════════════════════════
LEVEL │ 条件 │ 杀进程 Adj 范围
─────────┼───────────────────┼─────────────────────────
LOW │ 内存轻微不足 │ adj >= 900 (缓存进程)
MEDIUM │ 内存中度不足 │ adj >= 800 (重量级+)
HIGH │ 内存严重不足 │ adj >= 400 (重要后台+)
CRITICAL │ 内存极度不足 │ adj >= 200 (前台服务+)
EMERGENCY│ OOM 即将发生 │ adj >= 0 (全杀!)
═══════════════════════════════════════════════════════
被杀进程顺序:高 adj 优先 → 同 adj 比内存 → RSS 大者先杀
```
### 4.3 lmkd 关键配置
配置文件位置:
```
系统属性(运行时):
ro.lmk.use_minfree_levels = false // 用 PSI 而非 minfree
ro.lmk.kill_heaviest_task = true // 同 adj 杀最重的
ro.lmk.debug = false // 调试日志
AOSP 源码默认配置 (lmkd.cpp):
// 默认百分比阈值
low_ram_device 时:
level 阈值: LOW=70 MEDIUM=80 HIGH=90 CRITICAL=95
正常设备时:
level 阈值: LOW=60 MEDIUM=70 HIGH=80 CRITICAL=90
```
### 4.4 实战:监听 lmkd 杀进程
```bash
# 方式1查看 lmkd 日志
adb logcat -s lmkd:* | grep -E "Killing|kill|Low memory"
# 输出示例:
# lmkd: Killing 'com.example.app' (pid=32055, uid=10456),
# adj=1001, free memory=102400kB,
# to free 204800kB, reason: pressure level HIGH
# 方式2通过 dumpsys 查看历史杀进程记录
adb shell dumpsys activity lmk | head -50
# 方式3查看 /proc 中进程被杀统计
adb shell cat /proc/vmstat | grep oom_kill
```
---
## 📊 五、进程优先级与杀进程策略总结
### 5.1 进程"保命"金字塔
```
┌─────────────┐
│ PERSISTENT │ Adj=-800 系统常驻,永不被杀
│ SYSTEM │ Adj=-700
├─────────────┤
│ VISIBLE │ Adj=-100 可见但非焦点
│ PERCEPTIBLE │ Adj=-10 可感知(如通知)
├─────────────┤
│ TOP │ Adj=0 前台焦点 ★
│ BOUND_TOP │ Adj=50 绑定到前台
├─────────────┤
│ FGS │ Adj=200 前台Service ★安全线
│ BOUND_FGS │ Adj=250
├──── ────────┤ ← 以上进程几乎不会被杀
│ IMPORTANT │ Adj=300~400
│ BACKUP │ Adj=600
├─────────────┤
│ SERVICE │ Adj=700 Service进程 ★危险线
├─────────────┤
│ CACHED │ Adj=906 缓存Activity
│ CCH_EMPTY │ Adj=1001 空进程 ★最先被杀
└─────────────┘
```
### 5.2 开发者必知的 3 条铁律
1. **前台 Service 是你的保命符**:调用 `startForeground()` 后,进程 Adj=200普通内存压力下绝不会被杀。但必须显示通知。
2. **不要滥用 `android:persistent="true"`**:只有系统应用才能持久化,第三方应用即使声明也无效,会被 PackageManager 忽略。
3. **空进程是"缓存池"不是"泄漏"**:看到 `CACHED_EMPTY` 进程数量多是正常现象Android 故意保留空进程以加速下次启动。只有内存真的紧张时 lmkd 才会清理。
---
## 🧪 六、实战练习
### 练习 6.1:追踪一次完整的进程创建
```bash
# 清空日志
adb logcat -c
# 启动一个 App以 Chrome 为例)
adb shell am start -n com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity
# 抓取关键日志
adb logcat -d | grep -E "ActivityManager|Zygote|ActivityThread" \
| grep -E "Start proc|fork|attachApplication|realStartActivity" \
| head -20
```
**预期输出时间线:**
```
ActivityManager: Start proc 32188:com.android.chrome/u0a282 for activity
Zygote: Forked child process 32188
Zygote: Process 32188 started by zygote
ActivityThread: main() called
ActivityManager: attachApplication() from pid=32188
ActivityManager: realStartActivityLocked: 32188 com.android.chrome/...
```
> **作业**:用 `adb shell cat /proc/32188/status | grep -E "Uid|VmRSS|Threads"` 记录进程创建后 3 秒内的 VmRSS 变化。
### 练习 6.2:手动触发 lmkd 杀进程
```bash
# 步骤1找一个缓存进程
adb shell dumpsys activity processes | grep "cch-empty" | head -1
# 输出示例Proc #15: cch CEM/A/TPS trm: 0 32055:com.example.app/u0a456 (cch-empty)
# 步骤2查看其 oom_score_adj
adb shell cat /proc/32055/oom_score_adj # 预期 1001
# 步骤3模拟内存压力需要 root
adb root
adb shell "echo 100M > /proc/sys/vm/min_free_kbytes" # 提高最小空闲内存
adb shell "echo 3 > /proc/sys/vm/drop_caches" # 清缓存
# 然后快速分配内存触发压力
adb shell "stress --vm 1 --vm-bytes 500M --timeout 5s"
# 步骤4验证进程已被杀
adb shell "ps -p 32055" # 预期 "No such process"
```
### 练习 6.3:分析 ANR 与进程管理的关系
```bash
# 模拟 ANR在主线程 sleep
adb shell "am broadcast -a com.example.ANR_TEST --es action sleep"
# 获取 ANR trace
adb shell ls /data/anr/
adb pull /data/anr/anr_$(date +%Y-%m-%d-%H-%M-%S) ./
# 在 ANR trace 中查找关键行
grep -A5 "Cmd line:" anr_* # 进程命令行
grep "adj" anr_* # 当时 Adj 值
grep "sys.default" anr_* # 系统负载
```
---
## 📝 七、课后习题
### 选择题
**Q1**:以下哪个 Adj 值对应前台 Activity 所在进程?
A. -800
B. -100
C. 0
D. 200
<details>
<summary>答案</summary>
**C**。TOP Activity 所在进程 curAdj=0对应 PROCESS_STATE_TOP。
</details>
---
**Q2**lmkd 在哪个内存压力等级时会开始杀缓存进程Adj>=900
A. EMERGENCY
B. HIGH
C. MEDIUM
D. LOW
<details>
<summary>答案</summary>
**D**。LOW 级别就开始清理 adj>=900 的缓存进程,这是最温和的回收手段。
</details>
---
**Q3**:以下关于 ProcessRecord 的 `curAdj``setAdj` 说法正确的是?
A. 它们是同一个值,只是命名不同
B. `curAdj` 是内核中的实际值,`setAdj` 是 AMS 期望值
C. `curAdj` 总是小于 `setAdj`
D. 只有 `setAdj` 会同步到 lmkd
<details>
<summary>答案</summary>
**B**`curAdj` 反映 `/proc/[pid]/oom_score_adj` 的当前值,`setAdj` 是 AMS 计算出的目标值,两者通过 `applyOomAdjLocked()` 完成同步。
</details>
---
### 简答题
**Q4**:画出从用户「点击图标」到 `Activity.onCreate()` 被调用,涉及的所有进程和 IPC 通信,标注每一步的进程名和 IPC 类型。(参考答案参考第一节的 ASCII 图)
---
**Q5**:解释为什么 Android 要保留 CACHED_EMPTY 进程而不是立即杀掉?
<details>
<summary>参考答案</summary>
CACHED_EMPTY 进程是已经加载了应用框架ActivityThread、Application 对象已初始化)但没有任何 Activity/Service 的空进程。保留它们的好处:
1. **热启动加速**:再次启动 App 时,只需创建 Activity 即可,省去 fork+初始化框架的 ~200-500ms。
2. **Zygote CoW 特性**fork 出的子进程与 Zygote 共享只读内存页,空进程的额外内存开销很小。
3. **惰性回收**:只在内存真正紧张时才杀,避免频繁的进程创建/销毁抖动。
</details>
---
**Q6**:如果 `startForegroundService()` 启动后 5 秒内不调用 `startForeground()`会发生什么Adj 会受到什么影响?
<details>
<summary>参考答案</summary>
Android 8.0+ 规定:`startForegroundService()` 后必须在 **5 秒** 内调用 `startForeground()` 显示通知。超时后果:
1. **系统强制停止 Service** 并打印 ANR 警告(虽然不会真正弹 ANR 对话框)。
2. **进程 Adj 不会降到 200**,仍保持 SERVICE_ADJ (700) 或更差,变成易被杀状态。
3. 在 logcat 中可以看到:`Context.startForegroundService() did not then call Service.startForeground()`
</details>
---
### 实战题
**Q7**:编写一个简单的 Android App`onCreate()` 中通过读取 `/proc/self/oom_score_adj` 来监控自身进程的 Adj 变化。要求:
- 在 MainActivity 的 `onResume()``onPause()` 分别打印 Adj 值。
- 观察按 Home 键后 30 秒内 Adj 的变化(提示:在 `logcat` 中过滤你的 TAG
预期结果:前台时 adj=0 → 按 Home 后变为 adj=700~906 → 如果内存不足可能变为 adj=1001 → 被杀。
---
## 🔗 参考资料
| 资源 | 路径/链接 |
|:-----|:--------|
| ProcessRecord.java | `frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java` |
| OomAdjuster.java | `frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java` |
| ProcessList.java | `frameworks/base/services/core/java/com/android/server/am/ProcessList.java` |
| ZygoteProcess.java | `frameworks/base/core/java/android/os/ZygoteProcess.java` |
| lmkd.cpp | `system/memory/lmkd/lmkd.cpp` |
| ZygoteServer.java | `frameworks/base/core/java/com/android/internal/os/ZygoteServer.java` |
| ActivityThread.java | `frameworks/base/core/java/android/app/ActivityThread.java` |
| Android 进程状态文档 | https://developer.android.com/guide/components/processes-and-threads |
---
> **课程小结**:本课深入了 Android 进程管理的完整链路,从 Zygote fork 到 lmkd 杀进程,掌握了 ProcessRecord 的双值设计curAdj/setAdj、OOM Adj 的 19 级分级体系、以及 lmkd 基于 PSI 的五级压力杀进程策略。下一课将进入 **Activity 任务栈、启动模式与多窗口源码剖析**。
---
*Generated by AOSP Learning Assistant · Stage 5 Module A Lesson 1 · 2026-06-14*

View File

@@ -0,0 +1,919 @@
# 阶段⑤ 模块A 第2课Activity 任务栈、启动模式与多窗口源码剖析
> **课程定位**:阶段⑤ System Server 核心服务深度解析 · 模块A AMS深度 第2课
> **难度等级**:⭐⭐⭐⭐⭐(深度源码级)
> **预计阅读**30~35 分钟
> **前置课程**阶段⑤第1课AMS进程管理、阶段③模块C第12课AMS入门
> **源码基准**AOSP 14.0.0_r50(兼容 Android 12/13/15 核心逻辑)
---
## 🎯 课程目标
学完本课后,你将能够:
| 序号 | 能力目标 | 检验方式 |
|:---:|:---------|:--------|
| ① | 画出 WindowContainer → Task → ActivityRecord 的五层继承树 | 默写类图 |
| ② | 解释四种 launchMode 在 `ActivityStarter` 中的源码级分支逻辑 | 画出流程图 |
| ③ | 追踪 `startActivity()``realStartActivityLocked()` 的完整 IPC 调用链 | 写出10步链路 |
| ④ | 理解 `FLAG_ACTIVITY_NEW_TASK``TaskAffinity` 的协作机制 | 给出3个场景 |
| ⑤ | 掌握多窗口下 `TaskDisplayArea` 的任务栈隔离原理 | 画出分屏任务树 |
| ⑥ | 使用 `dumpsys activity activities` 诊断任务栈状态 | 实战输出分析 |
---
## 📐 前置知识检查
在进入本课前,确认你已经掌握:
- [x] Activity 生命周期基础onCreate → onResume → onPause → onStop → onDestroy
- [x] Binder IPC 基本调用链路Client → Server → 回调)
- [x] Intent 的基本结构ComponentName / Action / Category / Flags
- [x] AMS 在 system_server 中的角色定位第1课内容
- [x] `ActivityThread``ApplicationThread` 的关系
- [ ] ⚠️ 如果不熟悉以上内容请先复习阶段③模块B/C 对应课程
---
## 🧱 一、Task 系统全景架构:五层 WindowContainer 继承树
### 1.1 从 WindowContainer 到 ActivityRecord 的层级关系
Android 10 引入了 `WindowContainer` 基类,将所有窗口管理实体统一为一棵树。理解这棵树是理解 Task 系统的前提:
```
WindowContainer (抽象基类 — 树节点)
├── RootWindowContainer (树根 — 整个系统的窗口容器)
│ │ 单例,持有所有 Display 和顶层 Task
│ │
│ └── DisplayContent (每个屏幕一个)
│ │ 代表物理/虚拟显示器
│ │
│ └── TaskDisplayArea (任务显示区域)
│ │ 一个 Display 可以有多个 TDA分屏时为2个
│ │ ★ 多窗口隔离的关键:每个分屏窗口对应一个 TDA ★
│ │
│ └── Task (任务栈)
│ │ 一组 ActivityRecord 的集合
│ │ 对应最近任务列表中的一张卡片
│ │
│ └── ActivityRecord (Activity 记录)
│ 单个 Activity 实例的元数据
│ 持有 Token、状态、Intent、进程引用
```
### 1.2 WindowContainer 核心能力
```java
// frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
public class WindowContainer<E extends WindowContainer>
implements Comparable<WindowContainer>, Animatable {
// === 树结构字段 ===
protected WindowContainer mParent; // 父节点
protected final ArraySet<E> mChildren = ...; // 子节点列表
// === 层级操作 ===
void addChild(E child, int index); // 添加子节点
void removeChild(E child); // 移除子节点
void positionChildAt(int position, E child, boolean includingParents);
void moveToFront(String reason); // 移到最前
// === 可见性控制 ===
boolean mVisible; // 当前可见性
void setVisible(boolean visible); // 递归设置可见性
// === Surface 控制 ===
SurfaceControl mSurfaceControl; // SurfaceFlinger 图层
SurfaceAnimator mSurfaceAnimator; // 动画控制
// === 遍历 ===
void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom);
}
```
> **设计精髓**:所有窗口相关操作(添加/移除/显示/隐藏/动画)都在 `WindowContainer` 基类中实现,`Task`、`ActivityRecord`、`WindowState` 只需关注各自特有的逻辑。这种「组合模式」极大简化了窗口管理代码。
### 1.3 Task 数据结构深度解析
```java
// frameworks/base/services/core/java/com/android/server/wm/Task.java
public class Task extends WindowContainer<ActivityRecord> {
// ========== [身份标识] ==========
int mTaskId; // 全局唯一 Task ID
int mUserId; // 所属用户 ID多用户
String affinity; // ★ TaskAffinity决定归属
Intent mLastNonFullscreenIntent; // 最后一次非全屏启动的 Intent
// ========== [状态标志] ==========
boolean mIsFloating; // 是否浮动窗口(如气泡)
boolean mDragResizing; // 是否正在拖拽调整大小
boolean mOnTop; // 是否在最顶层
int mResizeMode; // 调整大小模式
// ========== [结构关系] ==========
TaskDisplayArea getTaskDisplayArea(); // 所属 TDA
ActivityRecord getTopActivity(); // 栈顶 Activity
ActivityRecord getRootActivity(); // 栈底 Activity
int getActivityCount(); // Activity 数量
// ========== [视觉属性] ==========
int mBounds; // 任务边界(分屏时非全屏)
boolean mMinimized; // 是否最小化Android 12+
// ========== [关键方法] ==========
void addActivityToTop(ActivityRecord r); // 压入栈顶
void removeActivity(ActivityRecord r); // 移除 Activity
ActivityRecord getActivity(IBinder token); // 按 Token 查找
void performClearTop(ActivityRecord r, ...); // 清栈到指定 Activity
void performClearTask(); // 清空整个 Task
}
```
### 1.4 ActivityRecord 核心字段
```java
// frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
public class ActivityRecord extends WindowContainer<WindowState> {
// ========== [身份] ==========
IBinder appToken; // ★ WMS 侧 Token跨进程传递
Task task; // 所属 Task
ActivityInfo info; // AndroidManifest.xml 解析结果
// ========== [Intent] ==========
Intent intent; // 启动该 Activity 的 Intent可包含 flags
String resolvedType; // MIME 类型
// ========== [状态机] ==========
enum State { INITIALIZING, STARTED, RESUMED, PAUSING, PAUSED,
STOPPING, STOPPED, FINISHING, DESTROYING, DESTROYED, RESTARTING }
State mState; // 当前生命周期状态
// ========== [进程关联] ==========
ProcessRecord app; // 所在进程null = 进程不存在)
WindowProcessController mWindowProcessController; // Android 10+
// ========== [启动模式] ==========
int launchMode; // 从 ActivityInfo 读取
String taskAffinity; // 从 AndroidManifest 或默认包名
// ========== [配置] ==========
Configuration configuration; // 屏幕配置(旋转/分屏时变化)
boolean mIsExiting; // 是否正在退出
// ========== [窗口] ==========
ActivityRecord mTaskOverlay; // 是否作为 Task 遮罩层
boolean mShowForAllUsers; // 是否对所有用户可见
}
```
---
## 🔄 二、startActivity() 全链路追踪10步跨越3进程
### 2.1 完整调用链路图
```
用户调用 startActivity(intent)
┌──────────────────────────────────────────────────────────┐
│ App 进程 (com.example.app) │
│ │
│ ① Activity.startActivity(intent) │
│ └─ startActivityForResult(intent, -1) │
│ └─ Instrumentation.execStartActivity() │
│ └─ ActivityTaskManager.getService() │
│ .startActivity(...) │
│ └─ Binder IPC ─────────────────┐ │
└───────────────────────────────────────────────────│───────┘
┌──────────────────────────────────────────────────────────┐
│ system_server 进程 │
│ │
│ ② ActivityTaskManagerService.startActivity() │
│ └─ startActivityAsUser(...) │
│ │
│ ③ ActivityStarter.executeRequest() ★ 核心入口 │
│ │ │
│ ├─ ④ 解析 Intent / 权限检查 / resolveActivity() │
│ │ │
│ ├─ ⑤ 确定目标 TaskcomputeStackFocus() │
│ │ ├─ standard → getOrCreateTask() │
│ │ ├─ singleTop → findTask() / 栈顶判断 │
│ │ ├─ singleTask→ findTask() / clearTop() │
│ │ └─ singleInstance → createNewTask() │
│ │ │
│ ├─ ⑥ 创建 ActivityRecord │
│ │ │
│ ├─ ⑦ 将 ActivityRecord 放入 Task │
│ │ Task.addActivityToTop(activityRecord) │
│ │ │
│ └─ ⑧ resumeFocusedTasksTopActivities() │
│ └─ RootWindowContainer.resumeFocusedTasks... │
│ └─ Task.resumeTopActivityInnerLocked() │
│ └─ ⑨ startSpecificActivity() │
│ ├─ 进程存在?→ realStartActivity()│
│ └─ 进程不存在?→ startProcess() │
│ (回到第1课的进程创建链路) │
│ │
│ ⑩ realStartActivityLocked() │
│ └─ app.thread.scheduleTransaction() ── Binder ─┐ │
└──────────────────────────────────────────────────────│────┘
┌──────────────────────────────────────────────────────────┐
│ App 进程 │
│ │
│ ApplicationThread.scheduleTransaction() │
│ └─ ActivityThread.handleLaunchActivity() │
│ └─ performLaunchActivity() │
│ ├─ makeApplication() │
│ ├─ Activity.attach() │
│ └─ Activity.onCreate() ★ │
└──────────────────────────────────────────────────────────┘
```
### 2.2 ActivityStarter所有启动逻辑的「调度中心」
```java
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
class ActivityStarter {
int executeRequest(Request request) {
// === 第一阶段:合法性检查 ===
// ① 检查调用者权限
checkStartAnyActivityPermission(intent);
// ② 解析 Activity 信息
resolveActivity(intent, resolvedType, ...);
// ③ 检查是否允许启动(后台限制等)
checkBackgroundActivityStart(...);
// === 第二阶段:确定目标 Task ===
// ④ 根据 launchMode 和 flags 找或创建 Task
Task targetTask = computeStackFocus(activityInfo, intent, ...);
// === 第三阶段:处理复用逻辑 ===
// ⑤ 如果是 singleTop / singleTask检查是否复用
ActivityRecord reused = findReusableActivity(targetTask, intent, ...);
if (reused != null) {
// onNewIntent 路径
deliverNewIntent(reused, intent);
return START_SUCCESS;
}
// === 第四阶段:创建并放置 ActivityRecord ===
// ⑥ new ActivityRecord
ActivityRecord r = new ActivityRecord(..., intent, activityInfo, ...);
// ⑦ 放入 Task
targetTask.addActivityToTop(r);
// === 第五阶段:启动流程 ===
// ⑧ Resume
mRootWindowContainer.resumeFocusedTasksTopActivities(...);
return START_SUCCESS;
}
}
```
### 2.3 Task/TaskDisplayArea 的查找逻辑
```java
// ActivityStarter.java → computeStackFocus() 简化逻辑
Task computeStackFocus(ActivityInfo aInfo, Intent intent, int launchMode) {
TaskDisplayArea targetTDA = getTaskDisplayArea(intent, activityOptions);
// ── 多窗口时的 TDA 选择逻辑 ──
// 如果指定了 displayId → 找到对应 TDA
// 如果是分屏启动 → 选择用户指定的分屏 TDA
// 否则 → 使用当前焦点的 TDA
String targetAffinity = aInfo.taskAffinity != null
? aInfo.taskAffinity
: aInfo.packageName;
switch (launchMode) {
case LAUNCH_SINGLE_INSTANCE:
// ★ 强制创建新 Task不与任何现有 Task 共享
return createNewTask(targetTDA, targetAffinity);
case LAUNCH_SINGLE_TASK:
// ★ 在整个 TDA 中查找 affinity 匹配的 Task
Task existing = targetTDA.findTaskByAffinity(targetAffinity);
if (existing != null) {
// 找到 → 将 existing 之上的 Activity 全部清掉 (clearTop)
existing.performClearTop(existing.getRootActivity());
return existing;
}
// 没找到 → 创建新 Task
return createNewTask(targetTDA, targetAffinity);
case LAUNCH_SINGLE_TOP:
// ★ 检查当前栈顶是否是目标 Activity
Task topTask = targetTDA.getTopTask();
if (topTask != null && topTask.getTopActivity().isSameIntent(intent)) {
// 复用栈顶 → onNewIntent
return topTask;
}
// 栈顶不匹配 → 走 standard 逻辑
// (注意:是否需要 NEW_TASK 取决于调用者的 Task)
return getOrCreateTask(targetTDA, intent);
case LAUNCH_MULTIPLE: // standard
default:
// ★ 默认行为:根据 FLAG_ACTIVITY_NEW_TASK 决定
if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0) {
return getOrCreateTask(targetTDA, targetAffinity);
} else {
// 复用调用者的 Task
return callerActivity.getTask();
}
}
}
```
---
## 🎭 三、四大启动模式:源码级行为对比
### 3.1 总览对比表
| 模式 | Manifest 常量 | Task 行为 | onNewIntent | 多实例 | 典型场景 |
|:-----|:------------|:---------|:-----------|:------|:--------|
| **standard** | `LAUNCH_MULTIPLE` | 放入调用者 Task | 不触发 | 允许 | 普通页面 |
| **singleTop** | `LAUNCH_SINGLE_TOP` | 栈顶匹配则复用 | 触发 | 允许(不在栈顶时) | 搜索页、详情页 |
| **singleTask** | `LAUNCH_SINGLE_TASK` | 独占 TaskclearTop | 触发 | 不允许Task内唯一 | 主页、浏览器 |
| **singleInstance** | `LAUNCH_SINGLE_INSTANCE` | 完全独占 Task | 触发 | 不允许(全局唯一) | 拨号、地图导航 |
### 3.2 每种模式的 Task 栈变化示意图
```
场景:依次启动 A(standard), B(singleTop), C(singleTask), D(singleInstance)
═══════════════════════════════════════════════════════════
TDA (TaskDisplayArea)
═══════════════════════════════════════════════════════════
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Task #1 │ │ Task #2 │ │ Task #3 │
│ affinity │ │ affinity │ │ affinity │
│ =com.a │ │ =com.c │ │ =com.d │
│ │ │ │ │ │
│ ┌──────┐ │ │ ┌──────┐ │ │ ┌────────┐ │
│ │ A │ │ │ │ C │ │ │ │ D │ │
│ │(std) │ │ │ │(sTsk)│ │ │ │(sInst) │ │
│ └──────┘ │ │ └──────┘ │ │ └────────┘ │
│ ┌──────┐ │ └──────────┘ └──────────────┘
│ │ B │ │
│ │(sTop)│ │
│ └──────┘ │
└──────────┘
关键观察:
- A 和 B 在同一个 TaskB 有 affinity=com.a 或未设置,默认=包名)
- C 有 affinity=com.c + singleTask → 独立 Task #2
- D 有 singleInstance → 独占 Task #3且该 Task 只能有 D 一个 Activity
```
### 3.3 singleTask 的 clearTop 算法源码还原
```java
// Task.java → performClearTop() 简化逻辑
void performClearTop(ActivityRecord target, String reason) {
// target: 要被保留在栈顶的 Activity
// 遍历栈中所有在 target 之上的 Activity
List<ActivityRecord> aboveActivities = new ArrayList<>();
for (int i = mChildren.size() - 1; i >= 0; i--) {
ActivityRecord r = mChildren.get(i);
if (r == target) break; // 遇到 target停止
aboveActivities.add(r); // 收集 target 之上的 Activity
}
// 依次 finish 掉这些 Activity
for (ActivityRecord r : aboveActivities) {
r.finishIfPossible(reason, false);
removeActivity(r);
}
// target Activity 收到 onNewIntent
target.deliverNewIntent(newIntent);
// 将 target 移到栈顶
positionChildAtTop(target);
}
```
**实例说明**
```
启动前Task#1 → [A] → [B] → [C] → [D] (D是栈顶)
调用 startActivity(singleTask-C)
执行 performClearTop(C)
1. 找到 C 在栈中的位置 (index=2)
2. 收集 C 之上的 Activity: [D]
3. finish D
4. C.deliverNewIntent()
5. C 移到栈顶
启动后Task#1 → [A] → [B] → [C] (C是栈顶D已销毁)
```
---
## 🏷️ 四、Intent Flags启动行为的「微调旋钮」
### 4.1 核心 Flags 一览
| Flag 常量 | 值 | 作用 | 与 launchMode 关系 |
|:----------|:---|:-----|:------------------|
| `FLAG_ACTIVITY_NEW_TASK` | 0x10000000 | 在新 Task 中启动 | ≈ singleTask但不强制 clearTop |
| `FLAG_ACTIVITY_CLEAR_TOP` | 0x04000000 | 清除目标之上的 Activity | 常与 NEW_TASK 组合 |
| `FLAG_ACTIVITY_SINGLE_TOP` | 0x20000000 | 栈顶匹配时复用 | ≈ singleTop |
| `FLAG_ACTIVITY_CLEAR_TASK` | 0x00008000 | 先清空整个 Task | 必须与 NEW_TASK 组合 |
| `FLAG_ACTIVITY_REORDER_TO_FRONT` | 0x00020000 | 将已有实例移到栈顶 | 不创建新实例 |
| `FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS` | 0x00800000 | 不显示在最近任务 | Task 不出现在 Recents |
### 4.2 经典组合效果矩阵
```
调用形式 │ Task 行为 │ Activity 行为
───────────────────────────────────┼─────────────────────┼────────────────────
NEW_TASK │ 新Task或匹配affinity │ 创建新实例不一定clear top
NEW_TASK + CLEAR_TOP │ 匹配Task + clearTop │ 目标之上全清 + onNewIntent
CLEAR_TASK + NEW_TASK │ 先清空再使用 │ 全新实例,旧的全部销毁
SINGLE_TOP │ 复用调用者Task │ 栈顶匹配则onNewIntent
REORDER_TO_FRONT │ 不改变Task结构 │ 直接移到栈顶不recreate
NEW_TASK + SINGLE_TOP + CLEAR_TOP │ 匹配Task + clearTop │ 三合一效果
```
### 4.3 FLAG_ACTIVITY_NEW_TASK + TaskAffinity 协作场景
```java
// 场景:从 App-A 启动 App-B 的 Activity
// App-A (包名: com.app.a, 默认 affinity: com.app.a)
// App-B (包名: com.app.b)
// === 场景 1: 不带 NEW_TASK ===
Intent intent = new Intent(context, AppBActivity.class);
startActivity(intent);
// 结果AppBActivity 被放入 App-A 的 Task 中!
// Task#1(affinity=com.app.a) → [AppA-Act] → [AppB-Act]
// 按返回键会回到 App-A
// === 场景 2: 带 NEW_TASK ===
Intent intent = new Intent(context, AppBActivity.class);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
// 结果:创建新 Taskaffinity=com.app.b
// Task#2(affinity=com.app.b) → [AppB-Act]
// 两个 Task 独立存在于 Recents
// === 场景 3: AppB-Act 声明了 taskAffinity="com.app.b.custom" ===
// Manifest: <activity android:name=".AppBActivity"
// android:taskAffinity="com.app.b.custom"/>
Intent intent = new Intent(context, AppBActivity.class);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
// 结果:创建新 Taskaffinity=com.app.b.custom (= 声明的值)
// 下次再启动这个 Activity 时,会匹配到同一个 Task
```
> **关键规则**`TaskAffinity` 是 Task 的「护照」。`NEW_TASK` 告诉 AMS "我要出国新Task"`TaskAffinity` 告诉 AMS "我要去哪个国家哪个Task"。
---
## 🪟 五、多窗口/分屏下的任务栈管理
### 5.1 Android 12+ 的分屏架构
```
RootWindowContainer
├── DisplayContent (主屏幕)
│ │
│ ├── TaskDisplayArea #1 (上半屏 / 左半屏)
│ │ ├── Task #1 → [Activity-A1] → [Activity-A2]
│ │ └── Task #2 → [Activity-A3]
│ │
│ └── TaskDisplayArea #2 (下半屏 / 右半屏)
│ ├── Task #3 → [Activity-B1]
│ └── Task #4 → [Activity-B2] → [Activity-B3]
└── DisplayContent (外接显示器)
└── TaskDisplayArea #3 (全屏)
└── Task #5 → [Activity-C1]
```
> **核心理解**:每个分屏窗口对应一个 `TaskDisplayArea`TDA 之间**任务栈完全隔离**。这意味着上半屏的 Task 和下半屏的 Task 互不干扰,各自拥有独立的返回栈。
### 5.2 分屏模式下的 startActivity 行为
```java
// ActivityStarter.java → getTaskDisplayArea()
TaskDisplayArea getTaskDisplayArea(Intent intent, ActivityOptions options) {
if (options != null && options.getLaunchDisplayId() != INVALID_DISPLAY) {
// 明确指定了 Display → 找到对应 Display 的焦点 TDA
DisplayContent dc = getDisplayContent(options.getLaunchDisplayId());
return dc.getDefaultTaskDisplayArea();
}
if (options != null && options.getLaunchTaskDisplayArea() != null) {
// 指定了 TDA如从分屏的某个窗口启动
return options.getLaunchTaskDisplayArea();
}
if (inMultiWindowMode() && callerActivity != null) {
// ★ 分屏时:默认将新 Activity 放入调用者所在的 TDA
// 这是用户直觉所在——在哪个窗口操作,就在哪个窗口显示
return callerActivity.getTask().getTaskDisplayArea();
}
// 非分屏:使用默认 TDA全屏的那个
return mRootWindowContainer.getDefaultTaskDisplayArea();
}
```
### 5.3 Task 在 TDA 之间的移动
```java
// Task.java → reparent()
void reparent(TaskDisplayArea newParent, boolean onTop, boolean animate) {
// 场景1用户将分屏窗口拖拽到全屏
// Task 从一个 TDA 移动到另一个 TDA
TaskDisplayArea oldParent = getTaskDisplayArea();
if (oldParent == newParent) return; // 同一 TDA无需操作
// 从旧 TDA 移除
oldParent.removeChild(this);
// 加入新 TDA
if (onTop) {
newParent.positionChildAtTop(this);
} else {
newParent.addChild(this);
}
// ★ 注意:当前焦点 Activity 需要重新 Resume
// 因为 TDA 切换意味着窗口焦点可能变化
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
```
### 5.4 分屏时的 Adj 计算差异
分屏模式下,两个 TDA 的 `TOP` Activity 都可能获得 `adj=0`
```
非分屏(全屏):
TDA#1(全屏) → Task#1 → [TOP-Act] adj=0
其他不可见 Activity adj=700~1001
分屏模式:
TDA#1(上半屏) → Task#1 → [TOP-Act-A] adj=0 ← 两个TOP
TDA#2(下半屏) → Task#2 → [TOP-Act-B] adj=0 ← 两个TOP
// 但实际只有一个是「焦点窗口」,另一个是「可见但非焦点」
// curProcState 会区分:
// 焦点窗口PROCESS_STATE_TOP (2)
// 非焦点可见窗口PROCESS_STATE_TOP_SLEEPING (11)
```
---
## 🧪 六、实战练习
### 练习 6.1:用 dumpsys 观察四种启动模式的 Task 变化
```bash
# === 实验准备:清理环境 ===
adb shell am force-stop com.android.chrome
adb shell am force-stop com.android.calculator2
# === 步骤1启动 Chromestandard ===
adb shell am start -n com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity
# 查看任务栈
adb shell dumpsys activity activities | grep -A30 "Display #0" | grep -E "Task{|ActivityRecord{"
# 预期输出:
# Task{#1 type=standard U=0 visible=true ...
# ActivityRecord{#1 u0 com.android.chrome/... t1}
# === 步骤2观察 singleTask ===
# Chrome 的 MainActivity 通常声明为 singleTask
adb shell am start -n com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity
# 再次启动同一个 Activity → 不会创建新实例
adb shell dumpsys activity activities | grep "com.android.chrome"
# 输出只有 1 个实例 → onNewIntent 被触发
# === 步骤3用 adb 模拟 NEW_TASK flag ===
adb shell am start -f 0x10000000 -n com.android.calculator2/.Calculator
# -f 0x10000000 = FLAG_ACTIVITY_NEW_TASK
# 计算器会在自己的 Task 中启动
# === 步骤4观察多个 Task ===
adb shell dumpsys activity activities | grep -E "^\s+\* Task{"
# 输出至少2个 TaskChrome的 + 计算器的)
```
### 练习 6.2:编写 Activity 验证 taskAffinity 作用
```java
// === 在 AndroidManifest.xml 中声明 ===
<activity android:name=".MainActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.custom_affinity" />
<activity android:name=".SecondActivity"
android:launchMode="standard" /> <!-- 默认 affinity=包名 -->
// === MainActivity.java ===
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle saved) {
super.onCreate(saved);
// 打印当前 Task 信息
Log.d("TASK_DEBUG", "Task ID: " + getTaskId());
Log.d("TASK_DEBUG", "isTaskRoot: " + isTaskRoot());
Log.d("TASK_DEBUG", "TaskAffinity: " +
getPackageManager().getActivityInfo(getComponentName(), 0).taskAffinity);
// 启动 SecondActivity不带 NEW_TASK
// 由于 SecondActivity 的 affinity=包名(默认),
// 而 MainActivity 的 affinity=custom,
// 不带 NEW_TASK → SecondActivity 进入 MAIN 的 Task? NO!
//
// 实际上:不带 NEW_TASK 时,新 Activity 总是进入调用者的 Task
// taskAffinity 只在 NEW_TASK 时起作用!
findViewById(R.id.btn).setOnClickListener(v -> {
startActivity(new Intent(this, SecondActivity.class));
});
}
}
// === SecondActivity.java ===
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle saved) {
super.onCreate(saved);
Log.d("TASK_DEBUG", "SecondActivity - Task ID: " + getTaskId());
// 如果从 MainActivity 不带 NEW_TASK 启动 → Task ID 与 MainActivity 相同
// 如果带 NEW_TASK → 会创建新 Taskaffinity=包名)
}
}
```
```bash
# 运行后查看日志
adb logcat -s TASK_DEBUG
```
### 练习 6.3:用 dumpsys 分析分屏模式下的 TDA
```bash
# === 步骤1进入分屏 ===
# 方式A在设备上通过手势/最近任务进入分屏
# 方式B模拟分屏Android 12+
adb shell am start --windowingMode 5 -n com.android.chrome/...
# === 步骤2检查分屏状态 ===
adb shell dumpsys activity activities | grep -E "Display|TaskDisplayArea|windowingMode"
# 预期输出:
# Display #0 (activities from top to bottom):
# TaskDisplayArea #1 (windowingMode=5) ← 分屏上半
# * Task{#1 ...}
# TaskDisplayArea #2 (windowingMode=5) ← 分屏下半
# * Task{#2 ...}
# === 步骤3观察两个 TDA 的 Task 栈 ===
adb shell dumpsys activity activities | awk '/TaskDisplayArea/,/^$/' | head -40
```
---
## 📝 七、课后习题
### 选择题
**Q1**:以下哪种情况会触发 `onNewIntent()` 而不是 `onCreate()`
A. standard 模式,同一个 Activity 被启动两次
B. singleTop 模式,目标 Activity 已在栈顶
C. singleTask 模式,目标 Activity 不在任何 Task 中
D. singleInstance 模式,第一次启动该 Activity
<details>
<summary>答案</summary>
**B**。singleTop 在栈顶匹配时触发 onNewIntent。Astandard 每次新建C不在 Task 中则新建D第一次必然新建。
**补充**singleTask 模式下,如果目标 Activity 已在其 Task 中存在(即使不在栈顶)且使用了 CLEAR_TOP也会触发 onNewIntent。
</details>
---
**Q2**:关于 `FLAG_ACTIVITY_NEW_TASK`,以下说法**错误**的是?
A. 加上此 flag 后一定会创建新的 Task
B. 如果已有 affinity 匹配的 Task会复用该 Task
C. 通常应与 `FLAG_ACTIVITY_CLEAR_TOP` 配合使用
D. 从非 Activity 的 Context如 Service启动 Activity 时必须使用
<details>
<summary>答案</summary>
**A**。NEW_TASK 不保证一定创建新 Task。如果有 affinity 匹配的已有 Task会复用该 Task。选项 D 正确——非 Activity Context 启动 Activity 必须加 NEW_TASK否则会抛异常。
</details>
---
**Q3**:分屏模式下,两个 TDA 各有一个前台 Activity。以下说法正确的是
A. 两个 Activity 的 oom_score_adj 都是 0
B. 只有一个 Activity 的 adj=0另一个的 adj=-100VISIBLE
C. 两个 Activity 的 adj 都是 -100
D. 两个 Activity 的 curProcState 完全相同
<details>
<summary>答案</summary>
**A** 最接近真相。在 Android 的分屏实现中,两个可见的 TOP Activity 的 `curAdj` 通常都是 0或非常接近 0`curProcState` 不同:焦点窗口为 `PROCESS_STATE_TOP (2)`,非焦点可见窗口为 `PROCESS_STATE_TOP_SLEEPING (11)`。所以 D 错误。B 错误——可见非焦点 ≠ VISIBLE(-100),那是被完全遮挡但仍在前台 Task 中的情况。
</details>
---
### 简答题
**Q4**:画出 `WindowContainer``RootWindowContainer``DisplayContent``TaskDisplayArea``Task``ActivityRecord``WindowState` 的完整继承树,并标注每个层级在分屏和非分屏场景下的实例数量。
<details>
<summary>参考答案</summary>
```
非分屏场景:
RootWindowContainer (1个单例)
└─ DisplayContent (1个主屏)
└─ TaskDisplayArea (1个全屏)
├─ Task #1 → [ActivityRecord #1] → [WindowState #1]
├─ Task #2 → [ActivityRecord #2] → [WindowState #2]
└─ Task #3 → [ActivityRecord #3] → [WindowState #3]
分屏场景:
RootWindowContainer (1个单例)
└─ DisplayContent (1个主屏)
├─ TaskDisplayArea #1 (上/左 半屏)
│ ├─ Task #1 → [ActivityRecord #1] → [WindowState #1]
│ └─ Task #2 → [ActivityRecord #2] → [WindowState #2]
└─ TaskDisplayArea #2 (下/右 半屏)
└─ Task #3 → [ActivityRecord #3] → [WindowState #3]
```
关键:非分屏时 TDA=1分屏时 TDA=2。Task 永远隶属于某个 TDA不能同时出现在两个 TDA 中。
</details>
---
**Q5**:解释 `singleTask` + `CLEAR_TOP``singleTask` 不带 `CLEAR_TOP` 的行为差异。给出具体的栈变化示例。
<details>
<summary>参考答案</summary>
```
初始状态Task#1 → [A] → [B] → [C] → [D] (D是栈顶A是singleTask)
场景1startActivity(A) — singleTask 不带 CLEAR_TOP
① AMS 找到 Task#1affinity匹配
② 发现 A 已在栈中(非栈顶)
③ 执行 clearTopfinish 掉 B、C、D
④ A 收到 onNewIntent
⑤ A 移到栈顶
结果Task#1 → [A] ← singleTask 默认隐式带 clearTop
场景2startActivity(A) — singleTask 显式带 FLAG_ACTIVITY_CLEAR_TOP
与场景1完全相同因为 singleTask 本身隐含了 clearTop 行为
★ 关键认知singleTask 在目标 Activity 已在栈中时,默认就会 clearTop
不需要额外加 FLAG_ACTIVITY_CLEAR_TOP。但如果目标不在栈中新的
则 CLEAR_TOP 无意义——因为没有东西需要清。
场景3singleTask 但 A 不在栈中
初始状态Task#1 → [X] → [Y]
startActivity(A) — singleTask
① AMS 查找 affinity 匹配的 Task → 找到 Task#1
② A 不在 Task#1 中
③ 创建新的 ActivityRecord A放入 Task#1
结果Task#1 → [X] → [Y] → [A]
注意:这次没有 clearTop因为 A 不在栈中,直接新建。
场景4singleTask 但目标在栈中,且不需要清栈
// 这种情况在标准 singleTask 中不存在
// 如果开发者想在 singleTask 的 Task 中保留栈,只能:
// ① 不用 singleTask改用 standard + 手动管理
// ② 或者在 onNewIntent 中自行处理而不依赖系统 clearTop
```
</details>
---
**Q6**:在分屏模式中,用户在「上半屏 Task-A」中点击按钮启动 Activity-Xstandard 模式。Activity-X 会被放入哪个 TDA为什么
<details>
<summary>参考答案</summary>
Activity-X 会被放入**上半屏的 TDA #1**。原因:
1. `ActivityStarter` 在确定 TDA 时,优先检查调用者 Activity 所在的 TDA
2. 由于调用者Task-A 中的按钮所在的 Activity位于 TDA #1
3. `standard` 模式且不带 `NEW_TASK` flag → 复用调用者的 Task
4. 调用者的 Task 属于 TDA #1 → Activity-X 进入 TDA #1
如果要让 Activity-X 进入下半屏的 TDA #2,需要:
- 使用 `ActivityOptions.setLaunchDisplayArea(TDA#2)` (非公开 API
- 或者使用 `Intent.FLAG_ACTIVITY_NEW_TASK` + `ActivityOptions.makeBasic().setLaunchDisplayId(...)`Android 12+
这体现了 **TDA 隔离原则**:默认情况下,启动操作不会跨越 TDA 边界。
</details>
---
### 实战编程题
**Q7**:编写一个 App包含 3 个 ActivityA、B、C验证以下场景
- A 声明为 `singleTask``taskAffinity="com.example.custom"`
- B 声明为 `standard`(默认 affinity
- C 声明为 `singleInstance`
要求:
1. 在 A 中启动 B再在 B 中启动 C使用 `dumpsys` 观察 Task 结构
2. 在 C 中启动 A`FLAG_ACTIVITY_NEW_TASK`),观察 A 回到自己的 Task 还是 C 的 Task
3. 按下 Home 键后,用 `adb shell dumpsys activity activities` 观察三个 Task 的 `visible` 状态
提交:`dumpsys` 输出截图 + 注释说明每个 Task/AcitivtyRecord 的含义。
<details>
<summary>预期结果提示</summary>
```
初始:启动 A(singleTask, affinity=custom)
Task#1(affinity=custom) → [A]
在 A 中启动 B(standard无 NEW_TASK)
Task#1(affinity=custom) → [A] → [B] ← B 进入 A 的 Task
在 B 中启动 C(singleInstance)
Task#1(affinity=custom) → [A] → [B]
Task#2(affinity=包名) → [C] ← C 独占一个 Task
在 C 中启动 A(带 NEW_TASK)
Task#1(affinity=custom) → [A] → [B] → [A_new?]
等等——A 是 singleTask + affinity=customTask#1 匹配!
Task#1(affinity=custom) → [A] ← clearTop(B被清掉A收到onNewIntent)
Task#2(affinity=包名) → [C] ← 不受影响
按 Home 键后:
两个 Task 都变为不可见visible=false但都不销毁
dumpsys 中 visible=false但 Activity 状态变为 STOPPED
```
</details>
---
## 🔗 参考资料
| 资源 | 路径/链接 |
|:-----|:--------|
| ActivityStarter.java | `frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java` |
| ActivityRecord.java | `frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java` |
| Task.java | `frameworks/base/services/core/java/com/android/server/wm/Task.java` |
| TaskDisplayArea.java | `frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java` |
| RootWindowContainer.java | `frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java` |
| WindowContainer.java | `frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java` |
| ActivityStackSupervisor.java | `frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java` |
| ActivityThread.java | `frameworks/base/core/java/android/app/ActivityThread.java` |
| Android 任务与返回栈 | https://developer.android.com/guide/components/activities/tasks-and-back-stack |
| Android 多窗口支持 | https://developer.android.com/guide/topics/large-screens/multi-window-support |
---
> **课程小结**:本课以 `WindowContainer` 五层继承树为起点,逐层剖析了 `Task` → `ActivityRecord` → `WindowState` 的数据结构与层级关系。完整追踪了 `startActivity()` 跨越三进程的 10 步调用链,重点分析了 `ActivityStarter` 中四种 launchMode 的分支逻辑和 `performClearTop` 的清栈算法。深入对比了 6 个核心 Intent Flags 的组合效果,解析了 `TaskDisplayArea` 在多窗口下的隔离机制。下一课将进入 **WMS 窗口管理WindowState、SurfaceFlinger 与 VSync 渲染管线**。
---
*Generated by AOSP Learning Assistant · Stage 5 Module A Lesson 2 · 2026-06-14*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff