使用Hilt完成依赖注入,让你的安卓代码层次有几层楼那么高(一)

依赖注入为何物

  既然你已经点进来了,想必已经知道什么是依赖注入了吧,什么?!你还不知道什么是依赖注入吗,那你更要继续往下读了。

首先,来看看依赖注入的定义:

依赖注入就是参数传递

test1.png

  噢噢,我的老伙计,希望你并没有因为这个简单的定义而直接离开本文,我当然知道参数传递是每一位入门级程序员都掌握的基本能力,但为什么要用其他工具来完成这个参数传递呢,因为随着项目的膨胀,项目组件的不断扩大,组件之间的耦合关系也会愈发复杂,我们用一个实际例子来看看

项目膨胀导致的参数传递问题

  假设我有一个这样的场景,我需要构造一个车子,车子又有其他零件,零件又有子零件。

/**
 * 车子
 */
class Car(
    private val steeringWheel: SteeringWheel,
    private val wheel: Wheel
)

/**
 * 方向盘
 */
class SteeringWheel(private val color:String)
/**
 * 轮子
 */
class Wheel(private val steel: Steel)

/**
 * 钢圈
 */
class Steel(private val circle:Int)

  那么我要如何构造这个车子呢,代码如下(多个场景表示在项目中存在多处需要构造Car的地方):

//模拟场景1
fun test1(){
    Car(SteeringWheel("红色"), Wheel(Steel(30)))
}

//模拟场景2
fun test2(){
    Car(SteeringWheel("蓝色"), Wheel(Steel(40)))
}

//模拟场景3
fun test3(){
    Car(SteeringWheel("黑色"), Wheel(Steel(50)))
}

  All right!所有的一切都没问题,项目正常上线,然后某天项目经理跟你说:嘿,这位码农,麻烦给钢圈加个花边吧。你按捺住内心想打人的冲动,然后打开AS,给钢圈加一个属性,不出意外的是,项目出现了一大堆报错,原来是因为汽车包含了轮子,轮子包含了花边,于是所有构造汽车的代码都报错了,于是你修改了代码,变成了如下:

/**
 * 钢圈
 */
class Steel(private val circle:Int,private val lace:String)

//模拟场景1
fun test1(){
    Car(SteeringWheel("红色"), Wheel(Steel(30,"花边1")))
}

//模拟场景2
fun test2(){
    Car(SteeringWheel("蓝色"), Wheel(Steel(40,"花边2")))
}

//模拟场景3
fun test3(){
    Car(SteeringWheel("黑色"), Wheel(Steel(50,"花边3")))
}

  加班到12点的你终于确认了所有的代码都被修改,提交测试上线,但是回到床位后的你依然战战兢兢的思考着今天的问题,万一下一次项目经理又要你添加其他属性呢,一想到又要加班熬夜你迅速起身疯狂百度,于是你了解到了工厂模式

  于是你又起床新增了一个Car的工厂接口

interface CarFactory {
    fun build():Car
}

object CarAFactory:CarFactory{
    override fun build(): Car {
        return Car(SteeringWheel("红色"), Wheel(Steel(30,"花边1")))
    }

}

object CarBFactory:CarFactory{
    override fun build(): Car {
        return Car(SteeringWheel("蓝色"), Wheel(Steel(40,"花边2")))
    }

}

object CarCFactory:CarFactory{
    override fun build(): Car {
        return Car(SteeringWheel("黑色"), Wheel(Steel(50,"花边3")))
    }

}

//模拟场景1
fun test1(){
    CarAFactory.build()
}

//模拟场景2
fun test2(){
    CarBFactory.build()
}

//模拟场景3
fun test3(){
    CarCFactory.build()
}

  情况看起来好了很多,调用端已经没有具体的构造代码了,一切的构造都交给了工厂类,但是一旦这些类的属性膨胀起来后,会出现许许多多的工厂类,甚至工厂类本身都会出现属性配置,调用端的代码依然需要手动去修改,也许你进一步学习了抽象工厂,但是这些手段总给你一种感觉:面多了加水,水多了加面。此时的你迫切希望有一种更加解耦,更加无侵入性的手段帮你去实现对象的构建。

test2.png

  因此,假设有一个容器,他内部定义了如何构建各个零部件的构建方法,我们只需要告诉容器我需要什么,容器就会自动返回成品,因此我们也不需要手动去实现工厂类,那该多好?

   因此,我隆重推荐谷歌Jetpack里面的组件------Hilt

为什么是Hilt?

  如果你之前了解过安卓端依赖注入相关的框架,那么你肯定听过Okio,Dagger2,Hilt这三个框架,那么为什么选择Hilt呢,我的理由如下:

  1. Hilt基于Dagger2封装的框架,即在Dagger2的基础上加了一些安卓的场景化配置,也就是说,使用Hilt可以完成Dagger2的工作,同时避免了安卓场景下的部分模板代码。
  2. Hilt是Jetpack组件,和其他Jetpack组件协作良好,例如ViewModel,Compose,WorkManager都可以和Hilt无缝协作。

Hilt的初体验

给顶层的build.gradle添加插件

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

给所有使用Hilt的Module对应的gradle添加如下配置:

apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

给项目的Application添加注解@HiltAndroidApp,添加这个注解的含义是给Application生成一个顶级容器,容器的生命周期跟随Application。

@HiltAndroidApp
class ExampleApplication : Application() { ... }

接下来,我们要给对应的android组件提供依赖(也就是对组件完成参数传递的),使用注解@AndroidEntryPoint,以Activity为例。

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }

  然后,我们就可以给Activity注入我们想要的依赖了,例如注入一个Adapter,使用@Inject注释添加到我们想要注入的属性上面(切记,被注入的属性不能是private否则会报错)。

@AndroidEntryPoint
class ExampleActivity:AppCompatActivity() {
    
    //注入,不用手动实例化
    @Inject
    lateinit var analyticsAdapter: AnalyticsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //直接调用对象,没有实例化代码
        analyticsAdapter.analytics()
    }

}

class AnalyticsAdapter{

    fun analytics(){}

}

  非常神器,我们并没有看到adapter有被初始化的地方了,因为Hilt帮我们完成了初始化,也就是帮我们完成了Adapter的依赖注入!

image.png

  然而当你满心欢喜的尝试去运行上面这段代码的时候,崩溃了!错误是analyticsAdapter并没有被正确实例化,这是什么原因呢,道理很简单:我们只是告诉了Hilt,我们需要一个AnalyticsAdapter,但是Hilt却并不知道AnalyticsAdapter该如何创建,解决问题也很简单,我们告诉Hilt如何创建它不就好了。

  我们给AnalyticsAdapter的构造方法添加一个@Inject注释,告诉Hilt我们用空的构造方法来构建AnalyticsAdapter。

image.png

  代码能运行了!

  同时细心的发现代码左侧出现了两个小图标,这是一开始添加的插件的功能,点击一下AnalyticsAdapter对象左侧有红色向上箭头的图标,会发现跳转到了AnalyticsAdapter的构造函数;点击AnalyticsAdapter构造函数左侧的3个灰方块组成的小图标,则跳转到了AnalyticsAdapter对象。

  上诉操作说明了当我们点击被注入的对象左侧的小图标的时候,实际上我们是在尝试“查找注入的来源”,当我们点击构造函数左侧的小图标的时候,实际上我们是在尝试“查找注入的去向”,通过这两个小图标我们可以快速查找到当前的对象是如何被注入的。

  此刻,项目经理出现了,让我们添加某个功能,体现在代码中则是,Adapter需要外部传入一个对象,于是我们新增一个成员属性到AnalyticsAdapter的构造函数中去。

image.png

  运行程序,不出意外的崩溃了。复盘原因,非常简单,因为我们告诉了Hilt如何创建AnalyticsAdapter,即使用它的带参构造函数去构建,但是Hilt在调带参构造函数的时候,发现自己不懂如何创建NetworkService。

  参考当初构建AnalyticsAdapter的方式,我们继续给NetworkService的构造参数也添加一个@Inject注解,熟悉的图标又出现了,Hilt这回懂得了如何正确创建NetworkService。

image.png

  运行代码,正确!好的,刚才给Adapter新增了一个成员属性,让我们再看看Activity的代码,完全没有发生变化。

image.png

  我们通过了Hilt来帮我们完成了参数的传递(注意,只是辅助完成了这个过程并不是消灭了参数传递),对于Activity来说,他只是需要Adapter,他并不在乎Adapter的内部构建过程,所以Adapter内部再怎么发生剧烈的变化,Activity的代码都是不用去修改的,非常的完美。

课后小练习

下面提供两个小练习让你去体验使用Hilt实现依赖注入的低耦合性

1.给AnalyticsAdapter扩充第二个成员变量,类型为你自己定义的类(可以参考NetworkService)

2.给NetworkService增加一个成员变量

好了,本文到此就结束,关于Hilt的话题我们才刚刚起步,还有许多问题等待着我们去解决,例如如何注入一个接口?那些我们不能修改代码的第三方类如何注入(因为不能修改构造函数)?如何保证注入的都是同一个对象(实现单例)?那么下期我将更详细地逐一解决这些问题。

猜你喜欢

转载自juejin.im/post/7123148433367498766