浅入深出配合Demo理解如何使用 dagger、Hilt。搭配Model在Mvvm使用。

306 阅读10分钟

1. 【浅入深出理解 dagger、Hilt】 - 简介

什么是依赖注入

我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。

dagger、Hilt是什么?

dagger、Hilt都是Google提供的用于依赖注入的库,Dagger因为稍微难用,所以Google专门为我们提供了一个使用更简单的依赖注入库Hilt

这些库能给我带来什么好处?

我们就直接开门见山了,常用场景就是MainActivity下需要new一个MainModel,然后MainModel构造函数要传递一个MainParameter类,代码如下:

public class MainEntity {
    public MainEntity() {
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class MainActivity extends AppCompatActivity {

    private MainEntity mainEntity;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainEntity = new MainEntity();
        mainEntity.setName("名称");

        ((TextView) findViewById(R.id.tvName)).setText(mainEntity.getName());
    }
}

如果使用依赖注入会有什么好处?

    1. 在大项目里面,一个对象类会应用在很多模块类里面使用,假如对象类的某个构造函数修改,那么涉及到的模块类都要修改。使用依赖注入并不是完全解耦对象类和模块类,依赖注入里面有个工厂模式Module,当涉及到修改的时候,同样需要修改Module。达成的目的,是为了把影响降到最低
    1. 方便单元测试,若需要替换为网络测试类,只需要修改相应的Module就可以了

下一篇文章,让我们介绍dagger 在后续,会在仿企业项目中加入依赖注入,让大家更好了解依赖注入带来的好处。欢迎star关注!

2. 【浅入深出理解 dagger、Hilt】 - dagger无参依赖注入

上一篇写了简单的传统java代码,那么这篇就是介绍dagger2代码 该简单场景代码是MainActivity里面有个MainEntity实体类,该实体类里面还有个属性是MainParameter,那么这次的代码是利用dargger2把实体依赖注入进Activity里面,Activity里面将会看不到new的代码。

1. 实体和属性类,使用 @Inject 标记
public class MainEntity {

    /**
     * 只允许有一个 @Inject,因为Component无法区别你需要什么样的对象
     */
    @Inject
    public MainEntity(MainParameter mainParameter) {
        this.mainParameter = mainParameter;
    }
}
public class MainParameter {

    @Inject
    public MainParameter() {
    }
}

当对构造函数标记了@Inject会为该类注入无参构造方法构造的对象,但是注意,如果注入的对象带有参数,如果该参数没有实例化,那么该参数将为null。例如代码里面的MainParameter ,它的构造函数我同样给了@Inject标记,所以不会为null

2. 使用@Component,类似中介类关联的意思
@Component
public interface MainComponent {
    /**
     * 该方法名随意起,不管起什么,起多少个,最终生成的代码都是参数跟注入的MainModel结合
     * 当然,只能放置一个参数,如果加入多个参数会运行编译报错
     */
    void inject(MainActivity activity);
}

@Component,用来管理依赖注入的细节,充当目标类和实例类之间的中介。当它发现目标类需要依赖,就会自动生成对应的实例,并注入到指定位置。

3. 在Activity使用实体类
public class MainActivity extends AppCompatActivity {

    @Inject
    MainEntity mainEntity;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.builder().build().inject(this);
        mainEntity.setName("名称");
        mainEntity.getMainParameter().setName("名称2");
        ((TextView) findViewById(R.id.tvName)).append(mainEntity.getName() + "_" + mainEntity.getMainParameter().getName());
    }
}

大家可以看到,mainEntity没有使用new的相关代码,直接使用赋值

该篇文章只是介绍了依赖注入的无参实体,那么有参数的实体如何构造呢?!请看下篇

3. 【浅入深出理解 dagger、Hilt】 - dagger有参依赖注入

上一篇写了dagger2的无参依赖注入代码,那么该篇就是有参依赖注入代码了 这次场景很简单,单纯在一个Activity构造并且使用一个实体类,参数由Activity传递

1. 依然使用 @Inject 标记
public class MainEntity {

    @Inject
    public MainEntity(String name) {
        this.name = name;
    }
}

不过这次的构造函数有点不一样,我们需要传递一个name

2. 使用@Module 和 @Provides
@Module
public class MainModule {

    String name;

    public MainModule(String name) {
        this.name = name;
    }

    @Provides
    MainEntity provideMainParameter() {
        return new MainEntity(name);
    }

}
  • 使用@Module标注类,表示它为工厂
  • 在类中使用@Provides标注方法,表示它为工厂提供实例的方法。 在该方法中对实例进行初始化并返回该实例。
3. 创建中介类,使用@Component
@Component(modules = {MainModule.class})
public interface MainComponent {
    /**
     * 该方法名随意起,不管起什么,起多少个,最终生成的代码都是参数跟注入的MainModel结合
     * 当然,只能放置一个参数,如果加入多个参数会运行编译报错
     */
    void inject(MainActivity activity);
}

上一篇已经具体介绍@Component,唯一区别是因为用到了Module,所以要给个额外标记

4. 在Activity使用实体类
public class MainActivity extends AppCompatActivity {

    @Inject
    MainEntity mainEntity;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainComponent.builder().mainModule(new MainModule("参数")).build().inject(this);

        ((TextView) findViewById(R.id.tvName)).append("_" + mainEntity.getName());
    }
}

那么这边已经介绍完毕有参的依赖注入了,我们主要还是深入了解Hilt,因为Hilt是google简化dagger的代码而产生的新的依赖注入库。所以dagger暂时结束了!

4. 【浅入深出理解 dagger、Hilt】 - Hilt

在之前的文章就已经介绍了依赖注入的作用。也说过Hilt是由dagger更加优化并且适合Android的一个依赖注入库。那我们直接看看如何使用吧!

让我们直接开始无参依赖注入

1. 在Project的build.gradle添加引入
    dependencies {
        ...
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.36"
    }
2. 在module的build.gradle分别两处地方添加
plugins {
    ...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
dependencies {
    ……

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

好了!添加相关插件自动下载后,我们继续!

3. HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application()

跟dagger有点不一样的是,必须包含一个带有 @HiltAndroidApp标记的Application类。 当然,别忘记修改AndroidManifest.xml

<application
        android:allowBackup="true"
        android:name=".MyApplication"
        ...
</application>
4. 依然使用 @Inject 标记相关需要new的类
class MainEntity @Inject constructor() {
    var name: String = ""
}
5. @AndroidEntryPoint 在Activity使用实体类

请注意,这里已经没有dagger的Component,这也算是体现出Hilt的精简的一个地方,但是我们仍然需要一个标记@AndroidEntryPoint表示该Activity会注入一些对象

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var mainEntity: MainEntity

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        mainEntity.name = "名称"
        binding.tvName.append(mainEntity.name)
    }
}

那如果是有参数的构造函数如何依赖注入呢

简单来说我们用到Hilt的工厂概念,通过传递不同的函数,返回相应的实体,那么我们直接开始吧!

1. 创建实体,使用 @AssistedInject标记构造函数 和 @Assisted标记参数
class MainEntity @AssistedInject constructor(@Assisted var name: String)
2. 创建工厂,使用 @AssistedFactory标记工厂接口
@AssistedFactory
interface MainFactory {
    fun create(name: String): MainEntity
}
3.最后在Activity使用
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var mainFactory: MainFactory
    lateinit var mainEntity: MainEntity

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        mainEntity = mainFactory.create("名称")
        binding.tvName.append(mainEntity.name)
    }
}

那如果同个构造函数里面的参数有多个相同呢?

其实很简单,通过@Assisted("key")不同的key来区分即可

class MainEntity @AssistedInject constructor(@Assisted("name") var name: String,@Assisted("area") var area: String)
@AssistedFactory
interface MainFactory {
    fun create(@Assisted("name") name: String, @Assisted("area") area: String): MainEntity
}

那么这篇文章就结束了

5. 【浅入深出理解 dagger、Hilt】 - Hilt进阶之MVVM

该章站在企业项目角度上如何在MVVM框架上完善并且合理的使用Hilt。

所以如果没了解MVVM的结构建议可以直接忽略这章。大家知道,MVVM里面View和Model之间有关联,假设一个场景View触发一个点击事件调用Model查询,然后Model会调用相关Http类进行查询,返回数据给Model,Model再返回数据给View。 当然上述表达的不是严格意义的MVVM,但是流程相仿,主要是讲解如何注解Model和相关Http类,让他们自动生成

让我们先看看有哪些类

image.png

类名解释
MyApplicationapp的入口
MainActivity顾名思义就是展示的View
ViewModelMainActivity对应的ViewModel,也是MVVM中的Model
TestApi一个仿造Http的类,只是单纯返回一个数据
NetworkModule一个制造类,Hilt通过标记自动寻找相关类,然后会找到该类调用相关函数实例化TestApi

那么让我们直接快速讲解

1. 在Project的build.gradle添加引入
    dependencies {
        ...
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.36"
    }
2. 在module的build.gradle分别两处地方添加
plugins {
    ...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
dependencies {
    ……

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

好了!添加相关插件自动下载后,我们继续!

3. HiltAndroidApp标记Application
@HiltAndroidApp
class MyApplication : Application()

跟dagger有点不一样的是,必须包含一个带有 @HiltAndroidApp标记的Application类。 当然,别忘记修改AndroidManifest.xml

<application
        android:allowBackup="true"
        android:name=".MyApplication"
        ...
</application>
5. Activity
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: ViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.tvName.append(viewModel.shopBanner.toString())
    }
}

大家看到viewModel就很奇怪,没有标记就能自动生成使用了?其实by viewModels()就已经代表Hilt生成处理了。让我们往下看

4. 创建一个仿造访问网络数据的TestApi 类
class TestApi {
    fun getValue(): Int {
        return 1
    }
}

只是一个简单的创建返回数据类

5. ViewModel

可以同时看到两个标记@HiltViewModel@Inject,那么TestApi在哪里实例化呢?Activity也没有创建TestApi呀,这就是Hilt方便的地方,让我们看下一个NetworkModule

@HiltViewModel
class ViewModel @Inject constructor(
    testApi: TestApi
) : ViewModel() {

    val shopBanner = testApi.getValue()
}
6. NetworkModule

通过@Module@InstallIn@Provides@Singleton等多个标记,让Hilt寻找TestApi的构造方法的时候,找到这里,并且调用GetApi方法创建实例。

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

    @Provides
    @Singleton
    fun GetApi(): TestApi {
        return TestApi()
    }

}
7. End

基本就结束了,那么大家很奇怪,其实很简单两句话代码放在MainActivity直接实例化不就行了吗,其实不是这样的,当项目越庞大的时候,可能改变构造函数的时候,那么我们使用Hilt就能很方便的集中在类似NetworkModule这样的类处理,而在Activity这些类中,我们是不需要关心构造函数的。如果你觉得理解还抽象,那么建议先用平常方式写Mvvm,最后再用Hilt优化这方面,或许会有更深入的理解呢!

标记符讲解表格

标记符标记是为了什么
@Module标记一个module,代表提供一些无法用构造@Inject的依赖, 比如接口, 第三方库类型, Builder模式构造的对象等
@InstallIn委托Hilt帮我们管理范围,以管理对象的生命周期,通过指定 Hilt 组件告诉 Hilt 绑定在哪些容器中可用,有很多种容器具体可以看图1
@Provides提供实例,注释函数,以告诉 Hilt 如何提供无法注入构造函数的 类型
@ViewModelScoped当在ViewModel中引入协程,如果直接使用CoroutineScope,那么需要在onCleared()方法中取消协程,如果忘记取消协程那么会导致出现内存泄漏等各种问题,此时需要使用ViewModel扩展属性viewModelScope来实现协程作用域

那么全部结束了,欢迎Star,下面是Demo源码地址

只是简单介绍依赖注入使用方式的实例源码如下: zhongjhATC/HiltAndDaggerDemo: 演示 Hilt、Dagger 的demo (github.com)

一个非常丰富的开源库,如果你需要相册、录制、录音等操作,那么这个也许对你有一定的帮助: zhongjhATC/AlbumCameraRecorder: 🔥一个高效的多媒体支持操作库,可多方面的简单配置操作相册、拍照、录制、录音等功能。也支持配套使用的展示图片、视频、音频的九宫格功能。 (An efficient multimedia support operation library, can be a variety of simple configuration operation album, photo, recording, recording and other functions.Also support supporting the use of the display of pictures, video, audio of the nine grid function.) (github.com)