最新的 Hilt 使用

基本概念

  • 用 @EntryPoint 表示当前类需要被注入
  • 用 @XXXComponent 表示其作用范围域,InstallIn,就是安装到的意思。那么@InstallIn(ActivityComponent::class),就是把这个模块安装到Activity组件当中。既然是安装到了Activity组件当中,那么自然在Activity中是可以使用由这个模块提供的所有依赖注入实例。另外,Activity中包含的Fragment和View也可以使用,但是除了Activity、Fragment、View之外的其他地方就无法使用了。
  • @module 表示这是一个用于提供依赖注入实例的模块 @Bind 表示可提供依赖 @Provides 表示提供依赖
  • @Qualifier注解的作用就是专门用于解决我们目前碰到的问题,给相同类型的类或接口注入不同的实例
    • 如果两个 activity 用不同的实例呢?
    • Component,从 Hilt 找依赖 然后注入给 需求依赖的地方。 将 @module 中提供的实例 传递给 @inject 标记的变量。

使用步骤

  1. Application 声明 @HiltAndroidApp,
  2. Android Class 声明 @AndroidEntryPoint, 如果非 Android Class , 声明 @EntryPoint。告诉hilt 当前类的被 @inject的变量需要被注入实例。
  3. hilt 看着 @inject 标注的变量, 去找其声明,如果其 构造函数有 @inject 就调用构造函数返回, 如果没有构造函数被 @inject , 就找@Module
  4. 每个 module 通过 @InstallIn 表示当前module能够给哪些 Classes 提供实例。 比如 SingletonComponent,就是全局class都从 这里取实例
  5. @Qualifier 告诉hilt, 如果 module 中有可以提供多个接口的实例,可以通过不同的 @Qualifier 取。
  6. 比如下面的例子就表示,创建 okhttpclient 需要 @InterceptorBASIC对应的实例。比如下面的例子就表示,创建 okhttpclient 需要 @InterceptorBASIC 对应的Interceptor实例。
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class InterceptorBASIC

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class InterceptorHEADERS

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    fun provideLogInterceptorOkHttpClient(
            @InterceptorHEADERS loggingInterceptor: Interceptor
    ): OkHttpClient {
        return OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build()
    }

    @Provides
    @InterceptorHEADERS
    fun provideLogInterceptorBASIC(): Interceptor {
        return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
    }


    @Provides
    @InterceptorBASIC
    fun provideLogInterceptorHEADERS(): Interceptor {
        return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS)
    }
}
复制代码
  1. viewModel 以及其成员变量不能持有 activity context

对一些概念说明一下

  1. @HiltAndroidApp // 启动 hilt 工作, 创建 applicationComponent, 让 hilt 能够给项目中的所有类提供依赖。

@HiltAndroidApp
class ExampleApplication : Application() { … }

  1. @inject 注入依赖,通过标记,表示其需要被注入依赖。
    1. @HiltAndroidApp 标记在application 当前应用需要依赖
    2. @AndroidEntryPoint 标记在四大组件(activity、service) 、Fragment、view 表示其需要依赖, 注意, 使用 @AndroidEntryPoint ,需要保证其依赖的所有class也都使用了此注解, 比如 Fragment如果使用@AndroidEntryPoint, 那么其 activity 也需要@AndroidEntryPoint。
    3. @HiltViewModel

@AndroidEntryPoint 会为每一个 Android Class 生成 component, component会接受依赖,而且具有传递性,具体见  Component hierarchy.

接着, 通过 @AndroidEntryPoint表示当前类需要注入, 然后通过 @Inject 给变量表示当前变量需要被注入实例。

Note:  1. Fields injected by Hilt cannot be private. Attempting to inject a private field with Hilt results in a compilation error.
3. 如果 hilt 提供的实例, 需要依赖其他实例,就需要Define Hilt bindings, 具体没讲,后面补充

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }
复制代码
  1. Hilt module

如果需要一个接口实现,或者三方库,我们不能在其构造函数中 @inject , 就需要通过 @module 组织后提供给 hilt。
1. 通过 @bind 提供接口实例。 比如上面的 AnalyticsService 是一个接口, 我们就可以 @module 标记的类中 提供一个抽象方法,具体如下

interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

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

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}
复制代码
  • bindAnalyticsService return type tells Hilt what interface the function provides instances of.
  • bindAnalyticsService parameter tells Hilt which implementation to provide.
    2. module 上的 @InstallIn(ActivityComponent::class) ,表示module中提供的所有实例能被项目中所有 activity 使用。
    3. 通过 @Provides 提供三方库的实例。
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}
复制代码
  1. Qualifier, 同一类型提供不同实例, 自定义 @Qualifier, 比如下面在 @provide 上面注解 @InterceptorBASIC 和 @InterceptorHEADERS 标记提供的不同实例, 在需要此实例的变量前使用不同的注解,表示需要不同的实例, 比如下面的例子就表示,创建 okhttpclient 需要 @InterceptorBASIC对应的实例。
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class InterceptorBASIC

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class InterceptorHEADERS

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    fun provideLogInterceptorOkHttpClient(
            @InterceptorHEADERS loggingInterceptor: Interceptor
    ): OkHttpClient {
        return OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build()
    }

    @Provides
    @InterceptorHEADERS
    fun provideLogInterceptorBASIC(): Interceptor {
        return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
    }


    @Provides
    @InterceptorBASIC
    fun provideLogInterceptorHEADERS(): Interceptor {
        return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS)
    }
}
复制代码
  1. 预制的 qualifers

Hilt 通过 @ApplicationContext 和 @ActivityContext 提供不同的实例, 比如下面的例子就表示 AnalyticsAdapter 需要 hilt 提供 activity 的 context。

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }
复制代码
  1. @InstallIn()

我们前面已经知道 Component 表示当前类需要被注入, 那Component 是怎么拿到实例的呢? 开发者 通过在 module上使用 @InstallIn  告诉 hilt, module 提供的这些实例可以被哪种

8.预制 qualifers

常见错误

  1. 如果被 @inject 的变量有基类, 不需要给基类声明 @AndroidEntryPoint
  2. Without an @inject constructor or an…..

使用不当造成的,比如

  1. viewmodel 中持有 context.

原因:
A module scoped to the ViewModelComponent cannot provide @ActivityContext Context because that would be a leak since ViewModels are retained across configuration changes and outlive the Activity. However if you only need a Context, not necessarily the activity one, then you can change your provider function to request for a Context with the @ApplicationContext qualifier.

  1. Module 提供的实例,比 @InstallIn 的生命周期长, 比如如下代码.
@Module
@InstallIn(SingletonComponent::class)
class TestModule {
    @Provides
    @Singleton
    fun provideTestSingleton(): TestSingleton {
        Log.d("heiheihei", "ApplicationModule provideTestSingleton")
        return TestSingleton()
    }
}
复制代码

上面的代码 提供 的 TestSingleton 实例是在 activity 中使用的, 如果我将 SingletonComponent 改成 ViewComponent 就会复现上面的问题, 由此说明上面的bug,就是由于 提供的 context 的module 作用域的问题。

作用域该怎么应对, 如下:
image.png
比如 singletonComponet 的 module 可以给箭头指向的所有 component 提供实例。 但是 viewComponet 就只有它自己能获得实例。

参考

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享