快速上手Google 提供的依赖注入框架 「Hilt」

1,639 阅读4分钟

背景

为什么要使用依赖注入

现在大多数应用已经通过 依赖注入 的方式来将依赖的类对象传递进来,从而提高代码扩展性,开发过程中可以灵活的替换依赖的类。

依赖注入 是什么?

在外部创建依赖的类对象,将其通过接口参数传递给类(需求方)使用。

Google提供的依赖注入框架 Hilt

在我们平时的项目中,类对象的创建和依赖注入基本涉及几十、上百个类,非常复杂。这些动作自己完成容易陷入吃力不讨好(费力易错)的境地。所以 Google 提供了依赖注入框架「Hilt」。通过 「hilt」支持的方式配置需要创建的类对象、类与类之间的依赖关系,「Hilt」就可以自动创建对象、管理对象的生命周期、依赖注入。

怎么快速上手

回顾当初学习依赖注入的过程, 我带着以下的问题慢慢的熟悉了 Hilt

  • 怎么告诉「Hilt」当前变量需要被提供实例
  • 怎么告诉「Hilt」该去哪里拿实例
  • 怎么告诉「Hilt」产生的实例该不该以单例的形式提供

从使用的角度上讲,「Hilt」通过解析注解识别并解决上面的问题。所以,使用「Hilt」其实很大程度上是学习其提供的各种注解。

那么「Hilt」提供了哪些注解, 我们又该如何根据自己的需求来选择。

使用步骤

@HiltAndroidApp: 初始化:自定义 Application 为其添加

```
@HiltAndroidApp
class MainApplication : Application()
```

@HiltAndroidAp、@HiltViewModel、AndroidEntryPoint : Android Class需要被注入实例

  • Application ---> @HiltAndroidApp
  • ViewModel ---> @HiltViewModel
  • Activity ---> @AndroidEntryPoint
  • Fragment ---> @AndroidEntryPoint

以 Activity 为例,建立依赖. @AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var analytics: AnalyticsAdapter } 注意

  • 如果非 Android Class,声明 @EntryPoint.
  • @Inject 标识的变量不能使用 private.
  • 如果使用了 @AndroidEntryPoint, 其依赖的类也需要添加此注解, 比如 Fragment, 那么其 HostActivity 也需要。

@Inject: 当前变量需要被注入

「Hilt」会找其类声明,其构造函数如果被 @inject,那么就调用构造函数返回.比如:

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { }

有点开发经验的同学就会说,我们平时用的三方库,比如 Retrofit 就没办法给构造函数添加@Inject, 那该怎么办呢? ---> @Module

@Module@Provides: 当前类通过@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)
  }
}

@Qualifier : 如果 module 中有可以提供多个接口的实例,可以通过不同的 @Qualifier 取。

比如下面的例子就表示,创建 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)
    }
}

@Binds: 提供接口实例

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
}

以上代码表示,AnalyticsModule 通过@Binds标注bindAnalyticsService方法,表示当前其可提供接口 AnalyticsService 的具体实现 AnalyticsServiceImpl, 其中的 bindAnalyticsService方法的返回值告诉表示可提供的实例, 其参数表示提供的实例。

补充

 Component hierarchy

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

@ApplicationCotext、@ActivityContext:预制的 qualifers

在项目中,我们经常会用 context, 我们通过以上两个注解,就可以拿到 ApplicationCotextActivityContext

@InstallIn()

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

如何选择作用域

image.png

通过这张图选择, 比如 singletonComponet 的 module 可以给箭头指向的所有 component 提供实例。 但是 viewComponet 就只有它自己能获得实例。

一些建议

  1. viewmodel 中不能持有 Activity Context. 因为 ViewModel 会在横竖屏切换时保存实例,从而导致内存泄漏

参考