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

本文是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
) {
   
  // ...
}

上面的代码有很多值得注意的地方:

  1. ImageLoader依赖一个BitmapFetcher接口的实现类,作为它构造函数第一个接收的参数。像所有参数一样,它是private val,私有只读(译者注:非强制).
  2. 你需要两种不同的CoroutineDispatcher实现。第一个用@Dispatchers.IO注释,你可以使用它进行后台操作,例如访问网络或转换Bitmap. 第二个用@Dispatchers.Main注释,你可以使用它与 UI 进行交互。
  3. 前面的参数都是必传参数,loadingDrawableId是第一个可选参数,代表后台作业正在进行时要显示的Drawable
  4. 最后,还有一个可选的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)
          }
        }
      }
}

在这部分代码中,你可以:

  1. 使用withContext在后台线程的上下文中运行包含的代码。

  2. 切换到 UI 线程以在加载和转换Bitmap时设置对应的Drawble.

  3. 在后台线程的上下文中,从网络获取Bitmap的数据。

  4. 变换Bitmap. 由于这是一项开销很大的操作,因此需要在后台线程的上下文中执行它。

  5. 返回到 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)
    }
  }
}

在代码中,可以看到

  1. 使用了@Dispatchers.IO作为限定符,用于注入后台线程的CoroutineDispatcher
  2. 使用@Dispatchers.Main作为限定符,修饰主线程的CoroutineDispatcher
  3. 注入一个BitmapFetcher.
  4. 注入一个ImageUrlStrategy对象,它创建要下载图像的 URL。
  5. 使用所有依赖项创建一个ImageLoader实例,并将图像加载到ImageView

这些代码显然太多了,尤其是在使用依赖注入的时候。你真的需要将所有依赖项都注入MainActivity吗?

只注射你需要的

为了简化代码,你不需要将ImageLoader所需要的全部依赖项都注入到MainActivity.作为替代,你可以只注入ImageLoader本身,让 Dagger 来完成困难的部分。

在di包中新建一个名为ImageLoaderModule.kt的文件,编写如下代码:

@Module
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值