跳到主要内容

组件间通信

问题

模块化后各模块之间如何通信?如何在不产生直接依赖的情况下互相调用?

答案

通信挑战

模块化后 feature-homefeature-profile 不能互相依赖,但业务需要互相跳转和数据交换:

app
├── feature-home ←→ feature-profile ❌ 不能互相依赖
├── feature-profile
├── core-common ← 两者都可以依赖的公共层
└── core-navigation

方案对比

方案原理优点缺点
接口下沉接口定义在公共模块类型安全、IDE 友好公共模块膨胀
SPI / ServiceLoaderJava SPI 机制无需公共接口模块反射开销
路由框架URL 映射解耦彻底字符串硬编码
事件总线发布-订阅简单难以追踪、类型不安全
依赖注入DI 容器管理类型安全、可测试配置复杂

方案一:接口下沉(推荐)

core-common/IUserService.kt
// 接口定义在公共模块
interface IUserService {
fun getUserName(): String
fun navigateToProfile(context: Context, userId: String)
}
feature-profile/UserServiceImpl.kt
// 实现在各自模块
class UserServiceImpl : IUserService {
override fun getUserName() = "Vincken"
override fun navigateToProfile(context: Context, userId: String) {
context.startActivity(Intent(context, ProfileActivity::class.java).apply {
putExtra("userId", userId)
})
}
}
app 模块中用 Hilt 绑定
@Module
@InstallIn(SingletonComponent::class)
abstract class ServiceModule {
@Binds
abstract fun bindUserService(impl: UserServiceImpl): IUserService
}
feature-home 使用
@HiltViewModel
class HomeViewModel @Inject constructor(
private val userService: IUserService // 只依赖接口
) : ViewModel()

方案二:SPI / ServiceLoader

core-common/IFeatureService.kt
interface IFeatureService {
fun init(context: Context)
}
feature-home/HomeFeatureService.kt
class HomeFeatureService : IFeatureService {
override fun init(context: Context) { /* ... */ }
}
feature-home/src/main/resources/META-INF/services/com.example.IFeatureService
com.example.home.HomeFeatureService
加载服务
val services = ServiceLoader.load(IFeatureService::class.java)
services.forEach { it.init(context) }

常见面试问题

Q1: 接口下沉方案会导致公共模块膨胀吗?如何解决?

答案

当接口越多,公共模块确实会变大。解决方案是进一步拆分公共层:

core-common          # 基础工具
core-model # 数据模型
core-service-api # 服务接口定义
├── user-api
├── order-api
└── payment-api

或者每个 feature 模块拆分出一个 -api 模块:feature-profile-api(只包含接口),其他模块依赖 -api 而不依赖实现模块。

Q2: 组件化中各模块如何独立运行调试?

答案

通过 isModule 开关将 feature 模块在 applicationlibrary 之间切换:

// feature-home/build.gradle.kts
val isModule: Boolean = project.findProperty("isModule")?.toString()?.toBoolean() ?: false

if (isModule) {
apply(plugin = "com.android.application")
} else {
apply(plugin = "com.android.library")
}

独立运行时提供单独的 AndroidManifest.xml(包含 LAUNCHER Activity)和 Application

相关链接