Hilt 在Now In Android的应用

55 阅读4分钟

NowInAndroid学习系列 Now in Android !AndroidApp开发的最佳实践,让我看看是怎么个事?

Now in Android学习Compose是怎么切换主题

Now in Androd 大项目是怎么构建程序的

1、Hilt 依赖注入框架

Hilt 是 Google 推荐的依赖注入(DI)框架,基于 Dagger 并针对 Android 进行了简化。 Hilt 感觉用起来还是很不错的。有点像写Java的意思了,Spring也是依赖注入那一套。 那就让我们看看都有哪些注解。原理懂不懂的以后再说,现在我们就用起来先。

1.1 @HiltAndroidApp

应用的入口点,必须添加到自定义 Application 类,触发 Hilt 的代码生成。

@HiltAndroidApp
class NiaApplication : Application(), ImageLoaderFactory {
    @Inject
    lateinit var imageLoader: dagger.Lazy<ImageLoader>

    @Inject
    lateinit var profileVerifierLogger: ProfileVerifierLogger

    override fun onCreate() {
        super.onCreate()
        // Initialize Sync; the system responsible for keeping data in the app up to date.
        Sync.initialize(context = this)
        profileVerifierLogger()
    }

    override fun newImageLoader(): ImageLoader = imageLoader.get()
}

1.2 @AndroidEntryPoint

  • 作用:标记 Android 组件(如 Activity、Fragment、Service 等),使其支持依赖注入。
  • 支持组件:Activity、Fragment、View、Service、BroadcastReceiver。
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

1.3 依赖提供注解@Inject

  • 作用

    • 构造函数:标记可注入的类,Hilt 自动创建实例。
    • 字段/方法:直接注入已绑定的依赖。
//构造方法,注入
class MainActivityViewModel @Inject constructor(
   userDataRepository: UserDataRepository,
) : ViewModel()
//字段注入
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

   /**
    * Lazily inject [JankStats], which is used to track jank throughout the app.
    */
   @Inject
   lateinit var lazyStats: dagger.Lazy<JankStats>

1.4 @Module 和 @InstallIn

  • @Module:定义提供依赖的模块(类的容器)。

  • @InstallIn:指定模块安装到哪个 Hilt 组件(决定依赖的生命周期)。 @Provides 和 @Binds

  • @Provides:在模块中标记方法,返回具体实例。

    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder().build()
    }
    
  • @Binds:在模块中标记抽象方法,用于接口/抽象类绑定到实现。

    @Module
    @InstallIn(ActivityComponent::class)
    interface MyModule {
        @Binds
        fun bindRepository(impl: RepositoryImpl): Repository
    }
    

    我觉得这些应该够用了。咱们去看看Now In Android 里具体是怎么依赖注入的

    2 、Now In Android database注入实践

    2.1 di Dao

@Module
@InstallIn(SingletonComponent::class)
internal object DaosModule {
   @Provides
   fun providesTopicsDao(
       database: NiaDatabase,
   ): TopicDao = database.topicDao()

   @Provides
   fun providesNewsResourceDao(
       database: NiaDatabase,
   ): NewsResourceDao = database.newsResourceDao()

   @Provides
   fun providesTopicFtsDao(
       database: NiaDatabase,
   ): TopicFtsDao = database.topicFtsDao()

   @Provides
   fun providesNewsResourceFtsDao(
       database: NiaDatabase,
   ): NewsResourceFtsDao = database.newsResourceFtsDao()

   @Provides
   fun providesRecentSearchQueryDao(
       database: NiaDatabase,
   ): RecentSearchQueryDao = database.recentSearchQueryDao()
}

@Module 作用:标记该对象是一个 Dagger 模块,用于提供依赖项。 说明:模块类会通过 @Provides 或 @Binds 方法定义如何创建和提供依赖实例。 @InstallIn(SingletonComponent::class) 作用:指定该模块安装在 Hilt 的 SingletonComponent 中,表示其中提供的依赖具有应用单例生命周期。 说明:这意味着这些 DAO 实例在整个应用程序生命周期中只会被创建一次,并保持到应用结束。 :标注在方法上,表示该方法可以提供一个依赖项实例。 说明: 每个 @Provides 函数都返回一个需要被注入的对象(如 TopicDao, NewsResourceDao 等)。 参数(如 database: NiaDatabase)由 Hilt 自动解析并注入。 访问权限 internal:仅在当前模块内可见,避免暴露给其他模块。

2.2 di Database

@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
   @Provides
   @Singleton
   fun providesNiaDatabase(
       @ApplicationContext context: Context,
   ): NiaDatabase = Room.databaseBuilder(
       context,
       NiaDatabase::class.java,
       "nia-database",
   ).build()
}

@ApplicationContext 是一个注入限定符,用于获取 Application 的 Context。 它通常与 Hilt 的 @Provides 方法一起使用,确保注入正确的 Context 类型。 适用于需要长期持有 Context 的场景,有助于避免内存泄漏。这样实例化的时候,就把Appcliation的Context 注入进去了。

2.3 这里有一个问题,Dao里的 NiaDatabase 是怎么注入进去的?

** Hilt 会查找 NiaDatabase 类型的提供者:** 在你的项目中,NiaDatabase 实例是由另一个模块 DatabaseModule.kt 中的 providesNiaDatabase 提供的。 该方法使用了 @Provides + @Singleton 注解,并被安装在 SingletonComponent 上,因此 Hilt 知道如何获取它。

注入方式: 当你在 DaosModule 的 @Provides 方法中声明 database: NiaDatabase 参数时,Hilt 会自动将之前提供的 NiaDatabase 单例实例注入进来。
DAO 创建过程: 每个 DAO(如 NewsResourceDao)都是通过调用 NiaDatabase 的相应方法创建的,例如 database.newsResourceDao()。 这些 DAO 实例由 Hilt 自动管理生命周期,并确保在整个应用中正确共享。

2.4 在数据仓库里注入使用,直接使用,数据仓库也需要di一下,

@Binds 接口绑定到实现类。

@Binds
internal abstract fun bindsTopicRepository(
    topicsRepository: OfflineFirstTopicsRepository,
): TopicsRepository
internal class OfflineFirstTopicsRepository @Inject constructor(
    private val topicDao: TopicDao,
    private val network: NiaNetworkDataSource,
) : TopicsRepository {

    override fun getTopics(): Flow<List<Topic>> =
        topicDao.getTopicEntities()
            .map { it.map(TopicEntity::asExternalModel) }

    override fun getTopic(id: String): Flow<Topic> =
        topicDao.getTopicEntity(id).map { it.asExternalModel() }

    override suspend fun syncWith(synchronizer: Synchronizer): Boolean =
        synchronizer.changeListSync(
            versionReader = ChangeListVersions::topicVersion,
            changeListFetcher = { currentVersion ->
                network.getTopicChangeList(after = currentVersion)
            },
            versionUpdater = { latestVersion ->
                copy(topicVersion = latestVersion)
            },
            modelDeleter = topicDao::deleteTopics,
            modelUpdater = { changedIds ->
                val networkTopics = network.getTopics(ids = changedIds)
                topicDao.upsertTopics(
                    entities = networkTopics.map(NetworkTopic::asEntity),
                )
            },
        )
}