应用如何向 WMS 申请一个窗口
应用如何向 WMS 申请一个窗口
setContentView() 只把布局内容装进 DecorView,并没有向 WMS 注册窗口。真正的加窗请求从 WindowManager 客户端进入 ViewRootImpl.setView(),再由 IWindowSession.addToDisplayAsUser() 跨进程到达 system_server。
View 树建好不代表窗口已经入场
| 常见误解 | 正确理解 |
|---|---|
setContentView() 一调用,窗口就注册到了系统 | 它只建立 Activity 内部的 View 树,加窗发生在后续 WindowManager 调用 |
| View 树就是窗口 | View 树是窗口里的内容,窗口是系统侧的一个记录 |
setContentView() 回答“窗口里面有什么”,但没有回答“这个窗口是否被系统接受”。只有 WindowManager 客户端把 View 树交给 ViewRootImpl,再由 ViewRootImpl 跨进程请求,窗口才算真正进入系统。
客户端主链
逐步解释:
- ①
setContentView():Activity 把布局 ID 交给 PhoneWindow,PhoneWindow 建立 DecorView 下的内容树。 - ② 客户端封装:WindowManagerImpl 把请求转给进程单例 WindowManagerGlobal,后者查找或创建 ViewRootImpl。
- ③ 根连接:ViewRootImpl 持有
IWindow(应用侧回调)和IWindowSession(服务端代理),成为 View 树与系统窗口的连接点。 - ④ 预排布局:加窗前先
requestLayout(),保证后续 relayout 有第一次布局请求。 - ⑤ 跨进程:
addToDisplayAsUser()通过 Binder 到达 system_server 的 Session,再转交WMS.addWindow()。 - ⑥ 返回:添加结果常量、Insets 和输入通道随 Binder 返回,ViewRootImpl 据此继续工作。
四个关键对象
| 对象 | 正式定义 | 在链路中的职责 |
|---|---|---|
| DecorView | PhoneWindow 的根 View,承载状态栏背景、标题栏和内容区 | 房间的骨架 |
| ViewRootImpl | View 树与系统窗口的根连接,负责 relayout、绘制调度和输入分发 | 房间的钥匙卡 |
| IWindow | 应用侧窗口回调接口,WMS 用它给应用发消息 | 房间门上的对讲机 |
| IWindowSession | WMS 为每个应用进程创建的一个 Binder 会话 | 前台电话专线 |
一句话小结:View 树是内容,ViewRootImpl 是连接,IWindowSession 是跨进程通道。
源码证据
阅读目标:确认 setContentView() 只建立内容树,加窗由 ViewRootImpl 发起。
正文指针:Activity.java:3797;WindowManagerGlobal.java:443;ViewRootImpl.java:1476。
// Activity.java:3797
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}// WindowManagerGlobal.java:443
root.setView(view, wparams, panelParentView, userId);// ViewRootImpl.java:1476
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
mTempControls, attachedFrame, compatScale);这段代码证明: setContentView() 只调用 getWindow().setContentView(),不触达 WMS;真正的跨进程加窗发生在 ViewRootImpl 调用 mWindowSession.addToDisplayAsUser() 这一行。
Session 从哪里来
ViewRootImpl 构造时通过 WindowManagerGlobal.getWindowSession() 获取 IWindowSession 代理。这个代理指向 WMS 的 openSession() 创建的服务端 Session 对象。后续所有窗口操作(relayout、finishDrawing、remove)都走同一个会话,方便 WMS 按进程管理窗口。
ViewRootImpl:1130 getWindowSession() → IWindowManager.openSession()
→ system_server 创建 Session → App 持有 Binder 代理源码入口
源码基准:android-14.0.0_r75。
以下文件默认位于 frameworks/base/,表中省略该前缀:
| 文件 | 关键位置 | 作用 |
|---|---|---|
core/java/android/app/Activity.java | 3797 行 setContentView() | 交给 PhoneWindow 建内容树 |
core/java/com/android/internal/policy/PhoneWindow.java | 508 行 setContentView(int) | 建立 DecorView 内容树 |
core/java/android/view/WindowManagerImpl.java | 156 行 addView() | App 侧 WindowManager 入口 |
core/java/android/view/WindowManagerGlobal.java | 345 行 addView();427 行 new ViewRootImpl(...);443 行 root.setView(...) | 客户端加窗编排 |
core/java/android/view/ViewRootImpl.java | 1130 行 getWindowSession();1150 行 mWindow = new W(this);1450 行 requestLayout();1476 行 addToDisplayAsUser() | 根连接与跨进程加窗 |
core/java/android/view/IWindowSession.aidl | 55 行 addToDisplayAsUser(...) | Binder 接口契约 |
services/core/java/com/android/server/wm/Session.java | 250-258 行 addToDisplayAsUser() → mService.addWindow(...) | Binder 服务端入口 |