8、addView 流程源码分析:从应用层到 WMS 的完整调用链路
这一章,我们来啃一块硬骨头——addView 的完整调用链路。
说实话,很多 Android 开发者用了好几年 WindowManager,却不知道 addView 背后到底发生了什么。我记得刚入行那会儿,以为 addView 就是把 View 塞进屏幕,简单得很。直到有一次线上出了个悬浮窗权限的诡异 Bug,我追了三天源码,才真正搞懂这条链路有多长。
今天我就带你走一遍,从应用层调用 windowManager.addView() 开始,一路追到 WMS 里注册窗口。你想想看,这中间跨了多少个进程?
8.1 起点:WindowManager.addView()
我们平时用的 WindowManager 其实是个接口。真正的实现类是 WindowManagerImpl。你写这段代码的时候:
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.addView(myView, layoutParams);
实际上调用的是 WindowManagerImpl.addView()。
我个人习惯把这里叫做「应用层的入口」。它做的事情很简单——把 View 和 LayoutParams 包装一下,扔给 WindowManagerGlobal。
关键点:WindowManagerImpl 本身不干活,它是个代理。真正干活的单例是 WindowManagerGlobal。
8.2 中转站:WindowManagerGlobal.addView()
进入 WindowManagerGlobal 之后,事情开始变得有意思了。
它会做三件事:
- 检查参数合法性——比如 LayoutParams 不能为空,View 不能为空。
- 创建 ViewRootImpl——这是 View 和 WindowManager 之间的桥梁。
- 调用 ViewRootImpl.setView()——这一步开始跨进程。
嗯,这里要注意:ViewRootImpl 是在应用进程里创建的,但它内部持有 IWindowSession 的代理对象。这个代理指向的是 WMS 所在进程的 Session 对象。
说白了,从这一步开始,我们就要跨进程通信了。
8.3 跨进程一跳:ViewRootImpl.setView()
ViewRootImpl.setView() 这个方法,我建议你好好看看源码。它做了几件关键的事:
- 把
IWindow的代理(也就是应用端的窗口令牌)传给 WMS - 调用
mWindowSession.addToDisplay()——这是 Binder 调用 - 等待 WMS 返回结果
我曾经在项目里遇到过一个坑:addToDisplay 返回 WindowManagerGlobal.ADD_OKAY 不代表 View 已经显示了,它只代表 WMS 接受了这个窗口。真正的显示还要等下一次 vsync 信号。
避坑指南:addView 返回后立刻操作 View 的宽高,大概率是 0。因为此时 View 还没有完成 measure/layout/draw。我曾经因为这个踩过坑,后来用 ViewTreeObserver.OnGlobalLayoutListener 才搞定。
8.4 WMS 侧:Session.addToDisplay()
Binder 调用到了 WMS 进程,进入 Session.addToDisplay()。Session 是 WMS 为每个应用进程创建的一个会话对象。
Session 会调用 WMS.addWindow()。这一步是真正的核心逻辑:
| 步骤 | 说明 |
|---|---|
| 1. 检查权限 | 比如悬浮窗权限、系统窗口权限 |
| 2. 检查 token | 窗口令牌是否合法 |
| 3. 创建 WindowState | WMS 内部表示窗口的对象 |
| 4. 分配 Surface | 通过 SurfaceFlinger 分配图层 |
| 5. 添加到窗口列表 | 按 z-order 排序插入 |
你想想看,这一步做完之后,WMS 就知道「哦,有个新窗口来了」。但它还没开始画。
8.5 回到应用端:ViewRootImpl 的后续
WMS 返回结果后,ViewRootImpl 会继续做几件事:
- 把
IWindow的 Binder 对象传给 WMS,这样 WMS 可以回调应用进程 - 请求第一次布局(requestLayout)
- 注册输入事件接收器
我记得第一次读源码时,最让我惊讶的是 ViewRootImpl 居然实现了 InputHandler。也就是说,所有触摸事件都是先到 ViewRootImpl,再分发给 View 树的。
8.6 完整链路图
下面这张图,我画了 addView 从应用层到 WMS 的完整调用链路。你保存下来,以后面试或者排查问题都能用上。
8.7 几个容易忽略的细节
走完这条链路,我再补充几个我踩过的坑:
- Token 检查:Activity 的 token 是系统自动传的,但如果你用
TYPE_APPLICATION_OVERLAY,需要手动申请权限。我遇到过线上用户反馈悬浮窗不显示,最后发现是 Android 10 以后权限策略变了。 - Surface 分配时机:WMS 分配 Surface 是异步的。addView 返回时 Surface 可能还没创建好。我曾经在 addView 后立刻调用
View.getWidth(),结果全是 0。 - 窗口顺序:WMS 内部维护了一个窗口列表,按 z-order 排序。如果你 addView 时指定了
TYPE_APPLICATION_OVERLAY,它会排在普通应用窗口之上,但低于系统窗口。
个人经验:排查 addView 相关问题时,我一般先看 logcat 里有没有 WindowManager: addView 的日志。如果没有,说明根本没走到 WMS。如果有,再看 WindowState 是否创建成功。这个方法帮我定位过至少 5 个线上问题。
好了,addView 的完整调用链路就讲到这里。你理解了这条链路,以后遇到窗口显示异常、悬浮窗权限问题,就知道该从哪里下手了。
公众号:蓝海资料掘金营,微信deep3321