每天学习一个Android中的常用框架——8.Dagger

1.简介

Dagger,是由大名鼎鼎的Square公司(之前也学习过该公司开发出的OkHttp、OkIo、Retrofit)开发的一个依赖注入框架。而Dagger2是Dagger的升级版,同样是一个依赖注入框架,其由谷歌接手后推出,现在也由Google接手维护。

为了做到与时俱进,这篇博客讲解的就是当下最新的Dagger2

网上讲述的Dagger2教程大多是与MVP架构挂钩,实际上,MVP + Dagger2确实是许多大项目开发的首选技术栈。但在做小项目时,使用Dagger2在处理简单的业务中反而会增加业务量,提高逻辑深度。当然了,作为当前相当流行的注入框架,我们自然也要去认真学习的。

本篇博客以单纯讲述Dagger2的使用为主,不结合MVP架构进行综合讲解,只以基本使用为主。当同时熟悉Dagger2的使用和MVP的架构核心后,应当就能自然地联想到这两者相当完美的契合性。

当然,每个框架都是具有两面性的。作者在搜索Dagger2的相关资料时,也搜索到了关于Dagger2的很多不足,有兴趣的读者可以浏览这篇博客:dagger2从入门到放弃-为何放弃

总而言之,我们还是以学习为主。由于作者水平也尚浅,可能只能演示基本的使用,对于一些进阶的使用方法,可以参考Dagger2的官方文档:Dagger

2.特性

作为一个依赖注入框架,Dagger2有一个具有相似功能的框架——ButterKnife。严格意义来说,ButterKnife不算是严格意义的注入,它只专注于View的注入。Dagger2的功能虽然要比ButterKnife要强大许多,同时也有许多复杂之处,所以最好是两个框架都学,然后在项目中搭配使用。

依赖注入的原理作者相信网上已经有很多人举了很多不错的例子,这里就不再重复讲解了。按照作者的理解,简而言之就是一句话:一个类想要使用另一个类,就只管用,而不管创建如果比较熟悉Java系的Spring框架,应该会很清楚依赖注入的意思

依赖注入的一个最大的好处就是解耦,即将使用和创建分开。

Dagger2的核心知识点是四大注解,即:

  • @Inject:Inject主要有两个作用,一个是使用在构造函数上,通过标记构造函数让Dagger2来使用(Dagger2通过Inject标记可以在需要这个类实 例的时候来找到这个构造函数并把相关实例new出来)从而提供依赖,另一个作用就是标记在需要依赖的变量让Dagger2为其提供依赖。
  • @cpmponent:Component一般用来标注接口,被标注了Component的接口在编译时会产生相应的类的实例来作为提供依赖方和需要依赖方之间的桥梁,把相关依赖注入到其中。
  • @moudle:用Module标注的类是专门用来提供依赖的。有的人可能有些疑惑,看了上面的@Inject,需要在构造函数上标记才能提供依赖,那么如果我们需要提供的类构造函数无法修改怎么办,比如一些jar包里的类,我们无法修改源码。这时候就需要使用Module了。Module可以给不能修改源码的类提供依赖,当然,能用Inject标注的通过Module也可以提供依赖。
  • @provides:用Provide来标注一个方法,该方法可以在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Injection的变量赋值。provide主要用于标注Module里的方法。

通过一张图片,可以简单地描述这四个注解之间的关系:
在这里插入图片描述
除此之外,@Singleton注解也很常用。顾名思义,它的作用就是标注该实例化的对象为单例。

比起讲解枯燥的特性,直接上实例或许可以更好地帮助理解(作者本人也是这样)。话不多说,下面直接上代码。

3.演示

3.1 集成

每个框架在使用之前都需要进行集成,老样子,我们仍然直接去dagger的GitHub官网上去查dagger版本的最新依赖:dagger,修改module下的build.gradle,添加相应依赖:

	api 'com.google.dagger:dagger:2.27'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.27'
    api 'com.google.dagger:dagger-android:2.27'
    api 'com.google.dagger:dagger-android-support:2.27' // if you use the support libraries
    annotationProcessor 'com.google.dagger:dagger-android-processor:2.27'

注意:如果只是单独应用dagger2作为注入框架的话,只需要引入前两行依赖即可。若想将dagger2应用到android环境下,则五行依赖全部都要加上,否则会出现问题,切记!

3.2 基本功能——依赖注入

3.2.1 编写实体类(依赖提供方)

我们先定义两个简单的实体类,一个类为Money,代表金钱,一个类为Man,代表人类,它们均只有简单的字段,无参构造方法和get/set方法,代码分别如下:

import javax.inject.Inject;

public class Money {

    private int total;

    @Inject
    public Money() {
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }
}
import javax.inject.Inject;

public class Man {

    private String name;

    private Money money;

    @Inject
    public Man() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Money getMoney() {
        return money;
    }

    public void setMoney(Money money) {
        this.money = money;
    }
}

注意:这两个类的无参构造方法上都加上了@Inject注解,该注解的作用在第二节中已经说明,在编写完成这个逻辑之后我们再总结。

3.2.2 编写Component(依赖注入容器)

编写完两个实体类之后,接下来需要编写Component接口。编写一个名为ManComponent的接口,代码如下:

package com.androidframelearn.util_dagger;

import dagger.Component;

@Component
public interface ManComponent {
    void inject(MainActivity MainActivity);
}

注意:这个接口加上了@Component注解,该注解的作用在第二节中已经说明,在编写完成这个逻辑之后我们再总结。

对了,在编写完这个接口时,使用Android Studio的读者需要Make Project一下,这样就会自动生成一个名为DaggerManComponent,这个类我们将在下一小节中使用。

3.2.3 编写MainActivity(依赖需求方)

上面的三个类编写完成后,我们就开始着手编写测试代码对Dagger2的效果进行测试,修改MainActivity,代码如下:

package com.androidframelearn.util_dagger;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Inject
    Man man;

    @Inject
    Money money;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerManComponent.builder().build().inject(this);
        man.setName("小明");
        money.setTotal(100);
        man.setMoney(money);
        Log.i(TAG,"当前人物:" + man.getName() + "=========拥有钱数:" + man.getMoney().getTotal());
    }
}

注意:获取两个实体类的实例时,均加上了@Inject注解,该注解的作用在第二节中已经说明,在编写完成这个逻辑之后我们再总结。
按照一般代码逻辑,要想使用某个实例对象(基本数据类型除外),就需要对它提前进行初始化,否则就会报出空指针异常,然而,当我们运行项目时,查看日志输出,可以发现:
在这里插入图片描述
也就是说,在我们没有获取到对象实例时,为它进行赋值,并没有报错。刚好对应了上面说的一句话:一个类想要使用另一个类,就只管用,而不管创建

3.2.4 总结

根据上面这个例子,我们可以针对流程进行如下总结:

  1. 我们首先对Man和Money的构造函数进行了@Inject的注解,意思就是告诉Dagger2:如果有谁要使用Man和Money这个类,我标注的这个构造函数,你可以直接用来实例化该类;
  2. 然后我们在Activity中对Man和Money也进行了@Inject的注解,意思是告诉Dagger2:这个类需要被注入,简单的说就是“这个类我要用,你帮我实例化”;
  3. 当然,事情并没有那么简单。上面的例子中还有一个注解@Component ,因为光靠@Inject的标注是不足以完成注入的,我们需要用@Component来完成注入;
  4. 上例中被@Component标记的ManComponent 接口就是一个注入器; void inject(MainActivity MainActivity);的意思是MainActivity中要用到这个注入器,然后我们在MainActivity中对注入器进行初始化,即DaggerManComponent.builder().build().inject(this); 然后Activity中所有被@Inject标记的类,都会通过ManComponent 来进行初始化;

通过这个例子,我们再进一步梳理一下依赖注入的步骤:

  1. 首先定义一个实体类,并在其构造函数用@Inject注解,表示告诉Dagger2这是我的构造函数,如果有地方要用到我,就用该构造函数对我实例化;

  2. 创建一个@Component注解的注入器接口,并在注入器中使用 void inject(MainActivity MainActivity);来表明哪里要用到注入器;这里表示MainActivity中要用到该注入器

  3. 在MainActivity中对注入器进行初始化DaggerXXXComponent.builder().build().inject(this); 初始化后该注入器就可以正常使用了;

  4. 在MainActivity中对需要注入的实体类用@Inject进行标注,表示该类需要被注入,即实例化;

注意:在代码编写过程中,我们会发现DaggerXXXComponent会不存在,这是因为注入器是在编译的过程中才生成,所以我们在对注入器编写完成后Make Project 一下就会生成DaggeXXXComponent

3.3 进阶功能——模块注入

现在我们已经明白了@InJect@Component的作用了,接下来我们来研究另外两个注解:@Module@Provide

通过上面的例子我们发现 @Inject是对类的构造函数进行标注来进行实例化的,但是有些类,比如第三方框架OkHttp、Retrofit等,我们是无法对其源码进行修改的。

即对其构造函数进行标注,这个时候我们就用到了@Module@Module是什么意思呢?

@Module是和@Component配合使用的,意思就是告诉注入器,如果你在实例化对象的时候,没有找到合适的构造函数,你就来我这里找。@Module通常标注一个类,该类里面可以实例化各种类,Component在注入对象的时候先去Module中找,如果找不到就会检查所有被@Inject标注的构造函数;

这里为了方便演示,我们去掉Money实体类中构造方法上的@Inject,即修改为:

public class Money {

    private int total;
    
    public Money() {
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }
}

假设Money实体类就是某个不能被修改源码的框架,由于Man中使用到了Money,而Money没有被实例化,所以100%会报空指针异常。

为了解决这个问题,我们新建一个ManModule类,新建一个方法provideMoney(),将Money的创建过程放到里面,代码如下:

package com.androidframelearn.util_dagger;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

@Module
public class ManModule {

    @Singleton
    @Provides
    Money provideMoney(){
        Money money = new Money();
        money.setTotal(10000);
        return money;
    }
}

可以看到,这里又用到了两个新的注解,简单地说明一下它们的作用:

  • @Provide:用来标注一个方法,告诉注入器,我标注的方法你可以用来提供实例;

  • @Singleton:顾名思义,标注该实例化的对象为单例

写完了ManModule还不行,我们得在ManComponent里绑定这个ManModule,才能正常使用,代码如下:

package com.androidframelearn.util_dagger;

import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component(modules = ManModule.class)
public interface ManComponent {
    void inject(MainActivity MainActivity);
}

可以看到标注头多了@Component(modules = ManModule.class),表示告诉注入器如果你要注入的类没有找到构造函数,你就去ManModule.class中找。

为了更好地观察效果,修改MainActivity,因为我们在ManModule中已经给Money对象赋了默认值(10000),所以无需再赋值。代码如下:

package com.androidframelearn.util_dagger;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Inject
    Man man;

    @Inject
    Money money;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerManComponent.builder().build().inject(this);
        man.setName("小明");
        man.setMoney(money);
        Log.i(TAG,"当前人物:" + man.getName() + "=========拥有钱数:" + man.getMoney().getTotal());
    }
}

除了直接在MainActivity获取Money对象之外,你也可以直接在Man类的Money字段上添加@Inject注解,效果是一样的。运行项目,查看结果,可以Money对象的Total字段确实编程了10000,如图所示:
在这里插入图片描述

3.4 总结

至此,我们就有了两种方式可以提供依赖,一个是注解了@Inject的构造方法,一个是在Module里提供的依赖,那么Dagger2是怎么选择依赖提供的呢,规则是这样的:

  • 步骤1:查找Module中是否存在创建该类的方法

  • 步骤2:若存在创建类方法,查看该方法是否存在参数

  • 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数

  • 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

  • 步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数

  • 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数

  • 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

4.源码地址

AFL——Android框架学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值