Skip to content

大型 App 模块化架构、SPI 机制与组件间通信 ​

回到总览:混合开发与桥接
相关模块:跨端一致性设计:多端架构分层与设计系统 · ViewModel 架构、SavedStateHandle 与进程被杀恢复

一句话定义 ​

大型 App 模块化(Modularization)与组件化架构是指将单体应用拆分为宿主壳工程 (App)、业务功能模块 (Feature Modules) 以及 基础公共库 (Core Modules);通过物理依赖隔离(implementation)以及 SPI (Service Provider Interface) 服务发现或路由机制,实现模块间零直接依赖的解耦通信与独立编译。

代码索引 ​

运行代码:modularization-spi-demo

关键源码:

为什么需要 ​

  • 为什么大型团队开发不能将所有代码放在单个 app 模块中?
    • 一句话答:单体模块会导致全量编译耗时极长、代码修改影响范围不可控、多人 Git 冲突频繁,且无法实现业务模块的独立开发与按需动态加载。
  • 模块间隔离后,feature_a 模块如何调用 feature_b 提供的服务而不需要直接 implementation project(':feature_b')?
    • 一句话答:通过“接口下沉(Interface Sinking)”模式:定义公共接口库 feature_b_export,feature_b 实现该接口,feature_a 仅依赖 feature_b_export 接口库,并通过 SPI (ServiceLoader) 或路由框架在运行时动态查找接口实现类。
  • Gradle api 与 implementation 依赖指令在模块化架构中有何本质区别?
    • 一句话答:implementation 会隐藏依赖的传递,当被依赖模块修改时只重新编译直接依赖项,大幅缩短增量编译时间;而 api 会向上传递依赖,任何改动都会引发全量重新编译。

底层机制 ​

1. 现代大型 App 三层模块化依赖拓扑 ​

图注补充:app 模块: 仅做全局 Application 初始化与组件装配;2. 业务功能模块层 (Feature Modules)";3. 接口下沉层 (Export / API Modules)";4. 基础公共库层 (Core / Infrastructure)"。

2. 组件解耦通信三大方案对比 ​

图注补充:SPI: SPI / ServiceLoader 容器;SPI: 1. ServiceLoader.load(IUserService.class);SPI: 2. 读取 META-INF/services/ 配置文件。

解耦方案对比表 ​

方案解耦机制原理优缺点
SPI (ServiceLoader)接口与实现分离读取 META-INF/services/ 接口映射原生 JDK 支持,无需第三方库;但懒加载控制较弱
路由框架 (ARouter / WMRouter)字符串 URL 映射APT / KSP 编译期生成路由表,字节码插桩支持页面跳转与服务发现;但依赖注解处理器与反射
EventBus 事件总线发布-订阅 (Pub/Sub)运行时内存事件派发完全无接口依赖;但过度使用会导致事件链路混乱

代码示例与 SPI 实现 ​

1. 接口下沉模块 (feature_user_export) ​

kotlin
// 放在公共接口模块,其他模块只需依赖此接口
interface IUserService {
    fun getUserId(): String
    fun isLogin(): Boolean
}

2. 接口实现模块 (feature_user) ​

kotlin
// 放在具体业务模块
class UserServiceImpl : IUserService {
    override fun getUserId(): String = "10086"
    override fun isLogin(): Boolean = true
}

在 feature_user 的 resources/META-INF/services/com.example.IUserService 文件中写入: com.example.user.UserServiceImpl

3. 调用方使用 (feature_pay) ​

kotlin
fun processPayment() {
    // 仅依赖 IUserService 接口,无需引用 feature_user 模块
    val userService = ServiceLoader.load(IUserService::class.java).firstOrNull()
    if (userService?.isLogin() == true) {
        println("用户已登录,开始支付: ${userService.getUserId()}")
    }
}

常见误配、事故后果与排障 ​

1. 事故:模块间循环依赖导致 Gradle 编译报错 ​

  • 误配原因:feature_a 依赖了 feature_b,同时 feature_b 又依赖了 feature_a。
  • 后果:Gradle 构建直接报错 Circular dependency between projects。
  • 排障与修法:将公共调用的数据模型与接口提取下沉到第三个共享模块 feature_export,彻底解开循环依赖。

对应实验 ​

Lab说明源码
modularization-spi-demoCLOSURE:补齐模块化 / SPI 的宿主入口、运行说明与回链SpiArchitectureDemoActivity.kt

复习检查题 ​

  1. 在 Android 模块化架构中,为什么推荐使用 implementation 而不是 api 引入依赖?

    答:使用 implementation 声明的依赖不会暴露给上层模块,当被依赖的模块内部发生修改时,Gradle 只需要重新编译当前模块,而不会触发上层依赖者的重新编译(避免依赖泄漏与编译链扩散),能够显著提升项目的增量编译速度。

  2. 什么是“接口下沉 (Interface Sinking)”模式?它如何解决业务模块间的强耦合?

    答:接口下沉是指将业务模块 feature_a 需要暴露给外部的方法提取为一个独立的接口库 feature_a_export。其他业务模块只依赖这个轻量的 export 接口库,而不直接依赖 feature_a 的真实实现。运行时通过 SPI 或路由框架获取接口实例,实现了模块代码在编译期的完全解耦。


速记 ​

  • 三层拓扑:App 壳装配 → Feature 业务模块 → Core 基础公共库。
  • 接口下沉:模块间沟通靠 Export 接口库,严禁业务模块间直接 implementation。
  • 依赖隔离:能用 implementation 不用 api,控制增量编译影响链。

站点构建时间:2026/8/24 23:43:17