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_MEDIA、TYPE_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_FOCUSABLE、FLAG_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_PANEL、TYPE_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窗口管理的"透视眼"。

相关推荐
三少爷的鞋11 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)15 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码17 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程20 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程21 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓1 天前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer