Android+Kotlin+Hilt:静态工具类依赖注入方案求解
解决Kotlin工具类依赖注入的最优方案
首先明确核心原则:带依赖的工具类不适合用静态类(object/companion object)实现,因为静态结构天生依赖全局状态,和依赖注入(DI)的实例化、解耦思想冲突,会导致初始化繁琐、测试困难、空指针风险等问题。下面针对你的需求给出两种方案,优先推荐第一种。
方案一:将工具类改为普通类,通过Hilt构造注入(推荐)
把AWUtils改成普通类,通过构造函数注入所需的AppSettings和Context,再用Hilt将其标记为单例(如果需要全局唯一实例),这样所有依赖都由Hilt自动管理,无需手动赋值。
实现代码
// 工具类:构造注入依赖 class AWUtils @Inject constructor( private val appSettings: AppSettings, @ApplicationContext private val context: Context // 注入全局ApplicationContext ) { fun setAmpersand2Yellow(text2Replace: String, target: String): String { return text2Replace.replace( target, "<span style=\"color:${appSettings.drawerFooterColor};\">$target</span>" ) } val isTablet: Boolean get() { return (context.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE } } // Hilt模块:提供单例的AWUtils实例 @Module @InstallIn(SingletonComponent::class) object AWUtilsModule { @Provides @Singleton fun provideAWUtils( appSettings: AppSettings, @ApplicationContext context: Context ): AWUtils { return AWUtils(appSettings, context) } } // 使用工具类的地方:直接注入实例 @AndroidEntryPoint class OtherClass @Inject constructor( private val awUtils: AWUtils ) { fun anyFunction() { val myStr = "whatever & test" val yellowStr = awUtils.setAmpersand2Yellow(myStr, "&") val isTablet = awUtils.isTablet } }
方案优势
- 完全符合DI思想,依赖由Hilt自动初始化和管理,无需手动赋值
- 单元测试友好:可以轻松mock
AppSettings和Context,验证工具类逻辑 - 无全局可变状态,避免空指针或未初始化的异常
- 代码耦合度低,工具类的依赖变更只需修改构造函数,调用方无需调整
方案二:保留object类,通过Application初始化(不推荐)
如果坚持要类似静态调用的体验,可以用object类,但仅在Application的onCreate中初始化一次依赖,避免每次调用都手动赋值。
实现代码
// 工具类:object单例,提供初始化方法 object AWUtils { private lateinit var appSettings: AppSettings private lateinit var context: Context // 仅在Application中调用一次初始化 fun initialize(appSettings: AppSettings, context: Context) { this.appSettings = appSettings this.context = context } fun setAmpersand2Yellow(text2Replace: String, target: String): String { // 确保已初始化,避免空指针 check(::appSettings.isInitialized) { "AWUtils未初始化,请先调用initialize方法" } return text2Replace.replace( target, "<span style=\"color:${appSettings.drawerFooterColor};\">$target</span>" ) } val isTablet: Boolean get() { check(::context.isInitialized) { "AWUtils未初始化,请先调用initialize方法" } return (context.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE } } // 在Application中完成初始化 @HiltAndroidApp class MyApp : Application() { @Inject lateinit var appSettings: AppSettings override fun onCreate() { super.onCreate() AWUtils.initialize(appSettings, applicationContext) } } // 使用时直接调用静态方法 @AndroidEntryPoint class OtherClass { fun anyFunction() { val myStr = "whatever & test" val yellowStr = AWUtils.setAmpersand2Yellow(myStr, "&") val isTablet = AWUtils.isTablet } }
方案缺点
- 存在全局可变状态,初始化时机依赖Application,若提前调用会抛出异常
- 单元测试需要手动初始化,灵活性远不如方案一
- 违背DI的解耦思想,工具类和依赖的耦合度较高
为什么不推荐静态类/companion object?
object类:Kotlin的object是饿汉式单例,无法通过构造注入依赖,只能手动设置字段,导致初始化时机不可控,容易出现空指针companion object:属于类的静态成员,Hilt无法为其注入依赖,因为DI是针对类的实例而非静态部分设计的,只能通过类的实例传递依赖,失去了静态调用的意义
内容的提问来源于stack exchange,提问作者Diego Perez
相关产品推荐
相关产品推荐

