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 之后,事情开始变得有意思了。

它会做三件事:

  1. 检查参数合法性——比如 LayoutParams 不能为空,View 不能为空。
  2. 创建 ViewRootImpl——这是 View 和 WindowManager 之间的桥梁。
  3. 调用 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 的完整调用链路。你保存下来,以后面试或者排查问题都能用上。

addView 完整调用链路 应用进程 WMS 进程 WindowManagerImpl.addView() WindowManagerGlobal.addView() 创建 ViewRootImpl ViewRootImpl.setView() Session.addToDisplay() WMS.addWindow() 创建 WindowState 分配 Surface Binder 调用 返回结果 跨进程通信通过 Binder 机制完成 ViewRootImpl 是连接应用层和 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