2. 系统服务、窗口管理、输入系统与 SystemUI 的交互关系

各位同学,今天我们来聊聊 SystemUI 背后那套庞大的 Android 系统架构。很多人觉得 SystemUI 就是个系统 UI 壳子,其实它背后依赖了三个关键角色:系统服务、窗口管理 (WMS) 和输入系统 (InputManager)。这三个家伙跟 SystemUI 的关系,有点像公司的后勤、行政和安保 —— 各司其职,缺一不可。

我最早接触这块的时候,花了整整两周才理清楚调用链。你想想看,一个状态栏下拉,背后要经过 Binder、跨进程、再绘制,稍不注意就会出问题。好,咱们一个一个拆。

2.1 系统服务 —— SystemUI 的“后台大管家”

SystemUI 本身跑在 com.android.systemui 进程里,但它要去获取各种系统状态,就必须跟 SystemServer 进程里的各种 Service 打交道。说白了,SystemUI 只是一个“前端界面展示层”,真正存数据、管状态的后台都是那些系统服务。

  • StatusBarManagerService:负责管理状态栏的显隐、图标、动画。SystemUI 通过 StatusBarManager 这个 Binder 代理来调用。
  • NotificationManagerService:通知的存储、排序、展示都由它管。SystemUI 订阅通知变化,然后把通知卡片画出来。
  • ActivityManagerService:SystemUI 需要知道当前前台是哪个 App,用来显示最近任务、调整导航栏样式。
  • PackageManagerService:获取图标、App 名称,比如状态栏的第三方通知图标。
  • 其他:BatteryStatsService、PowerManagerService、TelephonyManager 等,依功能而定。

个人经验:我在做 Android 12 自定义 SystemUI 时,遇到过一个坑 —— 如果 SystemUI 进程启动时还没拿到这些 Service 的 Binder 引用,就会空指针闪退。后来我给 Application 的 onCreate 里加了一个超时重试机制,等 Service 全部就绪再初始化业务逻辑。

2.2 窗口管理 (WMS) —— 布局指挥官

WMS 全称 WindowManagerService,它决定了每一个窗口的大小、位置、z-order。SystemUI 的导航栏、状态栏、锁屏界面都是系统级窗口,它们的窗口类型非常特殊。

窗口类型 Type 常量 作用
状态栏 TYPE_STATUS_BAR 系统全局最顶层
导航栏 TYPE_NAVIGATION_BAR 底部手势/按键栏
锁屏 TYPE_KEYGUARD 锁屏界面覆盖
通知面板 TYPE_VOLUME_OVERLAY 音量控制/其他浮层

你可能会想,SystemUI 怎么添加这些窗口?其实是通过 WindowManager.addView() 方法,传入 WindowManager.LayoutParams,把 type 设为上面那些系统类型。注意:只有系统签名等级的应用才能申请这些类型,普通 App 会直接抛异常。

// SystemUI 内部添加导航栏窗口的简化写法
WindowManager wm = context.getSystemService(WindowManager.class);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
    WindowManager.LayoutParams.MATCH_PARENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
    PixelFormat.TRANSLUCENT);
wm.addView(navigationBarView, lp);

我曾经踩过的坑:某次升级目标 SDK 版本后,系统禁止从后台直接调用 addView 添加系统窗口,导致导航栏闪烁。最后发现需要在 ActivityManager.START_FLAG_ONGOING 状态下才能稳定添加。

2.3 输入系统 (InputManager) —— 事件分发的“中枢神经”

InputManagerService (IMS) 负责从驱动拿事件,然后分发给焦点窗口。但 SystemUI 很特殊 —— 它并不总是焦点窗口,却需要监听很多全局输入事件,比如电源键、快捷手势、截屏组合键等。

做法通常有两种:

  1. InputFilter:在 IMS 的输入流里插一个过滤器,所有事件都会经过 SystemUI 的 Filter 过滤一遍。这个办法权限高,可以拦截甚至篡改事件。
  2. 注册监听器:比如 KeyguardUpdateMonitor 订阅指纹、电源键等。
  3. SystemUI Tuner 和 ScreenOff:也依赖 InputMonitor 去感知屏幕 touch 状态。

举个例子,截屏事件其实是 Power + VolumeDown 组合键,InputManager 先分发给 PhoneWindowManager,PhoneWindowManager 再回调 SystemUI 的截屏服务。这个过程涉及到 IMS → WMP → SystemUI 三条链路。

避坑指南:千万不要在 InputFilter 里做耗时操作,否则整个系统的输入延迟都会增加。我见过一个同事在 onInputEvent 里做了 byte 编码转换,导致打字卡顿。解决方案是把事件放到 Handler 的 MessageQueue 里异步处理。

2.4 三者协作流程总览

下面这张图概括了 SystemUI 与三个核心模块的交互关系。节点颜色代表不同的子系统,箭头表示依赖方向和数据流向。

SystemUI 与系统服务、WMS、InputManager 交互关系 SystemServer StatusBarManagerService NotificationManagerService ActivityManagerService BatteryStatsService (更多系统服务...) WindowManagerService 窗口层级管理 System 窗口添加/更新 InputManagerService 输入事件分发 InputFilter 拦截 SystemUI(系统 UI 进程) 状态栏 / 导航栏 / 锁屏 通知面板 / 快速设置 手势 / 截屏 / 音量 Binder IPC 获取状态 注册回调 窗口层级 / 布局参数 addView/updateView 输入事件 / 手势 反馈 / 拦截结果 说明: 实线箭头表示数据流入 SystemUI,虚线箭头表示 SystemUI 向外部服务发起调用。 所有通信均通过 Binder IPC 完成,SystemUI 进程不能直接访问系统服务内存。

2.5 关键交互路径总结

模块 与 SystemUI 的通信方式 典型场景
系统服务 Binder 远程调用(同步/异步) 获取通知列表、电量状态、当前应用
WMS WindowManager 的 addView/removeView/update 创建/更新导航栏、状态栏窗口
InputManager InputFilter / InputMonitor / 事件监听器 截屏组合键、手势导航、指纹解锁处理

2.6 一些实际经验

我记得做 Android 13 的锁屏适配时,遇到过 WMS 拒绝添加 TYPE_KEYGUARD 窗口的问题。查日志发现是因为 PhoneWindowManager 在 isKeyguardLocked 状态里做了限制。嗯,这里要注意:SystemUI 必须等 WMS 的 onWindowManagerReady 回调后才能添加系统窗口,否则会报 BadTokenException。

另外,输入系统这块有个常见陷阱:如果你用 InputFilter 拦截了 HOME 键,别忘了通知 WMS 释放焦点,不然按 HOME 键后界面会卡住。我当初调试了一整天才发现是焦点没释放……

一句话总结:SystemUI 通过 Binder 向系统服务索取信息,通过 WindowManager 向 WMS 注册窗口,通过 InputFilter 从 InputManager 获取事件。掌握了这三条数据通路,你就理解了 SystemUI 一半的架构。