多模块Android+Kotlin+Hilt项目:模块无法访问Application类
多模块Android应用集成Hilt:全局访问Application/配置类的解决方案
问题概述
基于Kotlin开发多模块Android应用,通过模块拆分实现关注点分离,集成Hilt做依赖解耦时,遇到模块无法访问Application类的问题:
- 原有的
AppSettings类整合了应用配置常量(如颜色)、Context、单例SQLite实例,此前用静态+WeakReference方式访问,现在要转用DI实现解耦和可测试性 - 纠结
@HiltAndroidApp标注的Application类放在哪个模块,以及如何让配置类在所有模块全局可用
项目结构与模块依赖
项目结构
project |- App |- Common |- Core |- DTO |- Repository
模块可见性关系
Project | |- App | |- Common | |- DTO | |- Core | |- Common | |- DTO | |- Core | |- Common | |- DTO | |- Repository | |- Common | |- DTO
核心矛盾分析
- Application类的位置限制:
@HiltAndroidApp注解必须放在App模块,因为Hilt会基于这个类生成主组件代码,其他模块不能放置该注解类,否则Hilt无法正常工作。 - 配置与Application的耦合:如果把包含配置常量的
AppSettings作为Application类放在App模块,依赖Common的模块无法访问它;如果放在Common模块,违反Hilt的Application类位置规则。 - 错误的实例创建方式:手动
new AppSettings()获取实例是错误的,Application由系统初始化,手动创建会导致Context无效、非单例等问题。
解决方案
1. 拆分职责,解耦配置与Application
把AppSettings的职责拆分为两部分:
- 配置常量类:在Common模块创建
AppConfig,仅存放静态配置值,让所有依赖Common的模块直接访问:// Common模块 object AppConfig { const val ACTION_BAR_TITLE_COLOR = "#0D0D0D" // 其他配置常量... } - Application类:在App模块创建
MyApp,仅负责Hilt初始化和提供Application级资源:// App模块 @HiltAndroidApp class MyApp : MultiDexApplication() { // 这里不需要放配置常量,仅保留Application生命周期相关逻辑 }
2. 通过Hilt提供全局可访问的Application级资源
在App模块创建Hilt模块,提供Context、SQLite等需要Application级别的资源,所有模块通过Hilt注入获取:
// App模块 @Module @InstallIn(SingletonComponent::class) object AppModule { // 提供Application Context @Provides @Singleton fun provideAppContext(@ApplicationContext context: Context): Context { return context } // 提供单例SQLite实例 @Provides @Singleton fun provideSqliteDatabase(context: Context): SQLiteDatabase { return SQLiteDatabase.openOrCreateDatabase( context.filesDir.path + "/app.db", null ) } }
3. 用接口统一封装资源(可选)
如果需要一个统一的类封装配置、Context、数据库,可以在Common模块定义接口,App模块实现后通过Hilt绑定:
// Common模块:定义接口 interface AppResources { val actionBarTitleColor: String val appContext: Context val sqliteDatabase: SQLiteDatabase } // App模块:实现接口 class AppResourcesImpl @Inject constructor( @ApplicationContext private val context: Context, private val sqliteDatabase: SQLiteDatabase ) : AppResources { override val actionBarTitleColor: String = AppConfig.ACTION_BAR_TITLE_COLOR override val appContext: Context = context override val sqliteDatabase: SQLiteDatabase = sqliteDatabase } // App模块:绑定接口与实现 @Module @InstallIn(SingletonComponent::class) abstract class AppBindingModule { @Binds @Singleton abstract fun bindAppResources(impl: AppResourcesImpl): AppResources }
4. 正确注入依赖
在任意模块的类中,通过构造注入获取所需资源:
// Common模块的TestClass class TestClass @Inject constructor( private val appResources: AppResources ) { fun whatever() { val color = appResources.actionBarTitleColor val db = appResources.sqliteDatabase } }
注意:Android组件(Activity/Fragment)需要添加
@AndroidEntryPoint注解才能使用Hilt注入;非Android类可以通过Hilt的EntryPoint获取实例,或者在被注入的类中通过构造注入传递。
关键注意事项
- 禁止手动创建Application实例:Application由系统初始化,手动
new会导致Context无效、内存泄漏等问题。 - 遵循Hilt模块规则:
@HiltAndroidApp必须在App模块,其他模块的Hilt模块通过@InstallIn指定合适的组件(如SingletonComponent)实现全局可用。 - 依赖倒置:让底层模块依赖抽象接口,而非具体实现,避免模块间的强耦合。
内容的提问来源于stack exchange,提问作者Diego Perez
相关产品推荐
相关产品推荐

