用了一周才搞明白Dagger/Hilt中的辅助注入,真正写起来其实很简单

本文详细介绍了Dagger/Hilt的辅助注入,解释了其概念、使用场景和历史演变。通过逐步指导,展示了如何进行普通类和ViewModel的辅助注入,包括多个参数的处理。同时,分享了在学习过程中遇到的问题和解决方案,为读者提供了一个清晰的实践指南。

什么是辅助注入

介绍辅助注入

辅助注入,如字面意思,就是辅助的依赖注入。这个辅助,其实是由开发者,也就是我们来辅助Hilt来进行依赖注入。当然这么看起来也是很懵,看一下下面的使用场景介绍应该就能懂个大概了。

辅助注入的使用场景

而辅助注入的使用场景呢?

我们都知道,依赖注入框架可以帮助我们创建实例对象。不过在使用Hilt(或者Dagger,作为一个Android开发,我在下文中都会称为Hilt)这个框架之前,我一直有一个困惑,那就是Hilt是怎么给我创建实例的。

别的时候都还好说,如果创建实例时,需要传入一个可变的var的参数。比如我要创建一个花的对象,需要在创建时就传入一个花的名字:

var name:String = "Rose"
val flower:Flower = FLower(name)

而交给Hilt要怎么创建呢?毕竟参数是可变的,Hilt怎么知道这个参数是什么呢?

@Inject
latinit var flower:Flower

在这里插入图片描述
这个时候就需要用到辅助注入了。

所以辅助注入的使用场景可以概括为:

  • 创建实例对象需要参数
  • 参数是可变的(不一定是var修饰的,val也可以,主要是想说这个参数不是固定的不变的,否则就能通过@Provide来创建了)

至于其他场景,比如不需要参数啊,参数是静态不变的,又可以根据不同的类型进行不同的处理,这里就不再赘述,可以看一下我的文章:

辅助注入的发展

辅助注入也经历过时代的发展,在Dagger的早期版本中,是通过@AutoFactory的形式进行辅助注入的。而在Dagger 2.31+版本之后,则是用@AssistedInject。对于Hilt也是一样,使用前一定要检查自己的依赖版本是否支持最新的Assisted Injection。

如何进行辅助注入

准备工作

和其他方式的依赖注入一样,准备工作分为两步。

导入依赖库

implementation "com.google.dagger:hilt-android:2.37"
kapt "com.google.dagger:hilt-android-compiler:2.37"

配置Application

@HiltAndroidApp
class MyApplication : Application()

记得把自定义的Application在Manifest配置一下。

普通类的辅助注入

定义一个带参数的类

在完成准备工作后,我们要先创建一个需要辅助注入的类,这个类的构造方法中包含一个String的参数

class ImageLoader @AssistedInject constructor(
    @Assisted
    private val imgUrlString: String
) {

    override fun toString(): String = imgUrlString
}

可以看到,与其他类型的依赖注入使用@Inject来标记构造方法不同,辅助注入使用的注解是@AssistedInject。此外,对于需要传入的参数,要用@Assisted来注解。很明显,@AssistedInject是告诉Hilt这个类要进行辅助注入,而@Assisted是告诉Hilt这个参数是需要传入的参数。

定义一个Factory接口

仅仅这样,Hilt也很难给我们直接创建ImagerLoader的实例对象,这里还需要一个辅助的工厂来生成ImageLoader的实例。

@AssistedFactory
interface ImageLoaderFactory {

    fun createImageLoader(imgUrlString: String): ImageLoader
}

在上面的代码中,创建了一个接口,并在接口上使用了AssistedFactory注解。在接口内有一个抽象方法,用于生成ImageLoader类的实例。这个抽象方法名是无所谓的,可以随便取。但是里面的参数,一定要与ImageLoader构造方法中的参数一一对应。最后,方法的返回类型为ImageLoader。到这里就可以进行辅助注入了。

进行辅助注入

我们选一个合适的入口点,我这里就是Activity。就像其他类型的依赖注入一样,要给Activity一个入口点的标记@AndroidEntryPoint,然后再定义一个factory的变量。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var imageLoaderFactory: ImageLoaderFactory 
}

通过@Inject注解,这个factory变量的创建就交给了Hilt,接着就是最后ImageLoader对象的创建。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val imageLoader = imageLoaderFactory
        .createImageLoaderFactory("https://dagge.dev/")
}

在代码中,调用了factory的create方法传入了所需要的参数,最后返回了ImageLoader的实例对象。到这里,就完成了一个需要动态可变参数的对象的辅助注入。

进阶:多个参数的辅助依赖注入

当构造函数内的参数不止一个时,如果参数类型各不相同,切记Factory中create方法中的参数要与其一一对应。

当参数类型有相同的时候,就需要通过别名来进行区分。

class MyDataService @AssistedInject constructor(
    dataFetcher: DataFetcher,
    @Assisted("server") serverConfig: Config,
    @Assisted("client") clientConfig: Config
) {}

@AssistedFactory
interface MyDataServiceFactory {
  fun create(
    @Assisted("server") serverConfig: Config,
    @Assisted("client") clientConfig: Config
  ): MyDataService
}

需要注意的是,Dagger官网中提到,此方法并不是一定有效。

ViewModel的辅助注入

我们知道,Hilt是Dagger的Android定制版,所以对于一些Android开发经常用到的组件就进行了优化,ViewModel就是这样。

准备工作

除了前面准备工作中导入依赖库和定义Application,辅助注入ViewModel时还需要做些额外的工作。

implementation "androidx.activity:activity-ktx:1.2.2"

其实就是多个库,这是Kotlin对Activity的支持,有了它就能在Activity中使用lateinit。

定义带参数的ViewModel

做完了以上的准备工作,就能开始上手了。首先就是要定义一个带参数的ViewModel。

class MyViewModel @AssistedInject constructor(
    @Assisted val name: String
) : ViewModel()

可以看到和普通类的定义是类似的,都是通过@AssistedInject注解修饰构造方法,通过@Assisted注解修饰要传入的参数。

定义Factory接口

@AssistedFactory
interface ViewModelFactory {
    fun createViewModel(name: String): MyViewModel
}

与普通类一样,也是定义一个Factory接口,使用AssistedFactory注解修饰,里面有一个createViewModel方法,方法中有要传入的参数,方法的返回类型为MyViewModel。

定义provideFactory方法

这里就是ViewModel与普通类不同的地方了,利用Kotlin函数即对象的特性,直接定义了一个方法,这个方法不在任何类或接口内。

fun provideFactory(
    assistedFactory: ViewModelFactory,
    name: String
): ViewModelProvider.Factory =
    object : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return assistedFactory.createViewModel(name) as T
        }

可以看到,方法有两个参数,一个是Factory接口,一个是ViewModel所需要传入的参数。方法最后返回了ViewModelProvider.Factory,这个就是最后创建ViewModel的关键。
在provideFactory方法中,则是直接创建了一个匿名类,类内又重写了create方法,返回类型为我们要创建的ViewModel。
在create方法中,又调用我们创建的Factory接口中的createViewModel方法,最终返回ViewModel。

进行辅助注入

和普通类一样,我们在Activity中进行ViewModel的辅助注入。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelFactory

    val viewModel: MyViewModel by viewModels {
        provideFactory(
            viewModelFactory,
            "Assisted Inject"
        )
    }
}

和普通类一样,要定义一个ViewModelFactory变量,加上@Inject注解,交给Hilt来创建Factory。与普通类不同的是,ViewModel可以通过Kotlin的特性直接lateinit,通过by viewModels{}就可以延迟初始化,而在viewModels{}中传入ViewModelProvider.Factory即可进行ViewModel的创建,而我们之前定义的provideFactory方法的返回类型就是ViewModelProvider.Factory,所以直接将方法传入即可。
再给方法传入参数,第一个是Hilt创建的Factory对象,第二个是动态的参数,到这里ViewModel的辅助注入完成。
在这里插入图片描述

结尾

在学习辅助注入的过程中我遇到很多问题,我是通过Google的SunFlower这个项目来学习Jetpack的,其中就用到了辅助注入。但是Sunflower项目中使用的还是旧版本的辅助注入,依赖库还是squreup那个版本的。我按照sunflower上的代码,看着Dagger官网的教程,导入的是Hilt最新的库,最后直接混乱,一直没法正常编译。

而且国内关于这方面的内容也很少,一直卡着没什么头绪,搞了快一周。最后偶然搜到了一篇国外的的教程,总算是解了我的困惑,其实使用起来不难,就是没有一个合适的教程而已。
所以我把那个教程翻译了一下,另外附上Dagger官网的翻译,都附在下面。

【译文】使用Dagger和Hilt辅助注入
【译文】Dagger辅助注入

另外提一嘴,Dagger在依赖库的管理上真的太混乱了。在上面我翻译的教程中使用的是Hilt的2.33-beta版。

implementation "com.google.dagger:hilt-android:2.33-beta"
kapt "com.google.dagger:hilt-android-compiler:2.33-beta"

而我刚开始使用的是我查到的Hilt的最新版2.37

implementation "com.google.dagger:hilt-android:2.37"
kapt "com.google.dagger:hilt-android-compiler:2.37"

我按照翻译的教程进行ViewModel的辅助注入时,编译时就会报错:

Hilt_MyApplication.java:20: 错误: 找不到符号
      return DaggerMyApplication_HiltComponents_SingletonC.builder()
             ^

而我把版本降为2.33-beta就能正常编译,真是日了狗了。最后发现,原来最新版本中,不再需要导入hilt-lifecycle-viewmodel库,要把下面的库删掉。

implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"

因为这个又耽误我好久。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值