Appearance
Activity 全生:启动链路、生命周期本质与窗口挂接
本页导读:本页把 Activity 从
startActivity()那一刻讲到第一帧上屏:应用侧与服务侧的双向调用链、生命周期回调为什么只是 Binder 消息、四种启动模式与任务栈、配置变更重建,以及 Window/DecorView 何时才真正挂进屏幕。前置:进程模型与架构篇 Binder、WMS 两页。
一、两个 Activity:应用里一个,system_server 里一个
很多人把 Activity 当成「界面的实现类」,但真正的控制权从来不在应用侧:
- 应用侧:
android.app.Activity实例,承载业务代码与 View 树;它的状态字段只是系统侧状态的本地缓存。 - 服务侧:
ActivityRecord(com.android.server.wm包)+ 所属Task+ActivityTaskManagerService(ATMS,Android 10 从 AMS 拆出)。它决定谁能 resume、谁该 pause、窗口放哪。 - Android 12+ 新增专职状态机:
LifecycleManager(com.android.server.wm.lifecycle包)统一管理每个 ActivityRecord 的ON_RESUME/ON_STOP/DESTROY事件队列,取代旧版散落在 ActivityRecord/ActivityTaskSupervisor 里的判断。
所以分析 Activity 的一切怪异现象(onPause 时机、重建、假死)都要问:系统侧此刻给它发了什么消息。
二、startActivity 的完整旅程(双向链)
以「A 调 startActivity(B)」为例,请求与服务两条方向各走一遍:
text
应用侧(进程 A) 服务侧(system_server)
Activity.startActivity
└ Instrumentation.execStartActivity
└ ActivityTaskManager.getService() ── Binder ──▶ ActivityTaskManagerService.startActivity
├ ActivityStartController(校验:权限/后台启动限制/launchMode/分屏)
├ 找到或新建 Task(任务栈匹配见第四节)
├ 目标进程未起 → AMS 走 Zygote fork + bindApplication
├ Task.startActivityUnchecked → 生成 ActivityRecord
└ realStartActivity ── Binder(oneway) ──┐
ActivityThread.ApplicationThread ◀──┘
└ scheduleTransaction(ClientTransaction[LaunchActivityItem])
└ TransactionExecutor → handleLaunchActivity
└ performLaunchActivity:Instrumentation.newActivity → Activity.attach(创建 PhoneWindow)
└ onCreate() 执行业务代码
(稍后)LifecycleManager 队列化 ResumeActivityItem → 再次 Binder → onResume()五个常被忽略的细节:
- 入口不是 AMS:Android 10 起走
IActivityTaskManager(ATMS),ActivityManager.getService()只负责进程/服务/广播侧。 - 校验发生在服务端:Android 10+ 的后台启动 Activity 限制(BAL)、Android 12 的 trampoline 限制(后台服务里顺手 startActivity 会被拦)都在
ActivityStartController里做。 - 目标进程可能还没活:先走完整的 Zygote fork →
bindApplication(见进程模型页),Activity 启动请求排队等待。 - onCreate 不是「已经显示」:LaunchActivityItem 只负责创建对象并调 onCreate;resume 由服务侧稍后单独下发。
- 对端回包:Activity 侧生命周期完成后通过
ActivityClientController回执,服务侧据此推进状态机——超时不回执就是 ANR 的原料(见 ANR 页)。
这是一套典型的两阶段提交:服务侧先记账(ActivityRecord 状态已翻),再通知应用执行,最后等回执确认。理解「记账先行」能解释很多现象:系统认为的 resumed 比你本地 onResume 早开始、晚结束,中间任何主线程卡顿都会被系统按 ANR 口径计时。
命令行观察这条链最直观:
bash
adb shell am start -W -n com.example/.MainActivity # -W 等待启动完成并打印 TotalTime
adb logcat -s ActivityTaskManager:V Timeline:V # START / DISPLAYED /_timeline 事件
adb shell dumpsys activity activities # 看 Task 与 ActivityRecord 现状三、生命周期的本质:一排被排好序的 Binder 消息
系统侧 LifecycleManager 把状态变化排成事件队列,打包成一批 ClientTransactionItem 经 oneway Binder 发给应用,客户端按序执行:
java
// frameworks/base/core/java/android/app/server/ClientTransaction.java(节选大意)
public void execute(ClientTransactionHandler client, IBinder token,
TransactionExecutor callbackExecutor) {
// 先跑 pre-execution(如 ConfigurationChangeItem)
// 再按序执行生命周期项:LaunchActivityItem → ResumeActivityItem → DestroyActivityItem...
for (int i = 0; i < mActivityCallbacks.size(); i++) {
ClientTransactionItem item = mActivityCallbacks.get(i);
callbackExecutor.execute(item, activityClient, client, this);
}
}由此得到三条铁律:
- 顺序由系统裁决,不由应用决定:B 的 onCreate 一定在 A 的 onPause 回执之后——因为 ResumeActivityItem 排在 PAUSE 队列状态之后(
queueIdleStateLocked类调度),应用侧任何「提前干活」的尝试都是在本地自嗨。 - 回调可能整批到达:冷启动时
onCreate+onStart+onResume同批送达;被 LMK 杀掉再回来是重建,不会补发 onPause。 - 不要在回调里做同步等待:回调都在主线程,阻塞它 = 阻塞回执 = 给服务侧制造 ANR(见 ANR 页)。
常用的事务项家族(android.app.server 包,均经上面这一个通道下发):
LaunchActivityItem/ResumeActivityItem/PauseActivityItem/StopActivityItem/DestroyActivityItem:生命周期五件套;NewIntentItem/ActivityResultItem:onNewIntent 与 onActivityResult 的本质;ConfigurationChangeItem/topResumedActivityChangeItem:配置变更与「谁是栈顶」通知;WindowVisibilityRequestedItem(以 AOSP 为准):可见性变化驱动 onHiddenChanged 类逻辑。
onSaveInstanceState 在 Android 12 后固定于 onStop 之后(Activity#onStateNotRunning 语义),新版把「保存」与「销毁」解耦,别再用「onDestroy 前最后一次保存」的旧直觉。
四、启动模式与任务栈:谁复用谁
任务匹配本质是 ActivityStarter 的三问:找哪个栈(taskAffinity + FLAG_ACTIVITY_NEW_TASK)→ 栈内找谁(intentFilter + singleTask/singleInstance)→ 怎么摆放(clear top/multiWindow 等)。
| launchMode | 复用规则 | 典型场景 | 备注 |
|---|---|---|---|
| standard | 总建新实例,压入当前栈 | 普通详情页 | 可被 FLAG_ACTIVITY_MULTIPLE_TASK 拆栈 |
| singleTop | 栈顶同组件复用,回调 onNewIntent | 搜索结果页 | 非栈顶仍会新建 |
| singleTask | 全局按 affinity 找栈,复用并清掉其上 Activity | App 主页 | 复用走 onNewIntent |
| singleInstance | 独占任务栈 | 车载/悬浮小窗类 | Android 12 起语义收紧 |
onNewIntent里setIntent()是最易漏的一行——不写,getIntent()永远是旧的:
kotlin
// 复用实例的参数刷新样板
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent) // 忘记这行 = 经典线上 bug
refreshWith(intent.data)
}FLAG_ACTIVITY_CLEAR_TOP/FLAG_ACTIVITY_CLEAR_TASK决定复用时要不要清掉压在其上的页面,和 launchMode 正交。- Android 12+ 对多窗口/桌面小窗启动有独立落栈规则,多形态机型(折叠屏、平板)务必用
ActivityEmbedding/TaskFragment(API 34+)替代手写 flags。
版本注记:基于 Android 16 / API 36;Android 10 差异:后台启动 Activity 默认被拒(仅白名单豁免);Android 12 差异:启动页系统接管(
SplashScreenAPI),trampoline 拦截;Android 15/16 差异:预测式返回手势下生命周期事件顺序微调(onBackInvokedCallback取代 onBackPressed,见事件页)。
五、配置变更与重建
默认行为:配置变更 = 销毁重建。 旋转、深浅色、语言、字体缩放、分屏尺寸变化……只要生效的配置命中 android:configChanges 未声明处理的项,就走 DESTROY 队列。
重建的真实流程(两端配合):
- 服务侧感知 display 配置变化 → 给栈内每个 ActivityRecord 排
ConfigurationChangeItem+ DESTROY + 新 LAUNCH; - 应用侧旧实例先
onSaveInstanceState → onStop → onDestroy,Bundle 经 Binder 回存服务侧; - 新实例
onCreate(savedInstanceState)重建 View 树,onRestoreInstanceState自动恢复 View 树状态。
工程决策树:
- 能不重建就不重建:AndroidX 的
ConfigurationController/ActivityRetainedContainer方向已定;但configChanges="orientation|screenSize|..."拦截仍合法,代价是全靠自己重绘(视频播放器是经典理由)。 - 数据别绑实例:ViewModel(跨配置变更存活)+ SavedStateHandle(跨进程死亡存活)两层;网络请求挂 ViewModel 生命周期,别挂 Activity。
- 拦截不等于万能:Android 8+ 多窗口拖拽会连续触发配置变更,依赖 configChanges 的界面要做防抖。
- 深色模式专项:
uiMode配置变更在 Android 10+ 自动跟随系统,App 若用 DayNight 主题需保证资源有 night 变体,否则表现是「切深色不生效」而非崩溃。 - 折叠屏/大屏:尺寸变化频率远高于手机旋转,
WindowSizeChangeProcessor/Jetpack WindowManager 是官方给的新答案,旧 configChanges 口径在新形态上会失灵。
六、Window 与 DecorView:界面何时才真挂上屏幕
Activity 只是「控制对象」,上屏靠 Window 三件套,时序藏得很深:
text
Activity.attach() // performLaunchActivity 内,早于 onCreate
└ mWindow = new PhoneWindow() // com.android.internal.policy
setContentView(R.layout.x) // 解析布局挂进 DecorView 的 content 容器(此时只是内存 View 树)
handleResumeActivity() // onResume 返回后
└ makeVisible → WindowManagerImpl.addView(decorView)
└ WindowManagerGlobal.addView → new ViewRootImpl(this)
└ ViewRootImpl.setView() ── IWindowSession ──▶ WindowManagerService 登记窗口
└ 下一次 vsync:performTraversal → 测量/布局/绘制 → SurfaceFlinger 合成上屏三个关键结论:
- onResume 返回时 View 还没有窗口:ViewRootImpl 在 resume 之后才创建,
onResume里拿不到宽高;要等第一帧后干活用View.post/doOnAttachedToWindow时机。 - 预览窗口(Starting Window)早于这一切:点图标时的白/透明底图是 WMS 按 theme 的
windowBackground画的,不是你的 View;Android 12+ 系统开屏页(SplashScreenAPI)也画在这层。 - Window 对象不等于 WMS 的窗口:应用侧是
PhoneWindow/DecorView,系统侧是 WMS 的WindowState,靠IWindow/IWindowSession两条 Binder 双向联系。
七、常见误区问答
Q1:onPause 里保存数据库安全吗? 不安全,它只保证「即将让出前台」,不保证进程后续存活。轻量状态放 onSaveInstanceState/落盘走 WorkManager。
Q2:为什么我的 Activity 启动白屏一下? theme 没配 windowBackground,预览窗口画了默认白。Android 12+ 用 SplashScreen API 统一接管;深色模式记得给 night 变体。
Q3:singleTask 复用了,为什么数据还是旧的? onNewIntent 没 setIntent,或页面里用 getIntent() 读参数。复用实例的参数刷新是应用的责任。
Q4:进程被 LMK 杀死重建,怎么和用户主动打开区分? 冷启动看 savedInstanceState != null;更可靠的进程死亡统计用 ActivityManager#getHistoricalProcessExitReasons(API 30+)。
Q5:为什么我 startActivity 有时毫无反应也不报错? 优先查三处:后台启动 Activity 被 BAL 策略静默拦截(Android 10+,logcat 有 abort 日志)、launchMode 复用了栈内旧实例但没刷参数、FLAG_ACTIVITY_NEW_DOCUMENT 等 flags 把任务开去了别处。用上面 am start -W 与 dumpsys activity activities 交叉验证。
Q6:预测式返回手势会不会打乱生命周期? 不会改 onPause/onStop 顺序;它改变的是返回的「投递方式」——系统手势先走 OnBackInvokedDispatcher(API 33+,target 34 默认启用),你可以先收到预测进度回调做动画,再决定是否真正 finish。
本页小结:Activity 的真相是「系统侧状态机 + 应用侧执行器」:启动是服务侧裁决、应用侧回执的双向链;生命周期回调只是按序投递的 ClientTransaction 项;launchMode 管任务栈复用;配置变更默认重建;View 树到 onResume 之后才经 ViewRootImpl 挂入 WMS——把「我的方法调用」思维换成「消息收发」思维,这个体系就通了。
参考链接
- https://developer.android.com/guide/components/activities
- https://developer.android.com/guide/components/activities/activity-lifecycle
- https://developer.android.com/guide/components/tasks/back-stack(任务栈)
- https://cs.android.com/android/platform/frameworks/base/+/main:core/java/android/app/ActivityThread.java
- https://cs.android.com/android/platform/frameworks/base/+/main:services/core/java/com/android/server/wm/lifecycle/LifecycleManager.java
- 站内:进程模型、ViewRootImpl 与四大流程、ANR、架构篇 WMS