一、从 addView() 说起

各位好,今天我们聊聊 WindowManager 添加 View 的完整流程。说实话,这个流程我研究了不下十遍,每次看源码都有新发现。你想想看,一个简单的 windowManager.addView() 调用,背后竟然牵动了那么多系统组件。

先看个最简单的例子:

// 最常见的用法
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.addView(myView, layoutParams);

就这一行代码,背后发生了什么?我带你一层层扒开看看。

二、WindowManagerImpl 的 addView()

实际上,我们拿到的 WindowManager 对象,是 WindowManagerImpl 的实例。它的 addView() 方法很简单:

// WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow);
}

看到没?它直接委托给了 WindowManagerGlobal。为什么要多这一层?我个人理解,这是为了统一管理所有窗口的添加操作。毕竟一个应用可能有多个 Activity,每个 Activity 都有自己的 WindowManager,但全局只需要一个 WindowManagerGlobal。

三、WindowManagerGlobal 的核心逻辑

好戏从这里开始。WindowManagerGlobal 的 addView() 做了几件关键的事:

  1. 检查参数合法性
  2. 创建 ViewRootImpl
  3. 建立 WindowSession
  4. 调用 ViewRootImpl.setView()

看代码:

// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    // 参数检查
    if (view == null) throw new IllegalArgumentException("view must not be null");
    if (display == null) throw new IllegalArgumentException("display must not be null");
    
    // 创建 ViewRootImpl
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    
    // 保存到列表中
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
    // 关键一步:建立连接
    root.setView(view, wparams, panelParentView);
}

嗯,这里要注意:mViewsmRootsmParams 三个列表是同步维护的。我在项目中遇到过一个问题,就是因为这三个列表不同步导致的内存泄漏。排查了好久才发现是某个第三方库直接操作了 mViews 列表。

四、ViewRootImpl 的创建

ViewRootImpl 是 View 和 WMS 之间的桥梁。它的构造函数里做了很多初始化工作:

// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mDisplay = display;
    mHandler = new ViewRootHandler();
    
    // 获取 WindowSession
    mWindowSession = WindowManagerGlobal.getWindowSession();
    
    // 创建 W 对象(Binder 服务端)
    mWindow = new W(this);
    
    // 初始化 Choreographer
    mChoreographer = Choreographer.getInstance();
}

这里有个关键点:mWindowSessionmWindow。前者是应用端与 WMS 通信的会话,后者是 WMS 回调应用端的接口。

五、WindowSession 的建立

WindowSession 是怎么来的?看 WindowManagerGlobal:

// WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                // 获取 WMS 服务
                IWindowManager windowManager = getWindowManagerService();
                // 打开会话
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            // 回调实现
                        });
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

说白了,这就是一次跨进程调用。应用端通过 Binder 调用 WMS 的 openSession(),WMS 返回一个 IWindowSession 对象。这个会话会一直保持,直到应用进程退出。

核心理解:WindowSession 是应用端和 WMS 之间的专用通道。所有的窗口操作(添加、删除、更新)都通过这个通道进行。

六、WMS 端的处理

当 ViewRootImpl 调用 setView() 时,最终会通过 WindowSession 调用到 WMS:

// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        // ... 各种初始化
        
        // 跨进程调用 WMS
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
    }
}

WMS 端的 addWindow() 方法会做大量工作:

  1. 检查权限和 token 合法性
  2. 创建 WindowState 对象
  3. 分配 Surface
  4. 添加到窗口列表
  5. 调整窗口顺序
  6. 触发 relayout

我画了一张流程图,帮你理清整个调用链:

addView() 完整调用链 应用代码 addView() WindowManagerImpl WindowManagerGlobal ViewRootImpl 创建 WindowSession 建立 Binder 跨进程边界 WMS.addWindow() 创建 WindowState 分配 Surface 添加到窗口列表 调整 Z-order 触发 relayout addToDisplay()

七、关键数据结构:WindowState

在 WMS 端,每个窗口对应一个 WindowState 对象。它包含了窗口的所有信息:

字段 说明 我的经验
mAttrs 窗口布局参数 type 字段决定了窗口层级
mClient 应用端的 IWindow 用于回调应用
mSession 所属的 WindowSession 一个 Session 对应一个应用进程
mSurface 绘制表面 SurfaceFlinger 管理
mWinAnimator 窗口动画控制器 动画卡顿常查这里

避坑指南:我曾经遇到一个诡异的问题——窗口添加后不显示。排查了半天,发现是 WindowState 的 mSurface 没有成功创建。原因是 SurfaceFlinger 那边的 BufferQueue 满了。所以,当你遇到窗口不显示的问题,先检查 Surface 的创建状态。

八、整个流程的时间线

我把整个流程按时间顺序梳理一下:

  1. 应用端:调用 wm.addView()
  2. WindowManagerImpl:委托给 WindowManagerGlobal
  3. WindowManagerGlobal:创建 ViewRootImpl,获取 WindowSession
  4. ViewRootImpl:调用 setView(),通过 Binder 调用 WMS
  5. WMS:创建 WindowState,分配 Surface,加入窗口列表
  6. WMS:返回结果,触发第一次 relayout
  7. ViewRootImpl:开始绘制流程(measure -> layout -> draw)
  8. SurfaceFlinger:合成并显示到屏幕

整个过程涉及 3 个进程(应用进程、SystemServer、SurfaceFlinger),多次 Binder 调用。你想想看,这么复杂的流程,能在一瞬间完成,Android 的架构设计确实厉害。

注意事项:addView() 必须在主线程调用。为什么?因为 ViewRootImpl 创建时会绑定主线程的 Looper。如果你在子线程调用,会抛出异常。这是 Android 的硬性规定,别想着绕过去。

九、总结

好了,今天的内容就到这里。我们从一行 addView() 代码出发,一路追踪到 WMS 内部。核心要点就三个:

  • ViewRootImpl 是 View 和 WMS 的桥梁
  • WindowSession 是跨进程通信的会话通道
  • WindowState 是 WMS 端管理窗口的核心数据结构

理解了这个流程,你就能明白为什么有些窗口操作会卡顿,为什么有些窗口显示不出来。下次遇到相关问题,你就知道该从哪里入手排查了。


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