SystemUI 中的 WindowManager 应用:状态栏、导航栏、锁屏、通知面板的窗口类型与层级管理

各位同学,今天我们来聊聊 SystemUI 和 WindowManager 的“联姻”。说实话,SystemUI 是 Android 系统里最“显眼”的进程——你每天解锁手机、看时间、调音量、下拉通知,全都在跟它打交道。而这一切的背后,都是 WindowManager 在默默调度窗口的层级和类型。

我个人习惯把 SystemUI 比作一个“窗口杂技团”。状态栏、导航栏、锁屏、通知面板,每个演员都有自己的窗口类型和层级位置。排错了,轻则遮挡内容,重则系统崩溃。嗯,咱们今天就拆开看看,这些“演员”到底是怎么登台的。

1. 状态栏:TYPE_STATUS_BAR 的“钉子户”

状态栏,就是屏幕顶部显示时间、信号、电量的那条。它的窗口类型是 TYPE_STATUS_BAR,对应的层级值在 WindowManager 里是 FIRST_STATUS_BAR_LAYERLAST_STATUS_BAR_LAYER 之间。

为什么说它是“钉子户”?因为它的层级非常高,仅次于系统警告弹窗。我曾在项目中遇到过一个问题:第三方应用想通过悬浮窗盖住状态栏,结果死活盖不住。原因很简单——TYPE_STATUS_BAR 的层级比 TYPE_APPLICATION_OVERLAY 高太多了。

关键点:状态栏窗口的 z-order 在 2000 以上,普通应用窗口的 z-order 在 1~99。所以别想着用应用层的手段去遮挡它。

另外,状态栏还有一个特殊属性:它是“系统级”窗口,由 SystemUI 进程直接创建,不经过应用层的 Activity 生命周期。这意味着即使前台应用崩溃了,状态栏依然坚挺。你想想看,如果手机死机了连状态栏都消失,那用户得多慌?

2. 导航栏:TYPE_NAVIGATION_BAR 的“三键客”

导航栏,就是底部的返回键、Home 键、多任务键。它的窗口类型是 TYPE_NAVIGATION_BAR,层级略低于状态栏,但依然远高于普通应用。

这里有个细节:导航栏的窗口层级是 FIRST_NAVIGATION_BAR_LAYERLAST_NAVIGATION_BAR_LAYER。为什么比状态栏低?因为导航栏在底部,用户手指操作时可能会误触到应用内容,所以给它留了一点“让步”空间。

我记得有一次调试一个全屏游戏,游戏想隐藏导航栏来获得沉浸式体验。结果隐藏后,用户从底部上滑时,导航栏重新出现,但动画卡顿。排查下来发现是导航栏窗口的 FLAG_NOT_FOCUSABLEFLAG_ALT_FOCUSABLE_IM 标志位没处理好,导致输入法窗口跟它抢焦点。嗯,这种坑,不踩一次真的记不住。

避坑指南:如果你在定制 SystemUI,修改导航栏窗口的层级时,一定要确保它低于状态栏但高于应用窗口。我曾经把导航栏层级调得比状态栏还高,结果状态栏被导航栏遮住,用户看不到时间了……那叫一个尴尬。

3. 锁屏:TYPE_KEYGUARD 的“守门员”

锁屏,也就是 Keyguard,它的窗口类型是 TYPE_KEYGUARD。这个类型的层级非常特殊——它介于状态栏和导航栏之间,但有一个关键行为:锁屏窗口会“接管”整个屏幕的触摸事件。

为什么需要这样?因为锁屏状态下,用户不应该能操作应用。所以 WindowManager 给 TYPE_KEYGUARD 窗口设置了 FLAG_NOT_TOUCH_MODALFLAG_WATCH_OUTSIDE_TOUCH 标志,确保所有触摸事件先经过锁屏窗口过滤。

我遇到过最头疼的一个 bug:锁屏状态下,用户从顶部下滑,状态栏居然能拉下来。排查后发现是锁屏窗口的 TYPE_KEYGUARD 类型被错误地改成了 TYPE_STATUS_BAR_PANEL,导致层级错乱。说白了,窗口类型就是身份证,乱改就会出大问题。

窗口类型 层级范围 主要用途
TYPE_STATUS_BAR FIRST_STATUS_BAR_LAYER ~ LAST_STATUS_BAR_LAYER 状态栏显示
TYPE_NAVIGATION_BAR FIRST_NAVIGATION_BAR_LAYER ~ LAST_NAVIGATION_BAR_LAYER 导航栏显示
TYPE_KEYGUARD FIRST_KEYGUARD_LAYER ~ LAST_KEYGUARD_LAYER 锁屏界面
TYPE_STATUS_BAR_PANEL FIRST_STATUS_BAR_PANEL_LAYER ~ LAST_STATUS_BAR_PANEL_LAYER 通知面板、快速设置

4. 通知面板:TYPE_STATUS_BAR_PANEL 的“下拉菜单”

通知面板,就是你从顶部下拉出来的那个大面板。它的窗口类型是 TYPE_STATUS_BAR_PANEL,层级在状态栏之上。

为什么要在状态栏之上?因为通知面板是从状态栏拉出来的,它需要覆盖状态栏本身。你想想看,如果通知面板在状态栏下面,那下拉后状态栏还露在外面,多难看?

这里有一个重要的层级管理逻辑:通知面板的窗口是“临时”的。它只在用户下拉时创建,收起时销毁。但注意,不是真的销毁,而是通过 hide() 方法隐藏。为什么?因为频繁创建销毁窗口会导致性能问题,尤其是动画过程中。

注意:通知面板窗口的 TYPE_STATUS_BAR_PANEL 类型有一个隐含要求:它必须有一个父窗口,通常是状态栏窗口。如果没有正确设置父窗口,面板的动画会跟状态栏脱节,出现“面板飞走了”的诡异现象。我曾经在定制 ROM 时踩过这个坑,排查了整整两天。

5. 层级管理的核心逻辑:z-order 排序

说了这么多类型,其实底层就一个逻辑:z-order 排序。WindowManager 在添加窗口时,会根据窗口类型计算一个 z-order 值,然后插入到窗口列表的对应位置。

这个排序规则大致是这样的:

  • 应用窗口(TYPE_APPLICATION):z-order 最低,1~99
  • 子窗口(TYPE_APPLICATION_PANEL、TYPE_APPLICATION_MEDIA 等):z-order 在父窗口之上
  • 系统窗口(TYPE_STATUS_BAR、TYPE_NAVIGATION_BAR 等):z-order 在 2000 以上
  • 系统警告窗口(TYPE_SYSTEM_ALERT、TYPE_SYSTEM_ERROR 等):z-order 最高,通常在 3000 以上

但这里有个细节:同类型的窗口之间,后添加的会覆盖先添加的。所以 SystemUI 在启动时,必须按照正确的顺序添加状态栏、导航栏、锁屏等窗口。顺序错了,界面就会乱套。

我建议你在阅读 SystemUI 源码时,重点关注 BaseStatusBar.java 中的 start() 方法。那里就是窗口创建的入口,顺序一目了然。

6. 一张图看懂层级关系

说了这么多,不如一张图来得直观。下面这张 SVG 图展示了 SystemUI 各窗口的层级关系:

SystemUI 窗口层级关系图 低层级 高层级 应用窗口 TYPE_APPLICATION 导航栏 TYPE_NAVIGATION_BAR 锁屏 TYPE_KEYGUARD 状态栏 TYPE_STATUS_BAR 说明: • 应用窗口层级最低,位于最底层 • 导航栏层级高于应用窗口,但低于锁屏和状态栏 • 锁屏层级高于导航栏,用于拦截触摸事件 • 状态栏层级最高,确保始终可见 • 通知面板(TYPE_STATUS_BAR_PANEL)层级在状态栏之上 注:实际层级值由 WindowManager 根据类型和标志位动态计算 不同 Android 版本可能有细微差异,但整体结构一致

从这张图可以清晰地看到:状态栏在最顶层,锁屏在中间层,导航栏在底层。通知面板虽然没画出来,但它其实在状态栏之上——因为下拉时它需要覆盖状态栏。

7. 实战中的坑与经验

最后,分享几个我在实际项目中踩过的坑:

  • 窗口类型冲突:有一次第三方应用用了 TYPE_SYSTEM_ALERT 来显示悬浮球,结果跟 SystemUI 的导航栏抢层级,导致导航栏点击无响应。解决方案是限制第三方应用使用系统窗口类型。
  • 动画卡顿:通知面板下拉动画卡顿,排查发现是窗口的 FLAG_HARDWARE_ACCELERATED 没开。加上后流畅很多。
  • 锁屏穿透:锁屏状态下,用户通过某些手势能操作后台应用。这是因为锁屏窗口的 FLAG_NOT_TOUCH_MODAL 设置不当,导致触摸事件穿透了。

嗯,今天就先聊到这里。SystemUI 的窗口管理远不止这些,但理解了状态栏、导航栏、锁屏、通知面板这四大金刚的层级关系,你就掌握了核心骨架。剩下的细节,咱们后面慢慢填。

一句话总结:SystemUI 的窗口管理,本质就是给每个 UI 组件分配正确的窗口类型和层级,让它们各司其职、互不干扰。类型错了,层级乱了,界面就崩了。

公众号:蓝海资料掘金营,微信deep3321