Android Window Z轴顺序计算与窗口组织 笔记

窗口的Z轴顺序,简单来说就是决定"谁盖住谁"的规则。它是WMS窗口管理的核心之一,直接影响用户界面的视觉层次和交互行为。理解Z轴顺序的计算,就能明白为什么对话框总是浮在Activity之上、为什么状态栏永远在最上层、为什么输入法窗口能覆盖部分应用界面。

为了讲清楚这个机制,我们需要先了解窗口在WMS中的组织方式,然后再深入Z轴的计算逻辑。


🧱 窗口组织模型:从屏幕到单个窗口

WMS并不是扁平地管理所有窗口,而是构建了一个清晰的层次结构,这个结构直接服务于Z轴顺序的计算。

  1. DisplayContent(屏幕) :最顶层的容器,代表一个逻辑显示屏(可能是物理屏幕、虚拟屏幕或投屏)。每个DisplayContent管理着属于该屏幕的所有窗口。在多屏环境下,不同屏幕的窗口是独立分层的。

  2. WindowToken(窗口令牌) :位于DisplayContent之下,代表一组具有相同行为或归属的窗口。最常见的两种:

    • AppWindowToken:对应一个Activity。一个Activity的所有窗口(主窗口、子窗口、启动窗口)都归属于同一个AppWindowToken
    • WindowToken:对应非Activity的窗口组,比如输入法窗口、壁纸窗口、系统级窗口(如状态栏、导航栏)等。
  3. WindowState(窗口状态) :最基础的单元,代表一个具体的窗口 。每个WindowState必定属于某个WindowToken,并最终归属于某个DisplayContent

组织关系可以这样理解

一个DisplayContent(展馆)里有多个WindowToken(展柜),每个WindowToken里摆放着多个WindowState(展品)。

这种组织方式使得WMS可以方便地控制一组窗口的整体行为(比如一起隐藏、一起移动),也为Z轴分层奠定了基础。


📐 Z轴顺序的计算:原则与过程

Z轴顺序的核心是为每个WindowState分配一个整型数值(layer),数值越大,窗口越靠上。这个值的计算遵循一套严格的规则,并不是简单的"后添加的在上"。

1. 基础原则:按窗口类型分层

Android定义了多种窗口类型(通过WindowManager.LayoutParams.type指定),它们在Z轴上天然处于不同的"层级带"。从低到高大致如下:

  • 底层:壁纸窗口(TYPE_WALLPAPER
  • 应用窗口层:普通应用窗口(TYPE_APPLICATION)、应用子窗口(TYPE_APPLICATION_MEDIATYPE_APPLICATION_PANEL等)
  • 系统应用层:如输入法窗口(TYPE_INPUT_METHOD)、输入法对话框(TYPE_INPUT_METHOD_DIALOG
  • 系统层:如状态栏(TYPE_STATUS_BAR)、导航栏(TYPE_NAVIGATION_BAR)、系统警告框(TYPE_SYSTEM_ALERT)、屏幕保护(TYPE_KEYGUARD)等

注意 :这里的"类型"是静态的,决定了窗口所属的主层级 。例如,所有TYPE_APPLICATION窗口都在应用层,所有TYPE_STATUS_BAR窗口都在系统层。

2. 动态调整:同一层内的排序

同一类型的窗口(例如多个Activity)也需要决定谁在上谁在下。这主要由以下因素动态调整:

  • 焦点状态:拥有焦点的窗口通常会提升到其所在层级的顶部。
  • 动画状态:正在播放进入/退出动画的窗口,可能临时获得更高的Z值,以保证动画流畅(例如切换Activity时的过渡效果)。
  • 启动窗口(StartingWindow):在Activity启动过程中显示的预览窗口,会在适当时候被真正的窗口覆盖。
  • 子窗口:依附于主窗口的子窗口(如菜单、对话框)必须在其父窗口之上,但又在其他应用窗口之下(除非特别指定)。
  • WindowManager.LayoutParams的参数 :开发者可以通过flags(如FLAG_NOT_FOCUSABLEFLAG_ALT_FOCUSABLE_IM)间接影响Z轴顺序(例如使窗口不接受焦点,从而影响其被选为焦点的可能性)。

3. 核心计算过程(简化版)

WMS中,负责计算Z轴顺序的核心函数是DisplayContent.assignWindowLayers()(在不同Android版本中函数名可能略有差异,但逻辑一致)。它会遍历该屏幕上的所有WindowToken,再遍历每个Token下的所有WindowState,按照以下步骤为每个窗口计算出最终的layer值:

  1. 确定基础层(Base Layer) : 根据窗口的类型(type),映射到一个预设的整型范围。例如:

    • 壁纸:底层(如21000)
    • 应用窗口:中间层(如22000)
    • 系统窗口:高层(如23000)
    • 关键guard:最高层(如24000) 这个基础值确保了不同类型的窗口天然处于不同的"海拔"。
  2. 应用类型内偏移(Type Layer): 在同一基础层内,不同的子类型再细分子层。例如应用窗口本身可能包括主窗口(+0)、媒体窗口(+1)、子窗口(+2)等。这一步保证了子窗口一定在主窗口之上。

  3. 窗口内排序(Layer within token) : 对于同一个WindowToken内的多个WindowState,按照它们被添加的顺序(即Z序)分配递增值。通常后添加的窗口在上。

  4. 动态调整: 将焦点窗口、正在播放动画的窗口、启动窗口等特殊窗口的Z值进行提升(增加一个较大的偏移量),确保它们暂时处于其所在类型层的顶部。

  5. 最终赋值 : 将计算出的整数赋值给WindowState.mLayer,并通过SurfaceControl.setLayer()告知SurfaceFlinger。SurfaceFlinger会根据这个值合成最终的画面。


🧠 关键影响因素深入解析

1. 窗口类型(Window Type)的主宰作用

窗口类型是Z轴分层的根本。系统通过WindowManagerPolicy(策略接口)定义了每种类型对应的基础层值。例如在PhoneWindowManager中,你可以看到类似这样的定义(伪代码):

arduino 复制代码
// 壁纸层
case TYPE_WALLPAPER: layer = 21000; break;
// 应用窗口层
case TYPE_APPLICATION: layer = 22000; break;
// 输入法窗口层
case TYPE_INPUT_METHOD: layer = 23000; break;
// 状态栏
case TYPE_STATUS_BAR: layer = 24000; break;
// 关键guard(如锁屏)
case TYPE_KEYGUARD: layer = 25000; break;

不同基础层之间差距足够大,使得不同类型窗口不会交错。

2. 输入法窗口(IME)的特殊位置

输入法窗口(TYPE_INPUT_METHOD)通常位于应用窗口之上,但又位于状态栏等系统窗口之下。这是为了保证输入法能覆盖应用界面(用于打字),但又不会挡住状态栏(用于显示时间、电量)。

3. 壁纸窗口(Wallpaper)的特殊处理

壁纸窗口永远处于最底层。而且当一个应用窗口可见时,WMS会确保壁纸窗口位于该应用窗口之下,并可能调整大小以适配。

4. 子窗口(Sub Window)

子窗口(如TYPE_APPLICATION_PANELTYPE_APPLICATION_MEDIA)的Z轴计算依赖于其父窗口。计算时,先确定父窗口的Z值,然后在此基础上加一个较小的偏移量(如+1),保证子窗口浮在父窗口之上,但依然位于其他应用窗口之后。

5. 动画的影响

当窗口启动或退出动画时,WMS可能会临时增加其Z值。例如Activity切换时,新Activity的窗口可能通过动画从屏幕外移入,如果Z值不够高,可能被旧窗口挡住。因此WMS会在动画期间为其分配一个更高的临时Z值,动画结束后恢复。


⏱️ 重新计算Z轴顺序的时机

Z轴顺序不是一成不变的,以下情况会触发assignWindowLayers()重新计算:

  • 添加或删除窗口时
  • 窗口焦点改变时(如用户点击另一个应用)
  • 窗口动画开始或结束时
  • 输入法显示或隐藏时
  • 窗口的可见性发生变化时(如一个Activity被暂停)
  • 屏幕旋转或窗口大小改变时(可能影响窗口的相对位置)

每一次重新计算,WMS都会遍历所有相关窗口,重新分配layer值,并通过SurfaceControl更新到SurfaceFlinger。


📊 总结:窗口组织与Z轴计算的协同

为了帮助你直观理解,我们可以用一张图表示窗口的组织与Z轴关系:

scss 复制代码
DisplayContent (屏幕)
│
├── WindowToken (AppWindowToken for Activity A)
│   ├── WindowState (主窗口)            ->  Z = 22001 (基础层+偏移)
│   ├── WindowState (子菜单窗口)         ->  Z = 22002
│   └── WindowState (对话框窗口)         ->  Z = 22003
│
├── WindowToken (AppWindowToken for Activity B,但被A盖住)
│   └── WindowState (主窗口)            ->  Z = 22000 (基础层+0)
│
├── WindowToken (InputMethod)
│   └── WindowState (输入法窗口)         ->  Z = 23001 (基础层+偏移)
│
└── WindowToken (StatusBar)
    └── WindowState (状态栏)             ->  Z = 24000 (基础层)

在这个结构中,Activity B的主窗口(22000)低于Activity A的所有窗口(22001-22003),因为A是当前活动且可能拥有焦点。输入法窗口(23001)又高于应用层,但低于状态栏(24000)。整个体系层次分明,互不干扰。


💡 学习建议

要深入掌握Z轴顺序,你可以尝试:

  1. 阅读PhoneWindowManager :查看windowTypeToLayerLw()方法,了解系统如何将窗口类型映射为基础层值。
  2. 跟踪assignWindowLayers():在WMS/DisplayContent源码中查找此函数,观察其遍历和计算逻辑。
  3. 实验不同窗口类型:尝试创建不同类型(应用窗口、系统警告框)的窗口,观察它们的层叠行为。
  4. 关注动画场景 :在Activity切换时,使用dumpsys window windows命令查看窗口的layer值变化,感受动态调整。

Z轴顺序是WMS协调所有窗口视觉关系的核心机制,它既保证了系统UI的稳定(状态栏永远可见),又为应用提供了灵活的展示空间(对话框、子窗口)。理解它,你就掌握了Android窗口管理的"透视眼"。

相关推荐
城东米粉儿2 小时前
Android ActivityManagerService 笔记
android
shalou29012 小时前
mysql-connector-java 和 mysql-connector-j的区别
android·java·mysql
aaajj2 小时前
【Android】手机蜘蛛魔术的简易app例子
android
qw102482 小时前
MySQL-mysql zip安装包配置教程
android·mysql·adb
小飞学编程...2 小时前
【Java相关八股文(一)】
android·java·开发语言
QCzblack2 小时前
第五周作业
android
c***03232 小时前
Mysql之主从复制
android·数据库·mysql
火焰中舞蹈的小孩2 小时前
Unity和Android Studio相互调用 CH340在unity中调用
android·ide·android studio
不是AI2 小时前
【Unity开发】一、在安卓设备上运行Unity项目
android·unity·游戏引擎