一、从 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() 做了几件关键的事:
- 检查参数合法性
- 创建 ViewRootImpl
- 建立 WindowSession
- 调用 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);
}
嗯,这里要注意:mViews、mRoots、mParams 三个列表是同步维护的。我在项目中遇到过一个问题,就是因为这三个列表不同步导致的内存泄漏。排查了好久才发现是某个第三方库直接操作了 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();
}
这里有个关键点:mWindowSession 和 mWindow。前者是应用端与 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() 方法会做大量工作:
- 检查权限和 token 合法性
- 创建 WindowState 对象
- 分配 Surface
- 添加到窗口列表
- 调整窗口顺序
- 触发 relayout
我画了一张流程图,帮你理清整个调用链:
七、关键数据结构:WindowState
在 WMS 端,每个窗口对应一个 WindowState 对象。它包含了窗口的所有信息:
| 字段 | 说明 | 我的经验 |
|---|---|---|
| mAttrs | 窗口布局参数 | type 字段决定了窗口层级 |
| mClient | 应用端的 IWindow | 用于回调应用 |
| mSession | 所属的 WindowSession | 一个 Session 对应一个应用进程 |
| mSurface | 绘制表面 | SurfaceFlinger 管理 |
| mWinAnimator | 窗口动画控制器 | 动画卡顿常查这里 |
避坑指南:我曾经遇到一个诡异的问题——窗口添加后不显示。排查了半天,发现是 WindowState 的 mSurface 没有成功创建。原因是 SurfaceFlinger 那边的 BufferQueue 满了。所以,当你遇到窗口不显示的问题,先检查 Surface 的创建状态。
八、整个流程的时间线
我把整个流程按时间顺序梳理一下:
- 应用端:调用 wm.addView()
- WindowManagerImpl:委托给 WindowManagerGlobal
- WindowManagerGlobal:创建 ViewRootImpl,获取 WindowSession
- ViewRootImpl:调用 setView(),通过 Binder 调用 WMS
- WMS:创建 WindowState,分配 Surface,加入窗口列表
- WMS:返回结果,触发第一次 relayout
- ViewRootImpl:开始绘制流程(measure -> layout -> draw)
- SurfaceFlinger:合成并显示到屏幕
整个过程涉及 3 个进程(应用进程、SystemServer、SurfaceFlinger),多次 Binder 调用。你想想看,这么复杂的流程,能在一瞬间完成,Android 的架构设计确实厉害。
注意事项:addView() 必须在主线程调用。为什么?因为 ViewRootImpl 创建时会绑定主线程的 Looper。如果你在子线程调用,会抛出异常。这是 Android 的硬性规定,别想着绕过去。
九、总结
好了,今天的内容就到这里。我们从一行 addView() 代码出发,一路追踪到 WMS 内部。核心要点就三个:
- ViewRootImpl 是 View 和 WMS 的桥梁
- WindowSession 是跨进程通信的会话通道
- WindowState 是 WMS 端管理窗口的核心数据结构
理解了这个流程,你就能明白为什么有些窗口操作会卡顿,为什么有些窗口显示不出来。下次遇到相关问题,你就知道该从哪里入手排查了。
公众号:蓝海资料掘金营,微信deep3321