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 很特殊 —— 它并不总是焦点窗口,却需要监听很多全局输入事件,比如电源键、快捷手势、截屏组合键等。
做法通常有两种:
- InputFilter:在 IMS 的输入流里插一个过滤器,所有事件都会经过 SystemUI 的 Filter 过滤一遍。这个办法权限高,可以拦截甚至篡改事件。
- 注册监听器:比如
KeyguardUpdateMonitor订阅指纹、电源键等。 - SystemUI Tuner 和 ScreenOff:也依赖 InputMonitor 去感知屏幕 touch 状态。
举个例子,截屏事件其实是 Power + VolumeDown 组合键,InputManager 先分发给 PhoneWindowManager,PhoneWindowManager 再回调 SystemUI 的截屏服务。这个过程涉及到 IMS → WMP → SystemUI 三条链路。
避坑指南:千万不要在 InputFilter 里做耗时操作,否则整个系统的输入延迟都会增加。我见过一个同事在 onInputEvent 里做了 byte 编码转换,导致打字卡顿。解决方案是把事件放到 Handler 的 MessageQueue 里异步处理。
2.4 三者协作流程总览
下面这张图概括了 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 一半的架构。