4、窗口层级与Z-order:窗口类型、层级定义、Z-order管理策略

好,咱们今天聊聊窗口层级和Z-order。说实话,这是车载窗口管理里最绕、也最容易出bug的地方。你想想看,一个屏幕上同时跑着导航、音乐、电话、还有系统弹窗,谁盖在谁上面?谁该被挡住?谁必须永远可见?这就是Z-order要解决的问题。

4.1 窗口类型:Application、System、Keyguard

Android Automotive里,窗口不是一锅粥的。系统把窗口分成了几大类,每一类都有固定的“地位”。我习惯把它们想象成三六九等——嗯,虽然不太政治正确,但技术上就是这么回事。

窗口类型 典型例子 层级范围 特点
Application 地图、音乐、设置 1 - 9999 普通应用窗口,受ActivityManager管理
System 状态栏、导航栏、Toast 2000 - 2999 系统级UI,常驻显示
Keyguard 锁屏界面 10000 - 10999 最高优先级,覆盖所有
Input Method 输入法面板 3000 - 3999 软键盘专用
Wallpaper 壁纸 0 - 999 最底层,永远被覆盖

这里有个关键点:Application窗口并不是最底层的。壁纸窗口才是。我记得有一次排查一个bug,客户说“为什么我的应用背景是黑的?”查了半天,原来是壁纸窗口被意外关闭了。嗯,这种坑踩过一次就记住了。

4.2 层级定义:谁在上,谁在下?

Z-order说白了就是一个数字。数字越大,窗口越靠上。但系统不是简单比大小——它有一套完整的层级策略。

在WindowManagerService里,每个窗口都有一个mBaseLayermSubLayer。前者决定大类,后者决定同类窗口里的顺序。

// 来自 WindowManagerService 的核心逻辑(简化版)
private int assignLayer(WindowState win) {
    int type = win.mAttrs.type;
    int baseLayer = typeToLayer(type);
    // 同类窗口按创建时间排序
    int subLayer = win.mSubLayer;
    return baseLayer * 10000 + subLayer;
}

你看,typeToLayer把窗口类型映射成一个基础层级值。比如Application窗口是1,System窗口是2,Keyguard是10。然后乘以10000,再加上子层级。这样就能保证:不同类别的窗口永远不会混在一起

核心原则:层级由窗口类型决定,同类窗口内按创建顺序排列。后创建的窗口默认在上层。

我个人习惯在调试时用dumpsys window命令查看当前所有窗口的Z-order。你会发现,即使两个应用看起来一样,它们的层级也可能差了好几千。这就是类型带来的差距。

4.3 Z-order管理策略:系统是怎么排队的?

Z-order管理不是静态的。窗口会动态变化——比如弹出一个Dialog,或者用户切换应用。系统需要一套策略来保证“该上的上,该下的下”。

4.3.1 焦点窗口优先

有焦点的窗口,Z-order会自动提升。你想想看,用户正在操作地图,地图窗口突然被音乐窗口盖住了,这合理吗?不合理。所以系统保证:焦点窗口永远在同类窗口的最上层

4.3.2 系统窗口的“特权”

System类型的窗口,比如状态栏,它的Z-order天然高于所有Application窗口。这意味着:应用永远无法覆盖系统UI。我曾经遇到一个需求,客户想让导航应用全屏显示,连状态栏都隐藏掉。嗯,这需要特殊权限,普通应用做不到。

避坑指南:如果你在车载项目里发现某个弹窗被状态栏挡住了,先检查窗口类型。大概率是用了Application类型,应该改成System类型或者Dialog类型。

4.3.3 Keyguard的“绝对统治”

Keyguard窗口的Z-order是10000起步。这意味着什么?意味着锁屏界面可以覆盖一切——包括系统UI。这是安全设计:用户锁车后,任何应用都不能在锁屏上方显示内容。

我记得有一次,客户反馈说“锁屏后导航还在播报语音”。查了代码发现,导航应用用了TYPE_SYSTEM_OVERLAY窗口,层级是2000多,确实比Keyguard低。但语音播报是另一个机制,跟窗口层级无关。嗯,这个坑也值得注意。

4.4 实战:如何调试Z-order问题?

说一千道一万,不如动手调一次。我常用的调试方法就两个:

  1. dumpsys window:查看所有窗口的层级信息
  2. 开发者选项里的“显示布局边界”:直观看到窗口覆盖关系
# 查看窗口层级(按Z-order排序)
adb shell dumpsys window | grep -E "Window #|mBaseLayer|mSubLayer"

# 输出示例:
# Window #3: Window{...} type=2000 mBaseLayer=2 mSubLayer=0
# Window #2: Window{...} type=1 mBaseLayer=1 mSubLayer=5
# Window #1: Window{...} type=10000 mBaseLayer=10 mSubLayer=0

你看,type=2000的是System窗口,type=1的是Application窗口,type=10000的是Keyguard。层级一目了然。

注意:不要随意修改窗口类型来调整Z-order。比如把Application窗口改成System类型,虽然能盖住状态栏,但会导致系统行为异常——比如无法正确处理触摸事件、无法被ActivityManager管理。我见过有人这么干,结果应用退不出来了。

4.5 总结一下

窗口层级和Z-order,说白了就是一套“谁在上谁在下”的规则。记住三点:

  • 类型决定地位:Keyguard > System > Application > Wallpaper
  • 焦点决定顺序:同类窗口里,有焦点的排最前
  • 不要乱改类型:改类型等于改规则,后果自负

嗯,下一章咱们聊聊窗口动画和过渡效果。那个更刺激——窗口怎么从A点飞到B点,怎么缩放、怎么淡入淡出。到时候见。