组件职责与接口契约
组件化拆分的核心在于明确每个组件的边界与协作方式。本章为 SystemUI 的各个拆分组件定义对外提供的服务(接口)、所依赖的外部能力(依赖接口)、向上层或平行组件传递的“事件回调”以及组件内部或跨组件流通的“数据模型”。这些契约是后续分层设计、依赖注入和集成测试的契约基础。
4.1 组件一览
SystemUI 按业务领域拆分为以下 7 个核心组件(不含基础库层):
| 组件名称 | 核心职责 |
|---|---|
| StatusBar | 状态栏图标、系统图标、蜂窝/WiFi/电量显示、通知图标 |
| NavigationBar | 三键导航(返回/Home/最近任务)、手势导航指示器 |
| QuickSettings | 快捷开关面板(WiFi/蓝牙/亮度等)、编辑布局 |
| NotificationPanel | 通知抽屉、通知列表管理、通知长按/滑动交互 |
| Keyguard | 锁屏界面、解锁逻辑、紧急呼叫、媒体控制 |
| VolumePanel | 音量弹窗、音量滑块、铃声音量/媒体音量切换 |
| BatteryController | 电池状态、充电状态、省电模式、BatterySaver |
4.2 接口契约定义方式
每个组件必须暴露 Interface / API 供其他组件或系统服务调用,同时声明自己依赖的 Required Interface。事件回调采用 Listener / Callback 方式向上冒泡或平行通知。数据模型分为:内部状态模型(组件的 UI 状态)和 共享数据模型(跨组件传递的不可变数据)。
设计原则:接口尽量窄(最小知识原则),回调尽量少(避免长链回调污染)。共享数据模型使用不可变对象,通过消息总线(如 LiveData / Flow)分发。
4.3 接口契约全景
下表涵盖每个组件对外提供的接口、依赖接口、事件回调和关键数据模型。契约的粒度以组件为单元,不考虑内部模块。
| 组件 | 对外接口(Provided) | 依赖接口(Required) | 事件回调(Callback) | 数据模型 |
|---|---|---|---|---|
| StatusBar | 更新系统图标、设置是否可见 | 通知管理器、时钟源、电池状态 | 点击系统图标(蓝牙/WiFi)回调 | StatusBarIcons, SystemIconState |
| NavigationBar | 设置导航模式、禁用/隐藏按键 | 手势检测引擎、窗口策略 | Home键/返回键/最近任务点击事件 | NavigationMode, KeyEvent |
| QuickSettings | 展开/收起面板、添加/移除Tile | 设置提供者、亮度控制器 | Tile点击/长按、面板滑动状态变化 | QsTile, TileSpec, PanelState |
| NotificationPanel | 显示/隐藏通知列表、更新单条通知 | 通知排序引擎、通知渠道管理器 | 通知点击、滑动删除、展开/折叠 | NotificationData, NotificationRow |
| Keyguard | 锁定/解锁、验证凭据、显示Pin/Patter | 用户管理器、生物识别服务 | 解锁成功/失败、紧急呼叫 | KeyguardState, SecurityMode |
| VolumePanel | 显示/隐藏音量面板、设置当前音量流 | 音频管理器、音频策略 | 音量滑块拖动、流切换 | VolumeStream, StreamState |
| BatteryController | 提供当前电池百分比、充电状态 | 电池管理服务、省电模式策略 | 电池状态变化、省电模式切换 | BatteryState, ChargeStatus |
4.4 接口依赖与事件流关系图
以下 SVG 展示了 7 个组件之间的接口依赖(实线箭头)和重要事件回调(虚线箭头),以及共享数据模型所在的位置。注意:BatteryController 作为纯数据提供者,被多个组件依赖;Keyguard 与 NotificationPanel 之间存在锁屏状态回调。
设计权衡:上图将 BatteryController 设计为“纯数据提供者”,其接口极简(仅 getBatteryState() 与 registerCallback())。这样做可以避免其他组件直接依赖电池硬件细节,也便于在测试时替换 Mock。但是,如果 BatteryController 过于集中,会形成“数据总线”的热点,需要评估更新频率。若电池状态变化极快(如充电电流波动),建议改用冷流(Cold Flow)避免背压。
4.5 数据模型的划分
数据模型分为两类:
- 内部状态模型:仅在组件内部使用的 UI 状态,例如 QuickSettings 的“当前面板展开高度”或 Keyguard 的“密码输入错误次数”。这些模型对外透明。
- 共享数据模型:需要跨组件传递的不可变对象,例如
BatteryState(包含百分比、充电状态、省电模式标志)在 StatusBar 和 QuickSettings 之间共享;NotificationEntry在 NotificationPanel 和 Keyguard 之间共享。
所有共享数据模型必须满足:序列化/反序列化支持(如果跨进程)、不可变(使用 data class 或 @Immutable)、通过公共模块(如 systemui-shared-model)发布。
4.6 风险点与选型权衡
| 风险点 | 表现 | 缓解措施 |
|---|---|---|
| 循环依赖 | 组件 A 依赖 B,B 间接依赖 A | 使用接口反转(DIP),将共享接口提升到公共基础库层 |
| 回调泛滥 | 一个组件的状态变化触发过多节点的更新 | 引入聚合事件(如 SystemUIEvent 统一分发给订阅者) |
| 数据模型膨胀 | 共享数据模型包含过多字段,导致耦合 | 按“最小需要原则”拆分,每个模型只包含该场景必需字段 |
| 跨进程接口延迟 | 接口调用涉及 IPC(如跨系统服务) | 在组件内部缓存状态,使用异步回调,或采用 binder 池减少连接 |
重要提醒:接口契约一旦制定,修改成本极高。建议在第一个版本就定义完备的接口签名,并采用“适配器模式”来兼容未来变化。不要在组件间直接暴露内部类,必须使用接口抽象层。
4.7 落地步骤与验收标准
- 编写每个组件的
.api接口定义文件(可使用 Kotlin 接口或 AIDL),并放置在独立模块中。 - 生成接口文档(如使用 Dokka 或 Doxygen),并人工评审接口签名是否违反依赖规则。
- 构建单元测试框架,为每个接口提供 Mock 实现,验证事件回调方向是否正确。
- 集成测试:通过自动化测试模拟所有组件同时运行,检查数据模型流转是否一致。
- 验收标准:所有组件只能通过已定义的接口通信,不存在绕过契约的直接引用;修改接口时必须修改所有实现方,否则编译失败。
通过上述接口契约的严格定义,SystemUI 的组件化拆分才能避免“拆而不解耦”的陷阱。下一环节将基于这些契约,设计组件间通信机制和依赖注入方案。