第2章:模块化概念与设计原则
本章围绕模块化在 Android Framework 语境下的定义、边界与四大设计原则展开,帮助架构师建立统一的模块化认知基线。所有内容依据架构方案型课程范式组织:边界、职责、数据流/控制流、选型权衡、落地步骤与验收标准。
2.1 模块定义与边界
在 Android Framework 中,模块是一个具备独立职责、可编译、可替换的逻辑或物理单元。物理形态包括动态库(.so)、JAR 包、APK 或 Apex 模块。
模块的边界通过以下三个维度显式定义:
- 构建边界:
Android.bp / Android.mk中的module声明,配合visibility规则控制谁可依赖。 - API 边界:
@public / @SystemApi / @TestApi / @hide注解定义暴露层级,未公开的 API 不属于模块契约。 - 运行时边界:SELinux label、进程(
isolatedProcess)与 Binder 域共同构成信任链路。
边界定义原则: 模块的公开 API 应视为“永久接口”,一经交付即产生维护成本;建议使用
@TestApi 或 @SystemApi 明确消费者范围。2.2 核心设计原则
模块化落地的四个原则构成相互增强的关系:高内聚与低耦合定义模块内部质量与外部关系,接口隔离与依赖倒置则约束模块间的通信契约。
| 原则 | 核心要点 | Framework 体现 | 违反后果 |
|---|---|---|---|
| 高内聚 | 职责单一,功能聚合 | WindowManagerService 专门管理窗口层级与输入分发 | 模块臃肿,牵涉过多变更领域 |
| 低耦合 | 依赖接口而非具体实现,通信规范化 | 一切跨进程交互通过 Binder 接口(AIDL)进行 | 一处改动导致连锁回归 |
| 接口隔离 | 调用方只依赖自已需要的精简接口 | IApplicationThread 与 IActivityManager 面向不同角色设计 | 调用方触及无关方法,违反最小知识原则 |
| 依赖倒置 | 高层模块不依赖低层实现,双方依赖抽象 | ServiceManager.addService / getService 实现服务发现与绑定 | 模块强耦合,无法独立编译或替换 |
高内聚 ? 低耦合的度量参考: 高内聚可用“扇入(被多少模块使用)+ 扇出(依赖多少模块)”的合理范围评估;低耦合通过“接口 vs 实现绑定”的比率判断,接口绑定时可替换性最高。
2.3 原则在 Framework 中的体现:数据流、控制流与信任链路
上述原则在 SystemServer 与服务模块间形成稳定的分层通信模型:
- 控制流: 启动阶段 → 系统服务向 ServiceManager 注册 → 客户端 getService 绑定 → 方法调用。控制权始终通过 Binder 接口传递。
- 数据流:
App 进程 →
Binder Proxy→ 内核 Binder 驱动 →Stub→ 实际服务实现。数据序列化/反序列化由 AIDL 工具自动生成。 - 信任链路: 根信任 → 启动映像签名(AVB) → SystemServer 进程(platform 域) → 应用进程(untrusted_app 域)。模块间信任由 SELinux 策略与进程隔离共同执行。
依赖倒置在此处表现为:高层(SystemServer)不直接实例化具体服务,而是通过 main() 创建并注册;消费者通过 ServiceManager 查找服务,实现“运行时绑定”。
2.4 选型权衡与风险点
- 权衡 1:模块粒度 — 细粒度提升复用性但增加 Binder 事务数量(1 MB 缓冲区限制);粗粒度简化通信但降低内聚。Framework 通常采用“按子系统聚合”的中等粒度。
- 权衡 2:接口稳定性 vs 灵活性 — 严格接口隔离有利于长期演进,但过度设计导致“接口爆炸”。建议从 2–3 个消费者开始抽象。
- 风险点:
- 循环依赖:模块 A 依赖模块 B,B 反向依赖 A。需通过接口抽取或事件总线解除。
- 边界泄漏:通过反射、
@hideAPI 或VisibleForTesting绕过模块边界,导致耦合度退化。 - 性能陷阱:高频场景下 IPC 延迟不可接受(如输入事件分发),必须使用内联或共享内存。(参考
InputDispatcher与WindowManagerService的边界设计)。
避坑提醒: 依赖倒置最常见的错误是在抽象层中包含业务逻辑。抽象层(接口)应仅定义契约,不持有状态或实现。否则会导致“抽象泄露”,违背隔离初衷。
2.5 落地步骤与验收标准
模块化重构应渐进开展,避免一次性大规模拆解。推荐 5 步走策略:
- 依赖梳理: 使用
androidmk/ Soong 或 gradle 依赖分析工具,生成模块依赖图,识别“超级模块”。 - 接口定义: 为每个服务提取 AIDL 或纯 Java 接口,确保接口只包含消费者需要的方法。
- 依赖倒置适配: 将
new ServiceImpl()替换为ServiceManager.getService()或构造注入。 - 代码搬迁: 按模块目录
/frameworks/base/services/core/java/com/android/server/...重组,调整Android.bp的visibility。 - 验证边界: 运行
atest+ 构建绿灯 + 接口调用审计(确保无跨边界反射)。
验收标准清单:
| 维度 | 验收条件 | 验证方式 |
|---|---|---|
| 独立性 | 模块可在无真实系统环境下通过单元测试 | Robolectric / 实测 Test APK |
| 可替换性 | 提供 Mock 实现后,消费者无需改动 | 接口注入替换测试 |
| 耦合度 | 扇入 + 扇出 较重构前降低或持平(不新增循环依赖) | Soong 依赖报告 / module_graph |
| 性能 | 新引入 IPC 或抽象调用满足延迟基线(P99 < 5ms) | Perfetto 插桩 / Binder 事务统计 |
上述验收项应在每日集成(TOT)中持续监控,防止模块化演进过程中的边界腐化。