一、从WindowManager.addView说起

大家应该都用过WindowManager,但你真的理解它的addView流程吗?

我记得刚做Android开发那会儿,以为addView就是把一个View塞到屏幕上。后来踩了坑才发现,这背后是一整套跨进程通信机制。说白了,你的App和系统服务之间,隔着一层Binder。

1.1 addView的调用链

我们先看一个最简单的例子:

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.addView(myView, layoutParams);

这行代码背后发生了什么?我画了一张图,帮你理清整个流程:

WindowManager.addView 核心流程 App进程 WindowManager.addView() Binder WMS (WindowManagerService) addWindow() Binder SurfaceFlinger 创建Surface Session:App与WMS之间的通信通道 每个App一个Session WindowToken:标识窗口类型的令牌 最终:View → Surface → 屏幕像素 跨进程通信贯穿始终 注:箭头表示Binder跨进程调用方向

看到这张图,你应该能感受到:addView不是简单的本地调用。它从App进程出发,经过Binder到达WMS,再通知SurfaceFlinger创建Surface。每一步都是跨进程的。

二、WindowToken与Session:两个关键角色

2.1 WindowToken是什么?

WindowToken,说白了就是一个令牌。系统用它来标识窗口的类型和归属。

我举个例子:你在Activity里addView,系统会检查这个Activity对应的WindowToken是否合法。如果token不对,直接拒绝添加。

核心要点:WindowToken是WMS管理窗口的凭证。没有token,窗口就创建不了。

WindowToken有三种常见类型:

类型 用途 创建时机
Activity类型的Token 标识Activity窗口 Activity启动时
子窗口类型的Token 标识PopupWindow等 父窗口创建时
系统窗口类型的Token 标识Toast、系统弹窗等 系统服务创建时

2.2 Session的作用

Session是App与WMS之间的通信通道。每个App进程在WMS中都有一个对应的Session对象。

我个人习惯把Session理解成「专属客服」。你的App有什么窗口操作需求,都通过这个Session告诉WMS。WMS处理完,结果也通过Session返回。

// 简化后的Session工作流程
// App端
IWindowSession session = WindowManagerGlobal.getWindowSession();
session.addToDisplay(window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets);

// WMS端
class Session extends IWindowSession.Stub {
    @Override
    public int addToDisplay(IWindow window, ...) {
        // 验证WindowToken
        // 创建WindowState
        // 分配Surface
        return res;
    }
}

避坑指南:我曾经遇到过一个问题:在Service中直接addView,结果崩溃了。原因就是Service没有有效的WindowToken。后来我改用系统窗口类型(TYPE_SYSTEM_ALERT),才解决。

三、Binder在绘制中的角色

你想想看,App进程和系统服务不在同一个进程里。View要绘制到屏幕上,数据怎么传过去?

答案就是Binder。Android的跨进程通信全靠它。

3.1 Binder传递了什么?

在绘制流程中,Binder主要传递这几类数据:

  • 窗口参数:LayoutParams、窗口位置、大小等
  • Surface引用:通过Binder传递Surface的句柄
  • 绘制指令:DisplayList等渲染数据
  • 输入事件:触摸事件的分发

3.2 一次完整的Binder调用

我们以addView为例,看看Binder调用链:

// 1. App端调用
WindowManagerGlobal.addView()
    → 获取IWindowSession (Binder代理)
    → session.addToDisplay()  // Binder调用

// 2. WMS端接收 (Binder Stub)
Session.addToDisplay()
    → 验证WindowToken
    → 创建WindowState
    → 分配Surface

// 3. 结果返回
    → 通过Binder传回SurfaceControl
    → App端拿到Surface

注意:Binder调用是同步的。如果WMS处理时间过长,App主线程会卡住。所以窗口操作要轻量,别在addView里做耗时计算。

四、从源码角度看addView

我们直接看关键源码,这样更直观:

// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    // 1. 检查参数
    if (view == null) throw new IllegalArgumentException("view must not be null");
    
    // 2. 创建ViewRootImpl
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    
    // 3. 设置参数
    view.setLayoutParams(wparams);
    
    // 4. 添加到列表
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
    // 5. 核心:通过Binder添加窗口
    root.setView(view, wparams, panelParentView);
}

// 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,
                mTempInsets);
    }
}

这段代码我看了不下十遍。每次看都有新体会。核心就一句话:addView的本质,是通过Binder让WMS帮你创建一个窗口

五、绘制流程中的跨进程协作

绘制不是App自己画完就完事了。它需要多方协作:

  1. App进程:测量、布局、绘制View树,生成DisplayList
  2. WMS:管理窗口层级、分配Surface、处理输入事件
  3. SurfaceFlinger:合成所有Surface,输出到屏幕

这三者之间,全靠Binder通信。我画个简化的时序图:

App WMS SurfaceFlinger addToDisplay() createSurface() 返回Surface句柄 scheduleTraversals() 绘制完成,提交Buffer

看到没?每一次交互都是Binder在背后默默工作。没有Binder,这些进程就是孤岛。

六、总结与避坑

好了,我们来捋一下今天的内容:

  • addView流程:App → Binder → WMS → SurfaceFlinger
  • WindowToken:窗口的身份证,没有它寸步难行
  • Session:App与WMS的专属通信通道
  • Binder:贯穿整个绘制流程的跨进程桥梁

最后说一句:理解这些底层机制,不是为了背源码。而是当你遇到窗口显示异常、崩溃问题时,能快速定位是哪个环节出了问题。我这些年排查的诡异bug,十有八九都跟WindowToken或Binder调用有关。

嗯,这一章就到这里。内容不少,但都是干货。你消化一下,有问题随时交流。

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