25、Activity 的 Window 创建流程:从 startActivity 到 onResume 的窗口创建过程
说实话,Window 的创建流程,是很多 Android 开发者容易忽略的「暗线」。大家往往只关注 Activity 的生命周期,却不知道在 onCreate 到 onResume 之间,系统到底干了多少脏活累活。今天我就带大家把这根线从头捋一遍。
起点:startActivity 发起的「窗口请求」
当我们调用 startActivity() 时,其实只是向 ActivityManagerService(AMS)发送了一个跨进程请求。AMS 负责调度 Activity 的生命周期,但它不负责画界面。真正跟窗口打交道的,是 WindowManagerService(WMS)。
流程大致是这样的:
- AMS 通知应用进程创建 Activity:通过
ApplicationThread回调到应用端。 - ActivityThread 执行
handleLaunchActivity():这里会创建 Activity 实例,并调用attach()方法。 attach()中创建PhoneWindow:每个 Activity 都持有一个 Window 对象,默认就是PhoneWindow。
关键点:PhoneWindow 是 Window 的唯一实现类。它不是在 onCreate 里创建的,而是在 attach() 阶段就已经实例化了。
中间站:onCreate 中的 setContentView
我们在 onCreate 里调用 setContentView(),其实是在操作 PhoneWindow。它内部做了三件事:
- 创建
DecorView(如果还没有的话) - 解析布局文件,添加到
DecorView的mContentParent中 - 通知
WindowManager更新布局
嗯,这里要注意:此时 Window 还没有真正被添加到 WMS 中。你只是准备好了视图树,但系统还不知道这个窗口的存在。
我的经验:我曾经在 onCreate 里直接获取 DecorView 的宽高,结果全是 0。原因就是 Window 还没被添加到 WMS,没有经过 measure/layout 流程。后来我改用 ViewTreeObserver 监听全局布局完成事件才搞定。
关键转折:onResume 与 WindowManager.addView
真正的窗口创建发生在 onResume 之后。流程是这样的:
- ActivityThread 执行
handleResumeActivity() - 内部调用
Activity.onResume()(我们重写的那个) - 接着调用
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 的窗口创建
避坑指南:我踩过的几个坑
我曾经在 onCreate 里调用 getWindow().addFlags() 设置全屏标志,结果发现状态栏闪了一下才消失。原因就是 addFlags 只是修改了 PhoneWindow 内部的 LayoutParams,但此时窗口还没添加到 WMS。正确的做法是在 onCreate 里设置,但效果要在 onResume 之后才生效。
另一个常见问题是:为什么 onResume 里拿不到 View 的宽高? 因为 onResume 回调时,WindowManager.addView() 还没执行完。View 的 measure/layout 是在 ViewRootImpl 的 performTraversals() 中完成的,这个调用是异步的。所以如果你非要在 onResume 里拿宽高,可以用 view.post() 把任务丢到消息队列末尾。
总结一下关键时间点
| 阶段 | 窗口状态 | 关键操作 |
|---|---|---|
| attach() | PhoneWindow 创建 | Window 对象诞生,但未关联 WMS |
| onCreate | DecorView 创建 | setContentView 填充布局 |
| onStart | 窗口仍不可见 | 无窗口相关操作 |
| onResume | WindowManager.addView() | 窗口注册到 WMS,开始渲染 |
| onResume 之后 | 窗口可见 | ViewRootImpl 执行首次遍历 |
我个人习惯:在 onCreate 里只做视图树的构建,所有跟窗口属性相关的操作(比如设置软键盘模式、屏幕方向)都放在 onResume 之前完成。这样能保证 addView 时 LayoutParams 已经是最终状态,避免不必要的窗口重建。
最后说一句:理解这个流程,对排查启动白屏、窗口泄漏、布局异常等问题非常有帮助。你想想看,如果连窗口什么时候创建都不知道,怎么定位问题呢?