2. 组件化拆分原则与架构选型
SystemUI 的组件化拆分不能仅凭直觉“按功能文件夹”切分,必须严格遵循几条核心原则,并在一开始就做对架构选型。本章逐一拆解分层原则、高内聚低耦合、接口抽象、依赖反转,以及 SPI / 接口 / AIDL 三种抽象机制的选型权衡。
2.1 分层原则与组件边界
Android 系统服务的天然分层(Kernel → HAL → Native → Framework → SystemUI)要求 SystemUI 内部也必须分层,否则混乱的跨层依赖会导致回归爆炸。
| 层级 | 职责 | 属于哪些组件 |
|---|---|---|
| UI 层 | 布局渲染、手势处理、动画驱动 | 通知面板、快速设置磁贴、导航手势 |
| 逻辑层 | 状态管理、事件路由、业务编排 | 通知管理器、音量调节器、屏幕录制 |
| 服务层 | 跨进程通信、系统数据聚合、权限校验 | StatusBarService、DreamManager |
| 数据层 | 本地存储、配置持久化、缓存 | SettingProvider 代理、SharedPreferences |
分层原则要求:上层依赖下层,下层绝不能依赖上层,且同层组件之间通过接口交互而非直接引用。
2.2 高内聚低耦合
每个组件只做一件事并且做到极致。例如“通知历史组件”只负责读取/写入通知历史,不混入通知过滤逻辑。
- 内聚:组件内部类应围绕同一职责变化,修改点集中在一个组件内。
- 耦合:组件间通信只能通过抽象接口,不允许直接 new 另一组件的实现类。
经验阈值:如果修改一个组件时,平均需要同步改动超过 3 个其他组件,说明耦合度过高,需重新划分边界。
2.3 接口抽象与依赖反转
依赖反转(DIP)是组件化拆分的灵魂:高层模块不依赖低层模块的具体实现,两者都依赖抽象接口。
在 SystemUI 场景下,
- UI 层组件(如快速设置面板)不直接引用“系统设置服务”的 ContentResolver,而是依赖一个
ISettingsFacade接口; - 接口定义在“公共 API 层”,实现类由低层或第三方注入。
这样替换实现(如从 SettingsProvider 换成 Mock)时,UI 层代码完全无需改动。
常见误区:接口抽象不等于增加一个 interface 文件就完事。接口的方法粒度必须与应用场景对齐,若暴露了过于底层的细节,依赖反转就退化为“接口泄漏”。
2.4 选型权衡:SPI vs 接口 vs AIDL
SystemUI 组件间通信机制取决于运行时隔离程度。下表三选一,不能混用。
| 机制 | 进程模型 | 调用方式 | 适用场景 | 风险 |
|---|---|---|---|---|
| Java 接口(Interface) | 同进程 | 直接调用/动态代理 | UI 层↔逻辑层、逻辑层↔服务层(同一 APK) | 无法跨进程;循环引用难检测 |
| SPI(Service Provider Interface) | 同进程 | 通过 ServiceLoader 或自定义类加载器 | 插件化组件、延迟加载、热替换 | 类加载性能开销;需要约定命名 |
| AIDL | 跨进程 | Binder 驱动 + 桩代理 | SystemUI↔SystemServer、SystemUI↔第三方 App | 序列化开销;接口版本升级困难 |
推荐策略:绝大多数内部组件使用 Java 接口 + 编译时依赖反转(通过 Dagger 或手动注入);仅在真正需要跨进程通信时(如与 SystemServer 的 WMS/AMS 交互)使用 AIDL;SPI 用于未来可能预留的第三方插件扩展点,但当前 SystemUI 拆分不建议引入。
2.5 架构关系示意
下面的 SVG 展示了 SystemUI 组件化后的典型分层与依赖反转流向,注意所有箭头均指向被依赖的一方,符合 DIP 原则。
2.6 风险点
- 接口膨胀:每个组件都按最坏情况设计接口,导致 API 层急剧增加。必须通过“最小接口原则”约束。
- 循环依赖:A 依赖 B 的接口,B 依赖 A 的接口,编译时可能通过但运行时死锁。可用 lint 或 ArchUnit 静态检查。
- 选型过度:大量使用 AIDL 会导致 Binder 调用排队,拖慢 UI 响应。仅当确实需要跨进程安全边界时才用。
2.7 落地步骤
- 识别“天然跨进程”的交互(如锁屏状态读取);
- 将 UI 层 与 逻辑层 的接口统一定义在
api组件中; - 使用 Dagger 或 Hilt 实现依赖注入,保证实现类可置;
- 每拆一个组件后运行全量自动化测试,验证接口调用是否符合分层约定;
- 禁止在非服务层组件内直接 import android.app.StatusBarManager 等系统 API,必须走接口抽象。
2.8 验收标准
| 验收维度 | 通过条件 |
|---|---|
| 分层合规 | 静态扫描报告显示无跨层引用(UI 层未直接调用服务层实现) |
| 接口覆盖率 | 组件间所有跨组件调用均已定义为接口,未发现 instanceof 或 getClass() 判断类型 |
| 循环依赖 | ArchUnit 检查未发现任何组件间循环依赖 |
| 选型一致性 | 同一组件内未同时使用 SPI 和 AIDL 做同一类通信 |
| 构建速度 | 删除任意一个组件模块后,其他模块可单独编译通过(依赖仅限接口) |