依赖注入框架-Dagger2 精炼详解

一、前期基础知识储备

1)依赖注入是什么?

依赖注入是面向对象编程的一种设计模式,其目的是为了降低程序耦合,这个耦合就是类之间的依赖引起的。

我们使用各种依赖注入框架比如View注入框架Butternife,Koltin注入框架kodein,更加简易的RoboGuice注入框架等等,都是为了降低类与类之间的耦合。实现依赖注入的方法通常有三种:①构造方法注入②定义set方法注入③注解注入。第三方依赖注入框架都是使用注解注入的方式。

2)Dagger2是什么?

Dagger2是Dagger的升级版,是Android目前最好用的依赖注入框架,在编译期间自动生成代码,负责依赖对象的创建。第一代由Square公司共享出来,第二代则是由谷歌接手后推出的。github地址为:google/dagger

二、上代码,具体实现

1)使用Dagger2的优势

代码对比一下,不使用依赖注入和使用依赖注入的代码区别。

三个类:Paper,Paint,TestDraggerActivity,代码如下:

 

public class Paper {
    public String draw(){
        return "线稿";
    }
}
public class Paint {

    private Paper mPaper;
    public Paint(Paper paper){
        mPaper = paper;
    }

    public String drawing() {
        return mPaper.draw();
    }
}
public class DraggerActivity extends AppCompatActivity {

    private Paint mPaint;
    private Paper mPaper;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPaper = new Paper();
        mPaint = new Paint(mPaper);
        Toast.makeText(this, mPaint.drawing(), Toast.LENGTH_SHORT).show();
    }
}

一段常规代码,Paint的构造方法中注入Paper实例,日常开发中经常使用构造方法set方法进行实例注入。然后在Activity中通过new的方式拿到实例对象,再调用对象的方法实现逻辑。但试想,如果Paper,Paint的构造方法改变,比如增减变换形参,这时就会导致所以实例化过Paint和Paper的地方都需要修改,工作量增加了,也违背了开闭原则(在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的“)。

下面是使用依赖注入方式的代码,实现同样的逻辑:

public class Paper {

    @Inject
    public Paper() {

    }
    public String draw(){
        return "线稿";
    }
}
public class Paint {

    private Paper mPaper;
    @Inject
    public Paint(Paper paper){
        mPaper = paper;
    }

    public String drawing() {
        return mPaper.draw();
    }
}
// Dagger2使用时新增一个接口
@Component
public interface TestDraggerActivityComponent {
    void inject(TestDraggerActivity activity);
}

public class TestDraggerActivity extends AppCompatActivity {

    @Inject
    Paint mPaint;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 先编译一下项目 然后Dagger2会自动生成这个类
        // Build --> Rebuild Project 或者 Build --> make module
        DaggerTestDraggerActivityComponent.create().inject(this);
        Toast.makeText(this, mPaint.drawing(), Toast.LENGTH_SHORT).show();
    }
}

可以看到Paint和Paper的代码基本不变,只是在类的构造方法上增加了注解@Inject;而Activity中代码减少,并且去掉了此前Paint和Paper的两个实例化的new方法。这样,之后无论Paint和Paper的构造方法如何改变,Activity中的代码都不会受到影响。

注意,在使用Dagger2时需要新建一个接口,命名规则为”Activity名 + Component“,同时在接口的上方加入注解@Component,同时为该接口写入接口方法inject(),方法的参数即为Activity,而这也标注了依赖注入的方向,即Dagger2将@Inject注解构建好的对象注入给谁,这个谁就是inject()方法中的参数。

2)@Inject和@Component两个注解

@Inject

依赖注入中第一个并且是最重要的就是@Inject注解。用以标记那些需要被依赖注入框架所提供的依赖。在Dagger 2中有3种不同的方式来提供依赖(即添加@Inject注解的位置有三处):

构造方法处

    @Inject
    public Paper() {

    }

告诉Dagger2可以使用这个构造器构建实例对象;

    @Inject
    public Paint(Paper paper){
        mPaper = paper;
    }

注入含参数的构造方法所需要的参数,以完成该实例对象的构建;

类属性(即对象类型变量)处

public class TestDraggerActivity extends AppCompatActivity {

    @Inject
    Paint mPaint;
    ... ...
}

被标注的属性不能使用private修饰,否则无法注入。属性注入也是Dagger2中使用最多的一个注入方式。

③ 方法注入:与属性注入类似

public class TestDraggerActivity extends AppCompatActivity {

    private Paint mPaint;

    ... ...
	
	@Inject
    public void setPaint(Paint paint) {
        mPaint = paint;
    }
}

注意要标注在public方法(该方法通常命名为setXXX)上,Dagger2会在构造器执行之后立即调用这个方法。

@Component

@Inject用来标记需要被注入框架注入的方法,属性,构造,而Dagger2则是用@Component来完成依赖注入的,@Component可以说是Dagger2中最重要的一个注解。

@Component
public interface TestDraggerActivityComponent {
    void inject(TestDraggerActivity activity);
}

接口方法:void inject(目标类 obj) — Dagger2会从目标类开始查找@Inject注解,自动生成依赖注入的代码,最后类中调用inject(this)即可最终完成依赖的注入。

有些还未使用Dagger2的开发者或许会有疑问:Activity中只有

    @Inject
    Paint mPaint;

而没有任何关于Paper的声明或注解,那么Paint要如何使用到paper实例呢?其实这个inject过程并不像我们的代码看上去是这种inject套inject的过程,真实过程是这样:在Activity中进行inject()的时候,发现Paint的构造函数被@Inject标注了且带有一个参数paper,然后Dagger2就去寻找Paper发现它的构造函数也被@Inject标注并且无参数,于是Dagger2把Paper的实例注入给Activity,然后再去实例化Paint的时候,用的是已经注入给Activity的那个paper实例。也就是说我们可以这样理解:并不是Paint直接实例化Paper,而是Activity实例化Paper后交给Paint使用的。

 

总结一下:

使用时添加依赖

api 'com.google.dagger:dagger:2.24',
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

依赖的是什么?注入的是什么?注入给谁?

依赖的是@Inject标注的实例对象,注入的也是@Inject标注的实例对象;

    @Inject
    Paint mPaint;

注入给@Component注解下接口方法内的目标类:

@Component
public interface TestDraggerActivityComponent {
    void inject(TestDraggerActivity activity);
}

使用注解,Dragger2生成的文件是什么?

我们在使用时,只会使用Dagger2的注解API,但是实际上在编译时Dagger2会生成几份中间文件。如下图:

3)@Module和@Provides两个注解

使用@Inject标记构造器提供依赖是有局限性的,比如说我们需要注入的对象是第三方库提供的,我们无法在第三方库的构造器上加上@Inject注解。
或者,我们使用依赖倒置的时候,因为需要注入的对象是抽象的,@Inject也无法使用,因为抽象的类并不能实例化。我们这里使用如下两个类okhttp3包下的OkHttpClient和RetrofitManager模拟不可改动代码的情况:

public class OkHttpClient implements Cloneable, Call.Factory {
	... ...
	
	public OkHttpClient build() {
    return new OkHttpClient(this);
  }
}
public class RetrofitManager {
    private OkHttpClient mClient;

    public RetrofitManager(OkHttpClient client) {
        mClient = client;
    }

    public OkHttpClient getmClient(){
        return this.mClient;
    }
}

这种情况下就需要用到Module了,代码如下:

① 编写Module类并使用@Module标注这个类,编写方法返回值为我们需要inject的类型并使用@Provides标注这个方法;

@Module
public class HttpActivityModule {
    @Provides
    OkHttpClient provideOkHttpClient() {
        return new OkHttpClient();
    }
}

编写Component接口,使用@Component标注这个接口,并使用modules=的方法链接上第一步中编写的Module类;

@Component(modules = HttpActivityModule.class)
public interface HttpActivityComponent {
    void inject(HttpActivity activity);
}

之后的步骤就像使用@Inject一样了。

public class HttpActivity extends AppCompatActivity {

    @Inject
    OkHttpClient mOkHttpClient;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerHttpActivityComponent.create().inject(this);
        Toast.makeText(this, mOkHttpClient.getCacheSize(), Toast.LENGTH_SHORT).show();
    }
}

复杂的@Module用法,如下:(上面的@Provides标注的构造方法没有参数,如果有参数会有一些不同)

@Module
public class HttpActivityModule {
    private int cacheSize;
    public HttpActivityModule(int cacheSize) {
        this.cacheSize = cacheSize;
    }

    @Provides
    OkHttpClient provideOkHttpClient() {
        OkHttpClient client = new OkHttpClient();
        client.setCacheSize(this.cacheSize);
        return client;
    }

    @Provides
    RetrofitManager provideRetrofitManager(OkHttpClient client) {
        return new RetrofitManager(client);
    }
}

复杂的情况,Module中其中一个依赖又要依赖另外一个依赖,如果被@Provides标注的方法带有参数,dagger2会自动寻找本Module中其他返回值类型为参数的类型的且被@Provides标注的方法,如果本Module中找不到就会去看这个类的构造参数是否被@Inject标注了(所以一般情况下Module中方法的返回值都不能相同,当然也有办法使多个方法的返回值类型相同,有需要的朋友请自行研究吧)

一般类无法使用@Inject注解时,才会使用@Module,所以使用双@Provides的情况常见。

public class HttpActivity extends AppCompatActivity {

    @Inject
    RetrofitManager retrofitManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerHttpActivityComponent.builder()
                .httpActivityModule(new HttpActivityModule(100))
                .build().inject(this);
        Toast.makeText(this, retrofitManager.hashCode(), Toast.LENGTH_SHORT).show();
    }
}

在Module的构造函数带有参数且参数被使用的情况下,所生产的Component类就没有create()方法了。

三、Dagger2的其他API和搭配MVP使用

下面是Dagger 2的API:

public @interface Component {
    Class<?>[] modules() default {};
    Class<?>[] dependencies() default {};
}

public @interface Subcomponent {
    Class<?>[] modules() default {};
}

public @interface Module {
    Class<?>[] includes() default {};
}

public @interface Provides {
}

public @interface MapKey {
    boolean unwrapValue() default true;
}

public interface Lazy<T> {
    T get();
}

还有在Dagger 2中用到的定义在 JSR-330 (Java中依赖注入的标准)中的其它元素:

public @interface Inject {
}

public @interface Scope {
}

public @interface Qualifier {
}

笔者开发中涉及的上面四个基础注解比较多,想深入研究的话,参考文章:《使用Dagger 2依赖注入 - API

另外,如果使用Dagger2搭配MVP使用,比如将presenter注入到view层;值得一提的是谷歌不推荐直接将presenter的构造参数添加注解,更加推荐的是将presenter放到Module里进行管理,因为这样代码更加容易管理。

参考文章:《Dagger2从入门到放弃再到恍然大悟》《Dagger2新手入门与使用基础教程

 

最后总结一下:MVP、组件化、RxJava中会搭配使用到Dragger2,那么到底有哪些好处?

  • 依赖的注入和配置独立于组件之外。
  • 因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。
  • 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。
  • Dagger2可以免去在每个使用类的地方都使用构造函数构造一个实例,使用@Inject注解可实现类实例的依赖注入,依赖的构建支持@Module、@Provide注解,用于构建自定义的类和第三方类,并使用@Scope注解来声明依赖的作用域,可以跟acitvity的声明周期绑定在一起,在需要的地方自动实现单例模式。

新文推荐《Dagger.Android — Dagger2进阶》,以上用法是Dagger2的基础用法,在实际项目中,我们会更多地使用Dagger专为Android提供的注入行为,推荐文章在本篇文章的基础上继续深入,希望能有所收获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值