天天看点

Andorid Jetpack Hilt

前言

现代开发语言,低代码,减少开发中模板代码的编写越来越被一线技术开发所提倡,google 官方在这方面也下了很大的功夫推出jectpack 架构组件,而Hilt依赖注入就是一个减少样板代码编写的利器,并且与组件的生命周期绑定,让把更多的精力放在业务而不是模板代码编写上和维护上。

Hilt 依赖注入 用到的注解和类

注解 解释
@HiltAndroidApp @HiltAndroidApp 会触发 Hilt 的代码生成操作,会生成一个应用级依赖项容器,对外提供依赖
@EntryPoint “hilt管理代码与非hilt管理代码之间的边界它是代码首次进入 Hilt 所管理对象的图的位置”
@InstallIn 指定可以获取到依赖的范围
@AndroidEntryPoint 声明AndroidEntryPoint注释到Android支持的组件上,可以方便获取应用级依赖
EntryPointAccessors 访问入口点工具
@ApplicationContext 预定义限定符,提供application级别的context上下文依赖
@ActivityContext 预定义限定符,提供context上下文依赖
DFM (动态功能模块) google play上可以动态更新某一个单独模块的技术
@Inject 在需要获取依赖的类型,标示提供的依赖项
@WorkerInject WorkManager注入
@Assisted WorkManager注入
@Singleton 等 限定作用域(@Singleton、@ActivityRetainedScope、@ActivityScoped、@FragmentScoped、@ServiceScoped)
Hilt 模块 @Module 、@InstallIn 注解

解决的问题:

1.不能通过构造函数注入接口,因为hilt无法知道实现类,需要通过@Module主动告知hilt如果提供实现类

2.对于非自己开发的类,比如三方库(okHttp、retrofit等),需要通过@Module主动告知hilt 如何创建对象

解决思路:

添加@Module注解的类,被@Binds注释的函数用来告知 Hilt 如何提供某些类型的实例

示例:

@Module
@InstallIn(ActivityComponent::class)  依赖项目注入所有拓展自ActivityComponent的子类
abstract class AnalyticsModule {

//AnalyticsServiceImpl是接口的实现类
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
  
}      

说明:在 Hilt 模块内创建一个带有 @Binds 注释的抽象函数(@Binds 注释会告知 Hilt 在需要提供接口的实例时要使用哪种实现),

带有@Binds的函数会向 Hilt 提供以下信息:函数返回类型会告知 Hilt 函数提供哪个接口的实例。函数参数会告知 Hilt 要提供哪种实现。
@Provides

解决问题:

1.Retrofit、OkHttpClient、Room或者必须使用构建器模式创建实例,也同样无法使用构造器函数注入

解决思路:

@Provides注释函数会向 Hilt 提供以下信息

  1. 函数返回类型会告知 Hilt 函数提供哪个类型的实例。
  2. 函数参数会告知 Hilt 相应类型的依赖项。
  3. 函数主体会告知 Hilt 如何提供相应类型的实例。每当需要提供该类型的实例时,Hilt 都会执行函数主体。

示例:

@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)
  }
}      
为同一类型提供多个绑定

比如提供OKhttp依赖的两种不同的实现,一个添加日志拦截器,一个不添加日志拦截器,

解决问题:

1.以依赖项的形式提供同一类型的不同实现

解决思路:

您可以使用限定符为同一类型定义多个绑定

限定符是一种注释,当为某个类型定义了多个绑定时,您可以使用它来标识该类型的特定绑定。

示例步骤:

1.定义要由于为@Binds 或 @Provides 方法添加注释的限定符

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

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


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

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}      

调用示例:

系统类中,自定义类中,hiltModule中

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}      

最佳做法:

如果对某一个类型添加限定符@Qualifier,应向提供该依赖项的所有可能的方式添加限定符。避免遗漏造成注入错误的依赖项。

@Inject 告知注入位置,@ActivityContext 预定义限定符,系统默认提供

java注解可应用于形参(PARAMETER,)和local_paramter 本地变量

为 Android系统类生成默认的组件

@InstallIn 可以关联的组件

ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent 带有 @WithFragmentBindings 注释的 View
ServiceComponent Service

示例:

@Module
@InstallIn(xxxComponent::class)
class xxxModule{

  @Binds
  或
  @Provides

}      

因为 Hilt 直接从 ApplicationComponent 注入广播接收器.

作用域

解决问题:

  1. 限制依赖的使用范围,在限制范围内依赖只初始化一次。避免重复创建浪费资源
  2. 依赖只在某一个作用域有效。

解决思路:

前两个需要着重查看下,hilt提供的作用域。

Application ApplicationComponent @Singleton
View Model ActivityRetainedComponent @ActivityRetainedScope
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
Service ServiceComponent @ServiceScoped

hilt支持注入的类,Android类,四大组件viewModel,View

上面介绍在构造函数中不能注入的对象或者其他不属于应用的第三方类图的对象,通过build来构建。下面介绍在Hilt不支持的类中注入依赖项

对于不支持的类型,定义入口点绑定组件形式,然后通过类检索组件,获取组件中的类型

比如:ContentProvider 中要使用Hilt提供的依赖项

解决问题:

  1. 不支持的类想使用hilt提供依赖对象

解决思路:访问入口点

  1. 使用@EntryPoint注释 @InstallIn(安装类),定义接口及需要提供的对象引用。声明在不支持的类中

示例

class ExampleContentProvider : ContentProvider() {
  @EntryPoint
  @InstallIn(ApplicationComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }
  ...
}      

多模块应用中使用Hilt

DFM 如何设置依赖,通过在主模块中设置入口点(与非动态模块设置依赖方向相反),设置Dagger模块依赖主模块功能点 放入功能模块,功能模块使用drgger来构建。

手动添加依赖注入

  1. 首先在一个类中创建所有的依赖对象,并拼装起来使用
  2. 为了复用,将拼装的逻辑抽取到服务定位器中进行全局统一管理
  3. 为了不同的界面流程需要创建不同的对象依赖。将对象的创建放入服务定位器中,并且创建工厂方法来对外提供对象
  4. 对于不同的业务流程,需要创建不同的依赖容器,并且绑定界面(Activity/Fragment)生命周期,在onCreate()创建在对应的Destroy()销毁
  5. 特殊情况需要在配置变更后保留对象状态,需要使用状态容器,生命周期绑定依赖容器和ui界面组件推荐使用生命周期管理工具LifeCycle

优点:

  1. 依赖注入对于创建可扩展且可测试的Android应用是一项合适的技术
  2. 将容器作为在应用内不同部分共享各个类的实例的一种方式,以及使用工厂创建各个类实例的集中位置
  3. 手动依赖项注入要求您手动构造每个类及其依赖项,并借助容器和工厂重复使用和管理依赖项。

缺点:

  1. 应用变大,流程变多,不同的界面流程需要维护不同的容器创建销毁,工厂方法的维护。
  2. 将会编写大量的样板代码,很容易出错,生命周期管理操作不当会产生微小错误和内存泄露

    手动注入需要写模板代码,多模块依赖顶层模块构建对象,需要依赖到不同子模块。 依赖管理,依赖范围管理,依赖生命周期管理,依赖复用。需要考虑

问题

  1. 比方说,我有一个类A,我想通过构造函数或者传递参数方式注入类B应该怎么做?
  2. 比方说,程序代码需要用到一个外部库Retrofit或者OkHttp对象或者接口(控制翻转),怎么声明依赖关系。

总结:

hilt基于Dagger提供一套标准依赖注入做法,Hilt优化了Dagger依赖注入的功能,减少了样版代码的编写,依赖注入主要有两种方式,构造函数注入是最常用的,而参数注入使用优先级低于构造函数注入,对于接口,三方工具库等不能修改源码的依赖注入,需要创建Hilt模块,并使用@Module @Installin(依赖安装的类::class),@Provider(sdk的Builder构造),@Binds(接口,抽象类)注释解决依赖传递问题。

hilt用法xmind 脑图

Andorid Jetpack Hilt