10、WindowSession 与 Binder 通信:IWindowSession 接口定义、Session 在 WMS 端的实现、跨进程调用流程

好,我们进入本章节的核心内容。前面几章我们把 WMS 的启动、窗口层级、布局流程都捋了一遍。现在要聊一个非常关键但又容易被忽视的环节——WindowSession

说白了,WindowSession 就是应用进程和 WMS 之间的「通信管道」。你想想看,一个 App 要添加窗口、更新布局、处理输入事件,总不能直接操作 WMS 的内存吧?Android 的进程隔离机制不允许。那怎么办?Binder 通信。而 Session,就是 WMS 为每个客户端(通常是每个应用进程)创建的 Binder 会话对象。

IWindowSession 接口定义

我们先看接口层。IWindowSession 是一个 AIDL 接口,定义在 IWindowSession.aidl 中。它暴露了客户端可以调用的核心方法。

我个人习惯把接口方法分成三类:

  • 窗口操作类:addToDisplay、remove、relayout
  • 输入事件类:addToDisplayWithoutInputChannel、pokeDrawLock
  • 动画与状态类:setInsets、finishDrawing

这里我挑几个重点方法说一下:

// IWindowSession.aidl 核心方法片段
interface IWindowSession {
    int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
            int displayId, in Rect outContentInsets,
            in Rect outStableInsets, in DisplayCutout.ParcelableWrapper outDisplayCutout,
            out InputChannel outInputChannel);

    int relayout(IWindow window, in WindowManager.LayoutParams attrs,
            int requestedWidth, int requestedHeight,
            int viewVisibility, int flags,
            out WindowManager.LayoutParams outAttrs,
            out Rect outFrame, out Rect outContentInsets,
            out Rect outStableInsets, out DisplayCutout.ParcelableWrapper outDisplayCutout,
            out Configuration outConfig, out SurfaceControl outSurfaceControl);

    void remove(IWindow window);

    void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction);
}

注意看 addToDisplayrelayout 这两个方法。它们都传入了 IWindow 对象——这是客户端的 Binder 代理,WMS 通过它来回调客户端。嗯,这里有个设计细节:双向 Binder 通信。客户端持有 WMS 的 Session 代理,WMS 持有客户端的 Window 代理。这样两边都能主动发起调用。

核心要点:IWindowSession 是 WMS 暴露给客户端的「门面」。客户端不能直接调用 WMS 的方法,必须通过 Session。这层抽象隔离了实现细节,也方便做权限校验和会话管理。

Session 在 WMS 端的实现

好,接口看完了。我们看看 WMS 端是怎么实现这个 Session 的。

Session 类定义在 com.android.server.wm.Session,它继承自 IWindowSession.Stub。每个应用进程在第一次与 WMS 交互时,会通过 WMS.openSession() 创建一个 Session 实例。

我记得在 Android 10 之前,Session 的创建逻辑比较简单。后来为了支持多窗口和自由形态,Session 内部增加了不少状态管理。来看核心实现:

// Session.java 核心实现片段
class Session extends IWindowSession.Stub {
    final WindowManagerService mService;
    final int mUid;
    final int mPid;
    final String mPackageName;
    
    // 该 Session 关联的所有 WindowToken
    final ArrayList<WindowToken> mWindowTokens = new ArrayList<>();
    
    // 该 Session 关联的所有 WindowState
    final ArrayList<WindowState> mWindows = new ArrayList<>();
    
    @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int displayId, Rect outContentInsets, Rect outStableInsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout,
            InputChannel outInputChannel) {
        // 权限检查
        if (mService.checkCallingPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("...");
        }
        
        // 委托给 WMS 处理
        return mService.addWindow(this, window, attrs, displayId, 
                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel);
    }
    
    @Override
    public int relayout(IWindow window, WindowManager.LayoutParams attrs,
            int requestedWidth, int requestedHeight, ...) {
        // 同样委托给 WMS
        return mService.relayoutWindow(this, window, attrs, ...);
    }
    
    void windowAddedLocked(WindowState win) {
        mWindows.add(win);
    }
    
    void windowRemovedLocked(WindowState win) {
        mWindows.remove(win);
    }
}

看到没?Session 本身并不做真正的窗口管理逻辑。它更像一个「代理」或「门卫」——做权限校验、会话跟踪,然后把实际工作委托给 WMS 的对应方法。

我曾经在排查一个 ANR 问题时,发现某个进程的 Session 里积累了上百个未释放的 WindowState。原因是该进程频繁创建窗口但不及时移除,导致 Session 的 mWindows 列表越来越大,最终拖垮了 WMS 的主线程。嗯,这个坑后面会细讲。

个人经验:Session 的生命周期与客户端进程绑定。如果客户端进程异常死亡,WMS 会通过 Binder 死亡通知回调 binderDied(),清理该 Session 关联的所有窗口。这个机制保证了即使 App 崩溃,窗口也不会残留。

跨进程调用流程

好,重头戏来了。我们完整走一遍跨进程调用流程。以 addToDisplay 为例:

  1. 客户端发起调用:App 调用 WindowManager.addView(),最终走到 ViewRootImpl.setView()
  2. 获取 Session 代理:ViewRootImpl 通过 WindowManagerGlobal.getWindowSession() 获取缓存的 Session 代理对象。
  3. Binder 序列化:参数被序列化到 Parcel 中,通过 Binder 驱动传输到 system_server 进程。
  4. WMS 端反序列化:Session.addToDisplay() 被调用,参数被还原。
  5. 权限校验:Session 检查调用者的 UID、PID、权限。
  6. 委托 WMS:Session 调用 mService.addWindow(),进入 WMS 的主线程(通过 mService.mWindowMap 的锁保护)。
  7. 创建 WindowState:WMS 创建 WindowState,添加到对应的 DisplayContent 和 WindowToken 中。
  8. 返回结果:通过 out 参数返回 contentInsets、InputChannel 等数据。
  9. Binder 反序列化:结果通过 Binder 驱动传回客户端。
  10. 客户端处理:ViewRootImpl 根据返回的 SurfaceControl 和 Insets 信息完成后续布局。

这个流程看起来简单,但每个环节都有坑。我挑两个关键点说一下:

避坑指南:我曾经遇到过一个诡异的问题——App 调用 addView 后,窗口一直不显示。排查了半天,发现是 Binder 调用超时了。原因是 WMS 主线程被一个长时间运行的动画阻塞了,导致 Session 的调用排队等待。嗯,从此我养成了一个习惯:在 WMS 主线程上绝对不做耗时操作,超过 5ms 的工作都要异步处理。

另一个要注意的是 Binder 线程池耗尽。如果短时间内有大量进程同时调用 WMS,Binder 线程池可能会被占满。这时候新的调用会阻塞,甚至触发 ANR。我记得 Android 8.0 之后,Binder 线程池默认大小是 16 个。对于 system_server 这种核心服务,这个数字其实偏紧。

核心流程图

下面我用一张 SVG 图把整个调用链路画出来,方便你理解:

WindowSession 跨进程调用流程 客户端进程 (App) System Server 进程 (WMS) ViewRootImpl WindowManagerGlobal IWindowSession (Binder Proxy) Session WindowManagerService WindowState DisplayContent Binder 驱动 返回 SurfaceControl、Insets 等 1 2 3 4 5 6

关键设计决策

最后,我想聊聊 Session 设计背后的几个决策点:

设计点 为什么这么做 我的看法
每个进程一个 Session 隔离不同进程的窗口,方便做权限和生命周期管理 合理。如果所有进程共用一个 Session,权限校验会变得非常复杂
Session 委托 WMS 执行 保持 WMS 作为唯一的状态管理中心,避免逻辑分散 正确。Session 如果自己处理窗口逻辑,会导致状态不一致
双向 Binder 通信 WMS 需要主动通知客户端(如输入事件、窗口焦点变化) 必要。单向通信无法满足窗口系统的需求
Session 持有 WindowState 列表 方便在进程死亡时快速清理所有关联窗口 实用。我见过没有这个设计的系统,进程死后窗口残留导致 UI 混乱

嗯,Session 的设计看似简单,但每个细节都经过深思熟虑。你想想看,如果没有这层抽象,WMS 直接暴露给所有客户端,那权限校验、会话管理、异常处理都会变得一团糟。

我个人觉得,Session 是 WMS 架构中「高内聚、低耦合」的典范。它把跨进程通信的复杂性封装在内部,让 WMS 的核心逻辑可以专注于窗口管理本身。这种设计思路,在我们自己写系统服务时也值得借鉴。

一句话总结:WindowSession 是应用与 WMS 之间的 Binder 通信桥梁。它负责权限校验、会话跟踪和调用委托,是理解 Android 窗口系统跨进程架构的关键入口。

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