Skip to content

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()

五个常被忽略的细节:

  1. 入口不是 AMS:Android 10 起走 IActivityTaskManager(ATMS),ActivityManager.getService() 只负责进程/服务/广播侧。
  2. 校验发生在服务端:Android 10+ 的后台启动 Activity 限制(BAL)、Android 12 的 trampoline 限制(后台服务里顺手 startActivity 会被拦)都在 ActivityStartController 里做。
  3. 目标进程可能还没活:先走完整的 Zygote fork → bindApplication(见进程模型页),Activity 启动请求排队等待。
  4. onCreate 不是「已经显示」:LaunchActivityItem 只负责创建对象并调 onCreate;resume 由服务侧稍后单独下发。
  5. 对端回包: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 找栈,复用并清掉其上 ActivityApp 主页复用走 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 差异:启动页系统接管(SplashScreen API),trampoline 拦截;Android 15/16 差异:预测式返回手势下生命周期事件顺序微调(onBackInvokedCallback 取代 onBackPressed,见事件页)。

五、配置变更与重建 ​

默认行为:配置变更 = 销毁重建。 旋转、深浅色、语言、字体缩放、分屏尺寸变化……只要生效的配置命中 android:configChanges 未声明处理的项,就走 DESTROY 队列。

重建的真实流程(两端配合):

  1. 服务侧感知 display 配置变化 → 给栈内每个 ActivityRecord 排 ConfigurationChangeItem + DESTROY + 新 LAUNCH;
  2. 应用侧旧实例先 onSaveInstanceState → onStop → onDestroy,Bundle 经 Binder 回存服务侧;
  3. 新实例 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 合成上屏

三个关键结论:

  1. onResume 返回时 View 还没有窗口:ViewRootImpl 在 resume 之后才创建,onResume 里拿不到宽高;要等第一帧后干活用 View.post/doOnAttachedToWindow 时机。
  2. 预览窗口(Starting Window)早于这一切:点图标时的白/透明底图是 WMS 按 theme 的 windowBackground 画的,不是你的 View;Android 12+ 系统开屏页(SplashScreen API)也画在这层。
  3. 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——把「我的方法调用」思维换成「消息收发」思维,这个体系就通了。

参考链接 ​

基于 Android 16 / API 36 · 内容为原创整理,转载请注明出处