Jetpack Hilt依赖注入

2021-08-10/2022-06-10

Android 依赖项注入

什么是依赖项注入

类通常需要引用其他类。例如,Car 类可能需要引用 Engine 类。这些必需类称为依赖项,在此示例中,Car 类依赖于拥有 Engine 类的一个实例才能运行。

类可通过以下三种方式获取所需的对象:

  1. 类构造其所需的依赖项。在以上示例中,Car 将创建并初始化自己的 Engine 实例。
  2. 从其他地方抓取。某些 Android API(如 Context getter 和 getSystemService())的工作原理便是如此。
  3. 以参数形式提供。应用可以在构造类时提供这些依赖项,或者将这些依赖项传入需要各个依赖项的函数。在以上示例中,Car 构造函数将接收 Engine 作为参数。

第三种方式就是依赖项注入!使用这种方法,您可以获取并提供类的依赖项,而不必让类实例自行获取。

而Hilt可以提供自动的依赖项注入而不用自己手动去new

举个例子:

在 Android 开发者官网有一张关于 MVVM 架构的示意图,如下图所示:

一般我们自己去实现MVVM架构时,需要在Activity中创建ViewModel的实例,在ViewModel中创建Repository的实例,而使用Hilt后,就可以让Hilt自动为我们提供这个依赖项的实例

引入Hilt

第一步,在项目根目录的 build.gradle 文件中配置 Hilt 的插件路径:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

接下来,在 app/build.gradle 文件中,引入 Hilt 的插件并添加 Hilt 的依赖库:

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

复制代码

这里同时还引入了 kotlin-kapt 插件,是因为 Hilt 是基于编译时注解来实现的,而启用编译时注解功能一定要先添加 kotlin-kapt 插件。如果用 Java 开发项目,可以不引入这个插件,同时将添加注解依赖库时使用的 kapt 关键字改成 annotationProcessor 即可。

最后在当前项目中启用 Java 8 的功能,编辑 app/build.gradle 文件,并添加如下内容:

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

简单使用

1.自定义一个 Application 加上一个 @HiltAndroidApp

@HiltAndroidApp
class LogApplication : Application() {}

将 MyApplication 注册到 AndroidManifest.xml 文件

2.在要使用依赖注入的Activity或Fragment上加上@AndroidEntryPoint注解

3.给要依赖注入的对象添加@Inject注解

//在后台,Hilt 将使用自动生成的 LogsFragment 依赖项容器中内置的实例在 onAttach() 生命周期方法中填充这些字段。
@Inject lateinit var logger: LoggerLocalDataSource

4.告诉Hilt从哪里去获取要自动依赖注入的实例

//将实例的作用域限定为 application 容器的注解是 @Singleton。该注解将使 application 容器始终提供相同的实例
@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) {...}

接口的依赖注入

假如 有接口LoggerDataSource并且LoggerLocalDataSource是实现它的

//将实例的作用域限定为 application 容器的注解是 @Singleton。该注解将使 application 容器始终提供相同的实例
@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao):LoggerDataSource{...}

实现

@Inject lateinit var logger: LoggerDataSource

的依赖注入

新建一个类

@InstallIn(SingletonComponent::class)
@Module
abstract class LoggingDatabaseModule {
    @Singleton
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

给相同类型注入不同的实例

可能一个接口有多个实现类,可以使用标识符

@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

//由于 LoggerDataSource 的不同实现的作用域限定为不同的容器,因此我们不能使用同一个模块
// LoggerInMemoryDataSource 的作用域限定为 Activity 容器,
// 而 LoggerLocalDataSource 的作用域限定为 Application 容器。
@InstallIn(SingletonComponent::class)
@Module
abstract class LoggingDatabaseModule {

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

//@InstallIn(ActivityComponent::class)表明只能注入到Activity Fragment 和View中
@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

    @InMemoryLogger
    @ActivityScoped
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}
//要将某个类型的作用域限定为 Activity 容器,我们需要为该类型添加 @ActivityScoped 注解:
@ActivityScoped
class LoggerInMemoryDataSource @Inject constructor() : LoggerDataSource {...}

//将实例的作用域限定为 application 容器的注解是 @Singleton。该注解将使 application 容器始终提供相同的实例
@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao):LoggerDataSource{...}

使用时:

//请注意,LoggerLocalDataSource 的实例与我们在 LogsFragment 中所用的实例相同,因为该类型的作用域限定为 application 容器。
// 但是,AppNavigator 的实例与 MainActivity 中的实例不同,因为我们尚未将其作用域限定为相应的 Activity 容器。
@InMemoryLogger
@Inject
lateinit var logger: LoggerDataSource

第三方类的依赖注入

告诉Hilt怎么去拿到这个第三方库类的实例即可

以Okhttp为例

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {

    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .build()
    }

}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var okHttpClient: OkHttpClient
    ...

}

Hilt内置组件和组件作用域

@InstallIn(ActivityComponent::class),就是把这个模块安装到 Activity 组件当中,另外,Activity 中包含的 Fragment 和 View 也可以使用.

如果想要在全程序范围内共用某个对象的实例,那么就使用 @Singleton。如果想要在某个 Activity,以及它内部包含的 Fragment 和 View 中共用某个对象的实例,那么就使用 @ActivityScoped。以此类推。

预置Qualifier

对于 Application 和 Activity 这两个类型,Hilt 给它们预置好了注入功能

class Driver @Inject constructor(val application: Application) {
}

class Driver @Inject constructor(val activity: Activity) {
}

这种写法编译将可以直接通过,无需添加任何注解声明。

ViewModel的依赖注入

对于 ViewModel 这种常用 Jetpack 组件,Hilt 专门为其提供了一种独立的依赖注入方式

在 app/build.gradle 文件中添加两个额外的依赖:

dependencies {
    ...
    implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
    kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
}
class MyViewModel @ViewModelInject constructor(val repository: Repository) : ViewModel() {
    ...
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    val viewModel: MyViewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) }
    ...

}

github仓库地址

OkAndGreat/android-hilt (github.com)

使用过程中可能会碰到的问题解决方案

Hilt升级到alpha03后报错 - 简书 (jianshu.com)

Ref:

https://developer.android.com/codelabs/android-hilt

https://juejin.cn/post/6902009428633698312


标题:Jetpack Hilt依赖注入
作者:OkAndGreat
地址:http://zhongtai521.wang/articles/2021/08/10/1628600856376.html

评论
发表评论