使用Hilt实现私有构造单例类的依赖注入适配问题
问题根因
两个核心问题直接触发了你遇到的编译报错和适配失败:
- 你给companion object里的
getInstance方法加@Inject注解是完全无效的:Kotlin的companion object编译后会生成独立的静态内部类,Dagger遇到这个注解会尝试对Companion类本身做注入,直接触发“不支持注入Kotlin对象”的编译错误。 - Hilt模块的作用域选错:你把模块绑定到
ActivityComponent,但数据层依赖需要全局单例,绑定到Activity组件会导致Activity重建时重复创建依赖实例,和你要的单例语义冲突。
适配方案(无需修改构造函数可见性)
全程保持RemoteDataSource、Repository的私有构造不变,按以下步骤调整即可解决问题:
- 移除所有加在companion object方法上的
@Inject注解,消除Dagger扫描Companion类的触发源 - 将Hilt模块的安装位置从
ActivityComponent调整为全局的SingletonComponent,匹配单例的生命周期 - 保留类内部的私有构造和静态实例获取方法,通过Hilt的
@Provides方法调用这个公开的静态获取方法完成依赖托管
修正后的代码示例
调整后的RemoteDataSource.kt
@Singleton class RemoteDataSource private constructor(private val jsonHelper: JsonHelper) { companion object { @Volatile private var instance: RemoteDataSource? = null // 移除原有的@Inject注解 fun getInstance(jsonHelper: JsonHelper): RemoteDataSource = instance ?: synchronized(this) { instance ?: RemoteDataSource(jsonHelper).apply { instance = this } } } fun getAllRemoteMovies(moviesCallback: LoadMoviesCallback) { moviesCallback.onAllMoviesReceived(jsonHelper.loadRemoteMovies()) } fun getAllRemoteTVShows(tvshowCallback: LoadTVShowCallback) { tvshowCallback.onAllTVShowsReceived(jsonHelper.loadRemoteTVShows()) } interface LoadMoviesCallback { fun onAllMoviesReceived(moviesResponses: ArrayList<MovieItem>) } interface LoadTVShowCallback { fun onAllTVShowsReceived(tvshowResponses: ArrayList<TVShowItem>) } }
调整后的Repository.kt
@Singleton class Repository private constructor(private val remoteDataSource: RemoteDataSource) : DataSource { companion object { @Volatile private var instance: Repository? = null // 移除原有的@Inject注解 fun getInstance(remoteDataSource: RemoteDataSource): Repository = instance ?: synchronized(this) { instance ?: Repository(remoteDataSource).apply { instance = this } } } override fun getAllRemoteMovies(): LiveData<ArrayList<MovieItem>> { val remoteMoviesResult = MutableLiveData<ArrayList<MovieItem>>() remoteDataSource.getAllRemoteMovies(object : RemoteDataSource.LoadMoviesCallback { override fun onAllMoviesReceived(moviesResponses: ArrayList<MovieItem>) { remoteMoviesResult.value = moviesResponses } }) return remoteMoviesResult } override fun getAllRemoteTVShows(): LiveData<ArrayList<TVShowItem>> { val remoteTVShowsResult = MutableLiveData<ArrayList<TVShowItem>>() remoteDataSource.getAllRemoteTVShows(object : RemoteDataSource.LoadTVShowCallback { override fun onAllTVShowsReceived(tvshowResponses: ArrayList<TVShowItem>) { remoteTVShowsResult.value = tvshowResponses } }) return remoteTVShowsResult } }
调整后的Hilt模块
@Module // 从ActivityComponent改为SingletonComponent,匹配全局单例生命周期 @InstallIn(SingletonComponent::class) object RemoteDataSourceModule { @Singleton @Provides // 给Context加上@ApplicationContext注解,避免内存泄漏 fun provideJsonHelper(@ApplicationContext context: Context): JsonHelper { return JsonHelper(context) } @Singleton @Provides fun provideRemoteDataSource(jsonHelper: JsonHelper): RemoteDataSource { // 直接调用类暴露的静态获取方法,全程不触碰私有构造函数 return RemoteDataSource.getInstance(jsonHelper) } @Singleton @Provides fun provideRepository(remoteDataSource: RemoteDataSource): Repository { // 直接调用类暴露的静态获取方法,全程不触碰私有构造函数 return Repository.getInstance(remoteDataSource) } }
适配说明
- 调整后因为Hilt的
@Singleton作用域绑定在全局SingletonComponent上,整个应用生命周期内只会调用一次对应的@Provides方法,类内部的双重检查锁逻辑会被正确触发,全局仅存在一个类实例,和你原本的单例逻辑完全一致。 - 整个过程没有将私有构造函数修改为公开,完全满足你的约束要求。
- 后续给ViewModel注入Repository时,只要在ViewModel的构造函数里加
@Inject,Hilt就会自动从依赖图里拿到对应的单例实例,不需要额外处理。
内容的提问来源于stack exchange,提问作者rijamyazid
相关产品推荐
相关产品推荐

