16、ViewRootImpl 的角色:它是如何连接 WindowManager 与 View 的?

说实话,Android 的 UI 体系里,最容易被忽略但又最关键的角色,就是 ViewRootImpl。

很多人知道 WindowManager 能加 View,也知道 View 能画界面。但这两者之间到底怎么连起来的?谁在中间做调度?谁负责把触摸事件分发给 View?谁去触发 measure/layout/draw?

答案就是 ViewRootImpl。

我个人习惯把它叫做「UI 系统的中枢神经」。它不直接暴露给开发者,但每一帧画面、每一次触摸、每一个输入事件,都得经过它。

ViewRootImpl 是什么?

简单说,ViewRootImpl 是一个 Handler + View 树的根节点 + WMS 的客户端代理 的合体。

它不是一个 View,也不是一个 Window。它是一个 内部 Binder 通信的桥梁

你想想看,WindowManager 运行在系统服务端(WMS),而 View 运行在应用进程。这两边怎么通信?

嗯,就是靠 ViewRootImpl 来做的。

核心职责一句话总结:

ViewRootImpl 负责将应用层的 View 树,与系统层的 WindowManagerService 进行绑定,并驱动整个 UI 的渲染流程。

它是怎么连接 WindowManager 和 View 的?

我们来看一条完整的链路:

  1. 应用层调用 WindowManager.addView() —— 比如你在 Activity 里 setContentView,或者自己 new 一个 Dialog。
  2. WindowManagerGlobal 创建 ViewRootImpl —— 每个 Window 对应一个 ViewRootImpl 实例。
  3. ViewRootImpl 通过 Binder 向 WMS 发起 addWindow 请求 —— 这一步是跨进程的。
  4. WMS 分配 Surface,返回给 ViewRootImpl —— 这个 Surface 就是画布。
  5. ViewRootImpl 把 Surface 交给 View 树 —— 然后开始 measure、layout、draw。

说白了,WindowManager 只负责「注册窗口」,View 只负责「画自己」。中间的调度、同步、事件分发,全是 ViewRootImpl 在干。

ViewRootImpl 的核心成员

成员 作用
mView 根 View(也就是你 addView 传进来的那个 View)
mSurface 从 WMS 拿到的画布,所有绘制最终画到这里
mWindowSession 与 WMS 通信的 Binder 代理
mHandler 主线程 Handler,用于调度输入事件和绘制
mChoreographer 垂直同步信号驱动,控制帧率
mAttachInfo 存储窗口信息,传递给子 View

我在项目中遇到过一个问题:某个自定义弹窗在低端机上频繁闪白屏。查了半天,发现是 ViewRootImpl 的 Surface 被 WMS 提前销毁了,但 View 还在尝试绘制。嗯,这就是典型的「窗口生命周期不同步」问题。

ViewRootImpl 的三大核心流程

1. 窗口添加流程

当 WindowManager.addView() 被调用时,ViewRootImpl 会执行 setView() 方法。这个方法里做了三件大事:

  • 调用 mWindowSession.addToDisplay() 向 WMS 注册窗口
  • 把 mView 赋值,并触发第一次 requestLayout()
  • 注册输入事件通道(InputChannel)
// 伪代码,简化版
public void setView(View view, WindowManager.LayoutParams attrs) {
    synchronized (this) {
        mView = view;
        // 向 WMS 注册窗口
        res = mWindowSession.addToDisplay(mWindow, mSeq, attrs, ...);
        // 触发第一次布局
        requestLayout();
        // 注册输入事件
        mInputChannel = new InputChannel();
        mWindowSession.inputChannelCreated(mInputChannel);
    }
}

避坑指南:

我曾经在 addView 之后立刻去拿 View 的宽高,结果全是 0。为什么?因为 ViewRootImpl 的 requestLayout 是异步的,measure 还没执行。正确的做法是用 View.post() 或者 OnGlobalLayoutListener。

2. 绘制调度流程

ViewRootImpl 通过 Choreographer 监听垂直同步信号。每收到一个 vsync 信号,就会执行一次 doTraversal()。

这个 doTraversal 里干了什么?

  • performTraversals() —— 这是核心方法
  • 内部依次调用:performMeasure() → performLayout() → performDraw()
  • 最终通过 Surface 的 lockCanvas / unlockCanvasAndPost 把内容提交给 SurfaceFlinger

你想想看,为什么有时候界面卡顿?其实就是 performTraversals 执行时间超过了 16ms,导致下一帧的 vsync 信号来了,上一帧还没画完。

3. 事件分发流程

触摸事件从屏幕硬件出发,经过 InputReader → InputDispatcher → ViewRootImpl 的 InputEventReceiver。

ViewRootImpl 收到事件后,调用 mView.dispatchTouchEvent(),把事件传给 DecorView,然后一路往下分发。

// 事件分发的入口
public void dispatchInputEvent(InputEvent event, ...) {
    // 交给 View 树处理
    mView.dispatchTouchEvent((MotionEvent) event);
}

这里有个细节:ViewRootImpl 是在主线程的 Handler 里处理事件的。所以如果你的主线程被阻塞了,触摸事件也会卡住。我曾经遇到过一个 bug:某个页面点击无响应,查到最后是主线程在做 IO 操作,导致 InputEventReceiver 无法及时处理事件。

ViewRootImpl 与 WMS 的通信模型

我画了一张图,帮你理解整个通信链路:

应用进程 View 树 ViewRootImpl Surface setView() performDraw() 系统进程 WMS SurfaceFlinger InputDispatcher Binder (addWindow) Binder (Surface) InputEvent

从这张图可以清楚看到:ViewRootImpl 是应用进程和系统进程之间的唯一桥梁。左边是 View 树,右边是 WMS 和 SurfaceFlinger,中间全靠 ViewRootImpl 做 Binder 通信和调度。

ViewRootImpl 的生命周期

它和 Window 是绑定的。Window 显示的时候,ViewRootImpl 就活着。Window 被移除的时候,ViewRootImpl 就 die 掉。

具体来说:

  • 创建时机:WindowManager.addView() 被调用时
  • 销毁时机:WindowManager.removeView() 或窗口被 WMS 强制移除时
  • 重建时机:配置变更(如屏幕旋转)时,ViewRootImpl 会重新创建

注意:

ViewRootImpl 的 die() 方法被调用后,它会释放 Surface、注销输入通道、移除所有回调。如果你在 View 里持有 ViewRootImpl 的引用,记得及时清理,否则可能造成内存泄漏。

我曾经在自定义弹窗里忘记释放 ViewRootImpl 的引用,结果弹窗关闭后,Surface 还活着,导致屏幕残留画面。嗯,这个坑我踩过。

总结一下

ViewRootImpl 不是什么神秘的东西。它就是那个在幕后默默干活的「总管」。

  • 它把 View 树挂载到 WMS 上
  • 它驱动 measure/layout/draw
  • 它分发触摸事件
  • 它管理 Surface 的生命周期

没有它,WindowManager 和 View 就是两个孤岛。有了它,整个 UI 系统才能跑起来。

一句话记住:

ViewRootImpl = 应用层 View 树 与 系统层 WMS 之间的 Binder 桥梁 + UI 渲染调度器 + 事件分发入口。


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