多模块Android架构中Hilt模块的优雅结构化方案问询
Hilt多模块依赖图接入问题解决方案
问题背景
当前使用Hilt 2.44.2版本,模块架构如下:
- :app模块:表示层,包含Activity、导航、Compose/视图、ViewModel、生命周期处理等,依赖:domain与:core模块;
- :domain模块:领域层,包含领域模型、用例及用例中使用的Repository接口,负责业务逻辑处理,依赖:core模块;
- :data模块:数据层,处理网络、数据库及相关传输模型,同时实现:domain中定义的Repository接口,依赖:domain与:core模块;
- :core模块:核心层,提供各模块共享的工具与通用逻辑。
核心问题:由于:data依赖:domain而非反向依赖,:data的Hilt Modules无法接入整体依赖图。常规方案是让:app依赖:data,但限制条件为不允许:app访问:data的类,也不想建立该依赖关系。
解决方案:Gradle配置隔离+Hilt组件扫描
步骤1:在:data模块中配置自定义Gradle依赖配置
在:data模块的build.gradle.kts中添加以下配置,将Hilt Modules相关编译产物单独暴露:
configurations { create("hiltModules") { isCanBeConsumed = true isCanBeResolved = false attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, Usage.JAVA_RUNTIME)) } } } artifacts { add("hiltModules", sourceSets.main.get().output) }
此配置会把:data的编译产物(包含Hilt Modules)单独放到hiltModules配置中,而非默认的implementation配置,避免业务类被意外引用。
步骤2:在:app模块中仅依赖:data的Hilt Modules配置
在:app模块的build.gradle.kts中添加依赖,同时限制依赖类型:
dependencies { // 编译时仅获取Hilt模块元数据,不允许引用业务类 compileOnly(project(path = ":data", configuration = "hiltModules")) // 运行时加载Hilt模块实现,完成依赖注入 runtimeOnly(project(path = ":data", configuration = "hiltModules")) }
compileOnly保证:app编译阶段无法访问:data的业务类(如传输模型、具体网络实现),runtimeOnly确保Hilt在运行时能找到并加载:data中的Repository实现。
步骤3:确保:data的Hilt Modules使用正确的@InstallIn注解
在:data模块的Hilt Modules上使用全局组件注解,比如:
@Module @InstallIn(SingletonComponent::class) class DataModule { @Provides fun provideXXXRepository(): XXXRepository { return XXXRepositoryImpl() } }
@InstallIn(SingletonComponent::class)会让Hilt在应用启动时自动扫描并加载该模块,无需:app显式引用。
原理说明
- 通过Gradle自定义配置,实现了:data模块中Hilt Modules与业务代码的隔离暴露,避免:app依赖整个:data模块;
compileOnly+runtimeOnly的组合,既保证了编译阶段的类隔离,又满足了运行时依赖注入的需求;- Hilt的组件扫描机制会自动识别
@InstallIn注解的模块,完成依赖图的整合。
替代方案:@EntryPoint(适用于单一接口注入场景)
如果仅需注入少数几个Repository接口,可在:domain模块中定义@EntryPoint:
// :domain模块中 @EntryPoint @InstallIn(SingletonComponent::class) interface XXXRepositoryEntryPoint { fun getXXXRepository(): XXXRepository }
然后在:app中通过EntryPointAccessors获取实例:
val repository = EntryPointAccessors.fromApplication( context, XXXRepositoryEntryPoint::class.java ).getXXXRepository()
此方案无需修改Gradle配置,但需逐个定义EntryPoint,适合场景单一的情况。
内容的提问来源于stack exchange,提问作者Yokich
相关产品推荐
相关产品推荐

