Skip to content

07 WindowManagerService:窗口的入场券与 Z 轴秩序 ​

本页导读:应用画不画得出像素,第一道闸门不是 SurfaceFlinger,而是 WindowManagerService(WMS):窗口类型允不允许你出现、叠在谁上面、拿哪块 Surface、谁能吃掉焦点与 insets,全是它说了算。本页拆:窗口类型与分层、relayout-finish 同步协议、与 SurfaceFlinger 的分工、输入窗注册、insets 体系、动画与 Transition 框架。基线 Android 16(API 36)。


一、窗口即对象:WindowState 与三剑客 ​

  • Session:一个应用进程在 WMS 的代理户头,持有该进程全部窗口的 binder 通道(IWindowSession);
  • WindowState:一个窗口的服务端档案:属性(WindowManager.LayoutParams)、帧、可见性、动画状态、对应的输入窗;
  • WindowToken:窗口的「身份证」。应用窗口的 token 就是 ActivityRecord(第 06 页容器树里一一对应),子窗口挂 parent 窗,系统窗用独立 token。

服务端还握着一张反向通道 IWindow:resize、insets 变化、焦点通知都从 WMS 打回 ViewRootImpl(frameworks/base/core/java/android/view/ViewRootImpl.java 的 resized()/insetsChanged() 家族)。窗口世界是双向呼叫:应用用 Session 上行,WMS 用 IWindow 下行,两条 Binder 方向相反、语义互锁。

java
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
// 关键入口(示意清单,签名以 AOSP 为准):
//   openSession()      应用 attach 窗口世界的入口
//   relayoutWindow()   尺寸/可见性协商,返回 Surface 与布局结果
//   addToDisplayNoWindowLocked / assignLayer  Z 轴排位
//   finishDrawingWindowLocked  首帧/变更完成回执

二、类型与分层:Z 轴的宪法 ​

WindowManager.LayoutParams.type 决定窗口落在哪个宪法管辖区(常量见 frameworks/base/core/java/android/view/WindowManager.java):

区间名称代表特性
1~99应用窗Activity 窗口归属 Task,参与返回栈
1000~1999子窗面板/子对话框跟随父窗移动
2000~2999系统窗状态栏(2000)、输入法(2011)、Toast(2005)、音量面板(2010)需权限,层级高于应用
3000~3999应用启动窗starting window生命周期极短

Z 轴次序由 WindowManagerPolicy(接口在 frameworks/base/core/java/android/view/WindowManagerPolicy.java? 实际为 frameworks/base/services/core/java/com/android/server/policy,以 AOSP 为准)的 getWindowLayer 家族计算:先按类型大区间,再按同类型内 mLayer 序号。Toast/悬浮窗类需要 SYSTEM_ALERT_WINDOW 类权限,Android 6+ 用户可授可撤。

权限侧速记:

  • 应用窗无需特权,但受「后台不能加窗」限制(Android 10+ 对后台起系统窗同样收紧,日志 Background window add 类字样,以版本为准);
  • 悬浮窗权限(SYSTEM_ALERT_WINDOW)Android 6 起进运行时授权;Android 12 起对「精确 ALPHA 动画」「启动画面劫持」类滥用有额外限制(SWE/延迟欺骗防御,以 AOSP 为准);
  • 音量面板(2010)、截屏预览(2030)等属于「系统自留地」,应用不可申用。

同类型内的相对次序由 mLayer/mSubLayer 计算;dumpsys 输出里的 mLayer 字段就是最终 Z 值的中间量,两个窗口「谁盖谁」之争,看它最诚实。

三、relayout-finish:一次「量体裁衣」的握手 ​

窗口从创建到上屏的核心同步协议:

text
ViewRootImpl.scheduleTraversals → doTraversal
  → relayout:IWindowSession.relayout(WindowState 属性, WindowLayout 出参)   [Binder→WMS]
    → WMS:布局计算(mWindowPlacer/WindowLayout)
      → 必要时新建/调整 SurfaceControl(BLAST 时代:给应用一个 BLASTBufferQueue)
      → 返回:帧、可见性位、新的 SurfaceControl、同步事务句柄
  → 应用侧 measure/layout/draw(RenderThread 录制显示列表)
  → queueBuffer → BLAST 提交(第 10 页)
  → finishDrawing:IWindowSession.finish(类型)                              [Binder→WMS]
    → WMS 确认尺寸一致(防「画完变卦」),把窗从 "waiting for draw" 摘下
    → 通知 ATMS 该 Activity 可标记 resuming/idle(第 06 页)

要点:

  1. relayout 决定你能画多大,finish 确认你真画了——旋转/resize 时的「黑边、拉伸帧」多半是这两步时序被应用作死(onMeasure 耗时/不同步);

  2. Android 11 起 SurfaceControl 直接传句柄,Surface 由 BLASTBufferQueue 包装(旧的 Surface+IGraphicBufferProducer 通道退役,第 10 页); relayout 返回值是一组 RELAYOUT_RES_* 位(是否被 resize、是否有新 Surface、insets 是否变化等,常量以 AOSP WindowManagerGlobal.java 为准),ViewRootImpl 据此决定要不要重走完整遍历——「明明改了尺寸没重画」类玄学,先看这几个位。

  3. WMS 侧用「同步组」(SurfaceSyncGroup,frameworks/base/services/core/java/com/android/server/wm/SurfaceSyncGroup.java,AOSP 目录以实际为准)把多窗变更攒成一次原子事务提交给 SurfaceFlinger——这是「动画中窗口切换不撕裂」的机制根基。

四、与 SurfaceFlinger 的分工:指挥与演奏 ​

  • WMS 管逻辑:谁能出现、多大、在谁上面、动画状态机;
  • SurfaceFlinger 管物理:Buffer 队列、VSYNC、合成(第 10 页);
  • 交接口是 SurfaceControl 事务:WMS 把「创建图层/设 z/设 alpha/设 crop/设矩阵」写进 Transaction,apply() 走 binder 给 SF;
  • WMS 不碰像素:截图(captureDisplay/captureLayers)请求也是 WMS 下指令、SF 执行,screencap 走的正是这条路。
  • 显示配置(刷新率候选、亮度)归 DisplayManagerService 与显示 HAL(第 10/11 页),WMS 只消费「可用显示区域」这一结论。

五、输入窗注册:把「谁有焦点」交给输入系统 ​

WMS 每次布局收敛后,把可见窗口清单打包成 InputWindowHandle 列表,经 InputMonitor/InputManagerService 下发 native InputDispatcher(第 09 页的 setInputWindows);焦点变化则调 setInputFocusWindow。链上三个细节:

  1. 一个窗口能否吃输入,由 FLAG_NOT_TOUCHABLE/FLAG_NOT_FOCUSABLE 等 flag 与可见性共同决定;
  2. pauseDispatching/resumeDispatching 用于「应用正忙,先别喂事件」的窗口级暂停;
  3. 焦点窗的 token 必须是已 attach 的有效窗,ANR 报告里的 Waiting because it is unresponsive 与这里状态直接相关;
  4. 手势导航时代还有「窥视窗」(spy window,Android 12+):SystemUI 的边缘手势层以窥视方式接收全部事件流而不截胡,第 09 页细讲。

六、insets 体系:屏幕边界的和平条约 ​

系统栏、刘海、手势条占据的「边界领土」用 insets 协商。Android 11(API 30)完成体系重写:

  • 服务端:InsetsStateController(services/core/java/com/android/server/wm/InsetsStateController.java)维护每个显示区的 insets source(状态栏、导航栏、IME、刘海);
  • 客户端:android.view.InsetsController + WindowInsetsController 负责响应:谁消费、谁避让、谁穿透;
  • 类型:Type.systemBars/systemGestures/mandatorySystemGestures/ime/displayCutout 等组合;应用可用 setDecorFitsSystemWindows(false) 进入边到边,Android 15+ 默认化后这是必修课(第 00 页版本注记);
  • IME 是最复杂的 source:弹出/收起动画与内容 resize 用 ime 类型 + 同步事务保持同步(三、同步组再次出场)。

实战速查:

需求正解常见错解
内容避让系统栏setDecorFitsSystemWindows(true)(默认语义)手算状态栏高度
内容画到栏后面关 decor fit + 自行处理 insets直接隐藏系统栏
跟随 IME 动画insets 动画 + WindowInsetsAnimation 回调监听键盘可见性轮询
全屏沉浸hide(systemBars) + 行为配置废弃的 SYSTEM_UI_FLAG 大杂烩

dumpsys window 与 dumpsys window displays 里 InsetsState 段就是谈判底牌。

七、动画与 Transition:从状态机到框架 ​

  • 旧时代:WindowStateAnimator + AppTransition/TransitionAnimation:系统代放「窗口级」动画(缩放、淡入),应用拿不到控制权,自定义转场靠 overridePendingTransition 缝缝补补;
  • 新时代(Shell Transitions):TransitionController(services/core/java/com/android/server/wm/TransitionController.java)把一次可见性变化打包成 Transition 对象:收集参与窗(leash 化的容器)→ 发给 SystemUI 侧 TransitionHandler 链执行 → finishTransition 原子提交(第 10 页 SyncFramework 同一思想);Android 12 起默认启用,旧路径 Android 14 前后移除(以 AOSP 为准);
  • 应用侧接口:Activity 转场动画 overridePendingTransition(旧)、ActivityOptions、ActivityTransition(API 34 的 enter/exit 自定义);预测式返回手势(API 34/35,OnBackInvokedDispatcher)同样吃 transition 的 leash;
  • 动画落位靠 SurfaceAnimator:把容器 reparent 到 leash 层做变换,结束后回嫁——理解「动画中 dumpsys window 看到临时层级」就不慌了。

八、实例:软键盘弹出的一圈系统 ​

把一次键盘弹出走一遍,能同时检验本页所有零件:

  1. 输入框获焦 → 应用 WindowInsetsController 发出 show(ime());
  2. 请求到 WindowManagerService/IME 子系统:Imms 找到当前 IME 服务(wm/InputMethodManagerService.java),拉起 IME 进程(第 06 页孵化链,IME 也是普通 App);
  3. IME 窗口 relayout 入场(第三节握手),类型 2011 高于应用窗,adjustResize/adjustPan 语义由 WMS 换算成应用窗的可用帧;
  4. InsetsStateController 把 IME source 的高度变化广播给全部相关窗口;
  5. 应用窗收到新 insets,重布局;键盘自身内容走 BLAST 提交(第 10 页);
  6. 整个「升起动画」被 SurfaceSyncGroup 打包成原子事务——键盘、被顶起的内容、导航栏色不会各走各的节奏;
  7. 收起同理;若键盘进程猝死,DeathRecipient(第 04 页)清理输入焦点,焦点窗收回。

顺带记住常用窗口 flag 的省电联动:FLAG_KEEP_SCREEN_ON 由 WMS 转告 PowerManagerService 维持屏幕(第 11 页 wakelock 家族之外的「借光」机制)。

九、实战:dumpsys window 的正确打开方式 ​

dumpsys window 的默认输出非常厚,实战永远带子命令与 grep;下面四条命令覆盖 80% 的窗口问题。

bash
adb shell dumpsys window displays        # 显示/区域/insets 快照
adb shell dumpsys window windows | grep -e "Window #" -e "mViewVisibility"  # 表格内书写需转义
adb shell dumpsys window visible         # 当前真可见的窗(SF 侧用 dumpsys SurfaceFlinger 对照)
adb shell dumpsys window anim            # 动画/transition 状态(子命令名随版本有出入)

典型判例:画面没出来——先 visible 里有没有窗;有窗不可见——看 mViewVisibility/alpha/isReadyForDisplay;有窗可见但黑——SF 图层段对照第 10 页。

  • 命令输出随版本漂移是常态:先 dumpsys window -a 里找段落名,再定位字段,不要背行号。

再补两条日常:

  • mAppVisible=false 而界面明明在——多为 starting window 挡刀或同步组未 finish;
  • dumpsys window transitions(子命令以版本为准)看 transition 是否卡住不收,动画类「画面定格」问题多出于此。

十、常见窗口问题清单 ​

症状第一嫌疑取证
加窗被拒类型无权限/后台加窗限制logcat add window 权限拒绝栈
悬浮窗盖不住输入法2005/2038 类低于 2011,层级宪法如此dumpsys window 对照类型
旋转后内容拉伸relayout-finish 间应用未重画relayoutResult 位 + 应用帧日志
动画撕裂/黑块同步组漏提交、第三方动画未入组dumpsys window anim + perfetto
刘海内容被切displayCutout 模式未处理InsetsState 里 cutout source
焦点丢失收不到键焦点窗不可见/token 失效/暂停分发dumpsys input 焦点段(第 09 页)

十一、版本注记 ​

  • Android 8~9(API 26~28):画中画、多窗口初体验;层级体系沿用大区间宪法。
  • Android 10(API 29):Shell Transition 机制落地(默认未全量)。
  • Android 11(API 30):WindowInsets API 重写;SurfaceControl 通道替换旧 Surface 通道。
  • Android 12(API 31):BLAST 全面默认;Shell Transitions 默认启用;TaskFragment 容器(第 06 页)。
  • Android 13~14(API 33~34):预测式返回;大屏/桌面化(窗口化多任务)大量 WMS 适配;RemoteTransition 类机制让自定义转场进入受控开放期(以 AOSP 当前 API 级为准)。
  • Android 15~16(API 35~36):边到边默认、letterbox 策略收紧;Transition 体系继续吸收旧动画残余(以 AOSP 为准)。
  • 兼容性提醒:WMS 的内部类从不在 SDK 稳定承诺内,反射改窗口行为的「黑魔法」在 Android 15+ 被非 SDK 名单与 CompatChanges 持续绞杀,别把赌注押在这。
  • Android 16 上 WindowManagerService 仍是单类巨无霸,但 InsetsStateController、TransitionController、TaskFragment 等子域持续模块化,读码按子域切入。

本页小结 ​

本页小结:学 WMS 的最佳路径是跟着一个窗口的生命周期读源码:add → relayout → finish → 动画 → remove;每一站对应本页一节。WMS 是「逻辑世界」的窗口政府:类型宪法决定能不能出现,relayout-finish 握手确定画多大画完没有,输入窗注册交出焦点外交权,insets 条约划定边界领土,Transition 把动画从私刑升级成程序正义。SurfaceFlinger 只认事务不认人——所有故事最后都要收敛在一句 Transaction.apply() 里。

参考 ​

  • 窗口与 insets 官方说明:developer.android.com/guide/ui (WindowManager/WindowInsets 章) 与 developer.android.com/develop/ui/navigation/insets(Insets 指南)
  • WindowManagerService 源码:cs.android.com/android/platform/frameworks/base/+/main/services/core/java/com/android/server/wm/WindowManagerService.java
  • TransitionController 源码:cs.android.com/android/platform/frameworks/base/+/main/services/core/java/com/android/server/wm/TransitionController.java
  • 图形栈衔接:source.android.com/docs/core/graphics(SurfaceFlinger 章)
  • 沉浸式/全屏官方指南:developer.android.com/training/articles/immersive-mode

基于 Android 16 / API 36 · 内容为原创整理,转载请注明出处