25、Activity 的 Window 创建流程:从 startActivity 到 onResume 的窗口创建过程

说实话,Window 的创建流程,是很多 Android 开发者容易忽略的「暗线」。大家往往只关注 Activity 的生命周期,却不知道在 onCreateonResume 之间,系统到底干了多少脏活累活。今天我就带大家把这根线从头捋一遍。

起点:startActivity 发起的「窗口请求」

当我们调用 startActivity() 时,其实只是向 ActivityManagerService(AMS)发送了一个跨进程请求。AMS 负责调度 Activity 的生命周期,但它不负责画界面。真正跟窗口打交道的,是 WindowManagerService(WMS)。

流程大致是这样的:

  1. AMS 通知应用进程创建 Activity:通过 ApplicationThread 回调到应用端。
  2. ActivityThread 执行 handleLaunchActivity():这里会创建 Activity 实例,并调用 attach() 方法。
  3. attach() 中创建 PhoneWindow:每个 Activity 都持有一个 Window 对象,默认就是 PhoneWindow

关键点PhoneWindow 是 Window 的唯一实现类。它不是在 onCreate 里创建的,而是在 attach() 阶段就已经实例化了。

中间站:onCreate 中的 setContentView

我们在 onCreate 里调用 setContentView(),其实是在操作 PhoneWindow。它内部做了三件事:

  • 创建 DecorView(如果还没有的话)
  • 解析布局文件,添加到 DecorViewmContentParent
  • 通知 WindowManager 更新布局

嗯,这里要注意:此时 Window 还没有真正被添加到 WMS 中。你只是准备好了视图树,但系统还不知道这个窗口的存在。

我的经验:我曾经在 onCreate 里直接获取 DecorView 的宽高,结果全是 0。原因就是 Window 还没被添加到 WMS,没有经过 measure/layout 流程。后来我改用 ViewTreeObserver 监听全局布局完成事件才搞定。

关键转折:onResume 与 WindowManager.addView

真正的窗口创建发生在 onResume 之后。流程是这样的:

  1. ActivityThread 执行 handleResumeActivity()
  2. 内部调用 Activity.onResume()(我们重写的那个)
  3. 接着调用 WindowManager.addView(decorView, layoutParams)

这个 addView 调用,才是真正把 DecorView 注册到 WMS 的入口。它内部会通过 ViewRootImpl 建立与 WMS 的通信通道。

核心逻辑WindowManager.addView()ViewRootImpl.setView()WMS.addWindow()。至此,窗口才算正式创建完成。

ViewRootImpl 的角色:窗口的「大脑」

ViewRootImpl 是连接 DecorView 和 WMS 的桥梁。它负责:

  • 向 WMS 请求添加窗口
  • 接收 WMS 的输入事件(触摸、按键)
  • 触发 measure/layout/draw 流程
  • 处理窗口大小变化、配置变更等

说白了,ViewRootImpl 才是真正干活的。没有它,DecorView 只是一棵孤立的视图树,画不出任何东西。

流程图:从 startActivity 到 onResume 的窗口创建

startActivity() AMS 调度 ActivityThread.handleLaunchActivity() attach() → 创建 PhoneWindow onCreate → setContentView onResume → WindowManager.addView() 用户发起 跨进程通信 应用端主线程 Window 对象诞生 视图树准备 窗口真正注册到 WMS

避坑指南:我踩过的几个坑

我曾经onCreate 里调用 getWindow().addFlags() 设置全屏标志,结果发现状态栏闪了一下才消失。原因就是 addFlags 只是修改了 PhoneWindow 内部的 LayoutParams,但此时窗口还没添加到 WMS。正确的做法是在 onCreate 里设置,但效果要在 onResume 之后才生效。

另一个常见问题是:为什么 onResume 里拿不到 View 的宽高? 因为 onResume 回调时,WindowManager.addView() 还没执行完。View 的 measure/layout 是在 ViewRootImplperformTraversals() 中完成的,这个调用是异步的。所以如果你非要在 onResume 里拿宽高,可以用 view.post() 把任务丢到消息队列末尾。

总结一下关键时间点

阶段 窗口状态 关键操作
attach() PhoneWindow 创建 Window 对象诞生,但未关联 WMS
onCreate DecorView 创建 setContentView 填充布局
onStart 窗口仍不可见 无窗口相关操作
onResume WindowManager.addView() 窗口注册到 WMS,开始渲染
onResume 之后 窗口可见 ViewRootImpl 执行首次遍历

我个人习惯:在 onCreate 里只做视图树的构建,所有跟窗口属性相关的操作(比如设置软键盘模式、屏幕方向)都放在 onResume 之前完成。这样能保证 addViewLayoutParams 已经是最终状态,避免不必要的窗口重建。

最后说一句:理解这个流程,对排查启动白屏、窗口泄漏、布局异常等问题非常有帮助。你想想看,如果连窗口什么时候创建都不知道,怎么定位问题呢?

公众号:蓝海资料掘金营,微信deep3321