本文是Assisted Injection With Dagger and Hilt博客的译文,英语好的同学可以直接阅读原文,不需要翻墙,文章中的源码也在原博客中。
以下是原文的翻译。
使用Dagger和Hilt辅助注入
本文通过学习辅助注入的用处、原理,以及如何通过Dagger的新的构建方式来给你的app添加辅助注入。
前言
使用Dagger进行依赖注入是Android社区的一个热门话题。Dagger和它的新的拓展-Hilt都是不断改进的开源项目,每天都有新的功能和提升加入。辅助注入(Assisted Injection)就是Dagger从2.31版本开始加入的新功能。
在这个教程中,你将会学习:
- 辅助注入是什么,以及为什么它会有用
- 在Dagger2.31版本之前如何通过AutoFactory使用辅助注入
- Dagger2.31+中辅助注入的工作原理
- 如何通过Hilt和ViewModels使用辅助注入
提示:本教程假设了你熟悉Android开发和Android Studio的使用。如果不是,请先阅读 Android开发入门
和 Android版Kotlin 教程。
本教程是Dagger系列文章中的一文,如果你不熟悉Dagger,请先阅读这些资源:
- Android中的Dagger2教程:进阶
- Android中的Dagger2教程:进阶2
- 从Dagger迁移到Hilt
- 全新的Dagger教程
现在让我们开始学习!
开始
首先通过点击本教程上方或者下方的Download Matrerails按钮下载最新的项目版本(译者注:请到原文链接中下载),然后在Android Studio打开,你将会看到下面的资源目录:
这是 AssistedGallery 项目的架构,你将通过这个项目学习辅助注入,构建(Build)并运行(Run)这个app你将会看到它是如何工作的。在App中你将会看到如下的内容:
提示: 在你的设备上,图片可能有所不同。这是因为应用使用placeimg.com服务,它提供了一个简单的接口,能够根据提供的尺寸和话题获取随机的图片。
现在项目搭建好了,让我们来看一下app的架构
AssistedGallery App架构
AssistedGallery是一个简单的应用,内部实现和使用了ImageLoader。
在看代码之前,先看一眼下面的图,其中描述了不同组件之间的依赖关系。当你要讨论依赖注入时,理解这个app主要组件之间的依赖关系是很必要的。
在这张图中,你可以看到:
ImageLoader
类:通过提供的URL下载图片加载到ImageView
ImageLoader
基于BitmapFetcher
实现,BitmapFetcher
用来处理从网络上获取的Bitmap
数据。这部分是怎么实现的在本教程中并不重要。- 访问网络和其他密集型的IO操作必须放到后台线程,所以
ImageLoader
依赖两个CoroutineDiapathcer
实例。 - 最后,表中还有一个通过不同的
ImageFilter
接口实现,来执行Bitmap
变换的操作。这部分过滤器的实现也不重要。
继续阅读以了解如何在代码中表示这一点。
ImageLoader 类
要了解ImageLoader
是如何工作的,先打开 bitmap包下的 ImageLoader.kt文件并查看代码。其中包含两个主要部分:
- 使用构造函数注入管理依赖项。
- 实现
loadImage
功能。
前面的图有助于了解如何实现构造函数注入。
使用构造函数注入管理依赖项
构造函数注入是在类中注入依赖项的好方法,这种方法会在你创建实例时进行注入,并使参数不可变。举个例子,如ImageLoader的主构造函数:
class ImageLoader constructor(
private val bitmapFetcher: BitmapFetcher, // 1
@Dispatchers.IO private val bgDispatcher: CoroutineDispatcher, // 2
@Dispatchers.Main private val uiDispatcher: CoroutineDispatcher, // 2
@DrawableRes private val loadingDrawableId: Int = R.drawable.loading_animation_drawable, // 3
private val imageFilter: ImageFilter = NoOpImageFilter // 4
) {
// ...
}
上面的代码有很多值得注意的地方:
ImageLoader
依赖一个BitmapFetcher
接口的实现类,作为它构造函数第一个接收的参数。像所有参数一样,它是private val,私有只读(译者注:非强制).- 你需要两种不同的
CoroutineDispatcher
实现。第一个用@Dispatchers.IO
注释,你可以使用它进行后台操作,例如访问网络或转换Bitmap
. 第二个用@Dispatchers.Main
注释,你可以使用它与 UI 进行交互。 - 前面的参数都是必传参数,
loadingDrawableId
是第一个可选参数,代表后台作业正在进行时要显示的Drawable
。 - 最后,还有一个可选的
ImageFilter
参数,用于转换从网络加载的Bitmap
。
注意:这里的可选参数意思是非强制传递参数,因为它有一个默认值。
实现 loadImage 方法
虽然这部分不是必要的,不过为了完整性,查看下面loadImage
实现的代码也是有用的:
class ImageLoader constructor(
// ...
) {
suspend fun loadImage(imageUrl: String, target: ImageView) =
withContext(bgDispatcher) {
// 1
val prevScaleType: ImageView.ScaleType = target.scaleType
withContext(uiDispatcher) {
// 2
with(target) {
scaleType = ImageView.ScaleType.CENTER
setImageDrawable(ContextCompat.getDrawable(target.context, loadingDrawableId))
}
}
val bitmap = bitmapFetcher.fetchImage(imageUrl) // 3
val transformedBitmap = imageFilter.transform(bitmap) // 4
withContext(uiDispatcher) {
// 5
with(target) {
scaleType = prevScaleType
setImageBitmap(transformedBitmap)
}
}
}
}
在这部分代码中,你可以:
-
使用
withContext
在后台线程的上下文中运行包含的代码。 -
切换到 UI 线程以在加载和转换
Bitmap
时设置对应的Drawble
. -
在后台线程的上下文中,从网络获取
Bitmap
的数据。 -
变换
Bitmap
. 由于这是一项开销很大的操作,因此需要在后台线程的上下文中执行它。 -
返回到 UI 线程以显示
Bitmap
.
现在,如何提供ImageLoader
所需要的依赖项并使用呢?
使用ImageLoader类
打开ui目录下的MainActivity.kt文件,查看以下代码:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
@Dispatchers.IO
lateinit var bgDispatcher: CoroutineDispatcher // 1
@Inject
@Dispatchers.Main
lateinit var mainDispatcher: CoroutineDispatcher // 2
@Inject
lateinit var bitmapFetcher: BitmapFetcher // 3
@Inject
lateinit var imageUrlStrategy: ImageUrlStrategy // 4
lateinit var mainImage: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainImage = findViewById<ImageView>(R.id.main_image).apply {
setOnLongClickListener {
loadImage()
true
}
}
}
override fun onStart() {
super.onStart()
loadImage()
}
fun loadImage() {
// 5
lifecycleScope.launch {
ImageLoader(
bitmapFetcher,
bgDispatcher,
mainDispatcher
)
.loadImage(imageUrlStrategy(), mainImage)
}
}
}
在代码中,可以看到
- 使用了
@Dispatchers.IO
作为限定符,用于注入后台线程的CoroutineDispatcher
。 - 使用
@Dispatchers.Main
作为限定符,修饰主线程的CoroutineDispatcher
。 - 注入一个
BitmapFetcher
. - 注入一个
ImageUrlStrategy
对象,它创建要下载图像的 URL。 - 使用所有依赖项创建一个
ImageLoader
实例,并将图像加载到ImageView
。
这些代码显然太多了,尤其是在使用依赖注入的时候。你真的需要将所有依赖项都注入MainActivity吗?
只注射你需要的
为了简化代码,你不需要将ImageLoader
所需要的全部依赖项都注入到MainActivity
.作为替代,你可以只注入ImageLoader
本身,让 Dagger 来完成困难的部分。
在di包中新建一个名为ImageLoaderModule.kt的文件,编写如下代码:
@Module