您现在的位置是:首页 >学无止境 >Hilt在Android中的使用网站首页学无止境

Hilt在Android中的使用

慢慢_飞 2023-06-13 12:00:03
简介Hilt在Android中的使用

1、简介:Hilt 是 Android 颇具特色的依赖项注入库,可减少在项目中使用手动依赖项注入时产生的样板代码。
2、使用

1.我们需要为 Application 类添加 @HiltAndroidApp 注解

@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类可使用依赖项注入。application 容器是应用的父级容器,这意味着其他容器可以访问它提供的依赖项。

@HiltAndroidApp
class LogApplication : Application() {
    ...
}

2.为 Android 类添加 @AndroidEntryPoint 注解会创建一个沿袭 Android 类生命周期的依赖项容器

利用 @AndroidEntryPoint,Hilt 可创建附着于 LogsFragment 生命周期的依赖项容器,并能够将实例注入 LogsFragment

@AndroidEntryPoint
class LogsFragment : Fragment() {
    ...
}

3.对于要进行注入的字段,我们可以利用 @Inject 注解让 Hilt 注入不同类型的实例

注意:由 Hilt 注入的字段不能是私有字段

@AndroidEntryPoint
class LogsFragment : Fragment() {

    @Inject lateinit var logger: LoggerLocalDataSource

    ...
}

4.告知 Hilt 如何通过 @Inject 提供依赖项

如要告知 Hilt 如何提供类型的实例,请向要注入的类的构造函数添加 @Inject 注解。

class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) {
    ...
}

5.将实例的作用域限定为容器

Android 类生成的组件作用域
ApplicationSingletonComponent@Singleton
ViewModelViewModelComponent@ViewModelScoped
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
ServiceServiceComponent@ServiceScoped

@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) {
    ...
}

6.Hilt 模块

有时,类型不能通过构造函数注入。发生这种情况可能有多种原因。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以使用 Hilt 模块向 Hilt 提供绑定信息

Hilt 模块是带有@Module 和 @InstallIn 注解的类。@Module 会告知 Hilt 这是一个模块,而 @InstallIn 会通过指定 Hilt 组件告知 Hilt 绑定在哪些容器中可用

6.1创建模块

@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {
}

6.2使用 @Provides 提供实例

我们可以在 Hilt 模块中为函数添加 @Provides 注解,告知 Hilt 如何提供无法通过构造函数注入的类型。

每当 Hilt 需要提供相应类型的实例时,都会执行带有 @Provides 注解的函数的函数主体。带有 @Provides 注解的函数的返回值类型会告知 Hilt 绑定的类型,即如何提供该类型的实例。函数参数是该类型的依赖项。

在 Kotlin 中,仅包含 @Provides 函数的模块可以是 object 类。通过这种方式,提供程序会得到优化。

@Module
object DatabaseModule {

    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
        return database.logDao()
    }
}

6.3使用 @Binds 注入接口实例

Hilt 模块不能同时包含非静态和抽象绑定方法,因此您不能将 @Binds 和 @Provides 注解放在同一个类中(A @Module may not contain both non-static and abstract binding methods)

一个抽象函数会返回我们告知 Hilt 的接口(即 AppNavigator),而函数参数是该接口的实现(即 AppNavigatorImpl

@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}

6.4限定符

如要告知 Hilt 如何提供同一类型的不同实现(多个绑定),您可以使用限定符

6.4.1 定义注解

package com.example.android.hilt.di

@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

6.4.2对实现添加注解

package com.example.android.hilt.di

@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {

    @DatabaseLogger
    @Singleton
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

    @InMemoryLogger
    @ActivityScoped
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

6.4.3通过注解选中实现方式

@AndroidEntryPoint
class LogsFragment : Fragment() {

    @InMemoryLogger
    @Inject lateinit var logger: LoggerDataSource
    ...
}

@AndroidEntryPoint
class ButtonsFragment : Fragment() {

    @InMemoryLogger
    @Inject lateinit var logger: LoggerDataSource
    ...
}

3、原理分析

3.1通过注解处理器生成hilt相关文件

3.2通过插件将super类替换

plugin主要代码地址:

/Users/xxx/.gradle/caches/modules-2/files-2.1/com.google.dagger/hilt-android-gradle-plugin/2.28-alpha/eb33a043b2bbdc7cdee3c851d0f8532bfd3645a5/hilt-android-gradle-plugin-2.28-alpha-sources/dagger/hilt/android/plugin/

查看AndroidEntryPointClassTransformer.kt主要代码:

private fun transformClass(clazz: CtClass): Boolean {
    if (ANDROID_ENTRY_POINT_ANNOTATIONS.none { clazz.hasAnnotation(it) }) {
      // Not a Android entry point annotated class, don't do anything.
      return false
    }

    // TODO(danysantiago): Handle classes with '$' in their name if they do become an issue.
    val superclassName = clazz.classFile.superclass
//将标有@AndroidEntryPoint和@HiltAndroidApp的父类替换为Hilt_XXX
    val entryPointSuperclassName =
      clazz.packageName + ".Hilt_" + clazz.simpleName.replace("$", "_")
    logger.info(
      "[$taskName] Transforming ${clazz.name} to extend $entryPointSuperclassName instead of " +
        "$superclassName."
    )
    clazz.superclass = classPool.get(entryPointSuperclassName)
    transformSuperMethodCalls(clazz, superclassName, entryPointSuperclassName)
    return true
  }

3.3打开dex文件查看MainActivity可以看到其父类是Hilt_MainActivity

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。