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 原则。

SystemUI 组件化分层与依赖反转(DIP) UI 组件层 通知面板/快速设置/导航 逻辑组件层 通知管理器/音量/录屏 服务组件层 StatusBarService/Dream 数据组件层 SettingProvider/缓存 公共接口抽象层 (API) - INotificationController - IQuickSettingsProvider - IStatusBarService (AIDL) 依赖 依赖 依赖 可选注册回调 箭头指向 被依赖方

2.6 风险点

  • 接口膨胀:每个组件都按最坏情况设计接口,导致 API 层急剧增加。必须通过“最小接口原则”约束。
  • 循环依赖:A 依赖 B 的接口,B 依赖 A 的接口,编译时可能通过但运行时死锁。可用 lint 或 ArchUnit 静态检查。
  • 选型过度:大量使用 AIDL 会导致 Binder 调用排队,拖慢 UI 响应。仅当确实需要跨进程安全边界时才用。

2.7 落地步骤

  1. 识别“天然跨进程”的交互(如锁屏状态读取);
  2. 将 UI 层 与 逻辑层 的接口统一定义在 api 组件中;
  3. 使用 Dagger 或 Hilt 实现依赖注入,保证实现类可置;
  4. 每拆一个组件后运行全量自动化测试,验证接口调用是否符合分层约定;
  5. 禁止在非服务层组件内直接 import android.app.StatusBarManager 等系统 API,必须走接口抽象。

2.8 验收标准

验收维度通过条件
分层合规静态扫描报告显示无跨层引用(UI 层未直接调用服务层实现)
接口覆盖率组件间所有跨组件调用均已定义为接口,未发现 instanceof 或 getClass() 判断类型
循环依赖ArchUnit 检查未发现任何组件间循环依赖
选型一致性同一组件内未同时使用 SPI 和 AIDL 做同一类通信
构建速度删除任意一个组件模块后,其他模块可单独编译通过(依赖仅限接口)