由浅入深,带你一步步走进Dagger2

Android中,相比较RxJava、Retrofit等框架,Daager2可以说是最难上手的框架了,没有之一。Dagger2,一款依赖注入框架。看下官方对它的描述:


在这里插入图片描述

用于Android和java的一款高效的依赖注入工具。Dagger1.x由square开发,Dagger2由google公司接手,这里是Dagger2。我们先从一段代码讲起:

public class A {
    private String aMsg;

    public A(String aMsg) {
        this.aMsg = aMsg;
    }
}

public class Test {
    public void test() {
        A a = new A("msgA");
    }
}


一段最简单不过的代码,Test类中的test方法中new了一个A类的对象,Test类依赖A类。我们称Test是依赖方,A是被依赖方。看起来似乎很完美。那么如果这样呢?

public class A {
    private String aMsg;

    public A(String aMsg) {
        this.aMsg = aMsg;
    }
}

public class Test {
    public void test() {
        A a = new A("msgA");
    }
}

public class Test1 {
    public void test() {
        A a = new A("msgA");
    }
}

Test、Test1两个类都依赖于A。有的人会说,依赖就依赖呗,我代码照样写,项目照样跑啊。是的,没错,你只要遵循java的语法,你的代码不会有任何问题。但是,如果有一天你的A类的构造方法新增了一个参数呢?你可能会说,改一下呗,实例化的时候多传一个参数就行了,的确可以,这里只有两个类依赖了A,改起来也很轻松,但假如有有100个类都依赖了A呢?我们这种做法就不是很优雅、很软工了,真要你改,你宁愿重新写一个类也不愿去改100个地方吧。其实回过头来我们发现:问题的症结在于A类的实例化放在了依赖方,导致A每次变动,都会导致Test类的变动,这就是传说中的耦合了。那么有没有办法解决这个问题呢?有的,就是我们的依赖注入(Dependency Injection)。依赖注入有三种方式:

1. 构造方法注入

public class Test {
    private A a;

    public Test(A a) {
        this.a = a;
    }

    public void test() {
        System.out.println(a.hashCode());
    }
}

A类的实例化不放在Test类中,由构造方法传进来,这时无论A怎么变,都不需要再修改Test中的代码,Test只用关注自身的变化就好,不用再关注依赖A的变化。

2. setter方法注入

public class Test {
    private A a;

    public void test() {
        System.out.println(a.hashCode());
    }

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

在调用test方法之前确保调用了setA方法且传入的A对象不为空就好,能实现同样的效果。

3. 接口注入

public interface SetA {
    void set(A a);
}

public class Test implements SetA {
    private A a;

    public void test() {
        System.out.println(a.hashCode());
    }

    @Override
    public void set(A a) {
        this.a = a;
    }
}

A类实现了一个SetA接口,同样在调用test之前能确保调用set(A a)方法且传入的A对象不为空即可。上述三种方式都是在解决一个问题:把A类的实例化从依赖方剥离,让依赖方不用关注依赖的变化,只关注自身的变化就好

然而,依赖方的确不需要实例化依赖了,但是依赖对象不是从石头里蹦出来的啊,总要有地方去进行依赖的实例化。

而实现依赖注入的框架Dagger2就是我们今天的主角。本人是一位Android开发者,所以博客中关于Dagger2的demo都是基于Android平台下的。走你,先看下gradle中的依赖代码,如下:

  implementation 'com.google.dagger:dagger:2.4'
  annotationProcessor  'com.google.dagger:dagger-compiler:2.4'
  implementation 'org.glassfish:javax.annotation:10.0-b28'

下面有下面一个需求:
有一个类Person,我需要在MainActivity中不调用Person的构造函数就能够使用。我们先来分析一下,这里MainActivity是依赖方,或者说需求方,即它需要一个Person类的实例。Person是被需求方。所谓有需求才有供给嘛,没有需求哪来的供给呢?在此之前我们先看两个注解:
@Inject和@Component

如果对注解还不太了解的话,请移步:注解的使用

@Inject注解是一个标签,它是给Dagger2看的,主要用在两个地方:

  1. 给一个类的构造方法注解。表示它可以提供一个依赖。
  2. 给一个类的属性做注解,表示这个属性是需求方,需要一个依赖对象。
  3. 给一个类的方法做注解,可以理解为属性的set方式,也是注入的目标。

这个注解就是用来标注需求方和被需求方。不过,仅仅凭这一个注解是不能让Dagger2正常运行的,我们还需要一个@Component注解。 @Component 相当于联系纽带,将 @inject 标记的需求方和依赖绑定起来,并建立了联系,而 Dagger2 在编译代码时会依靠这种关系来进行对应的依赖注入。好了,理论说完了,咋用呢?以上面需求为例,直接上代码:

public class Person {
    @Inject
    public Person() {

    }
}

可以看到,我们在Person的构造方法上用 @Inject进行了注解。表示它是一个依赖的提供方,负责提供Person类的实例。

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    Person person;

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

我们用 @Inject对MainActivity中的person变量进行了注解,表示它是一个依赖需求方,它需要一个Person类的实例。需求方和提供方都有了,还需要一个中间联系人MainComponent :

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

MainComponent 是一个接口,Dagger2会在编译的时候生成其对应的实现类,用来将提供方提供的实例注入到需求方。用 @Component 注解表示它是一个注入器,专门用来注入依赖实例的。void inject(MainActivity mainActivity)表示将依赖实例注入到哪里,这里我们就是将实例注入到MainActivity里。如果其他Activity也用到这个实例,重写一个方法就好了,把参数改成其它Activity就行。到此还没有完成,还缺少最终一步。Dagger2中采用了反射机制,我们知道,反射是比较消耗性能的,而Android对于性能这块看的比较重。Dagger2权衡之下,采用了APT(Annotation Process Tool) 在编译时生成辅助类,将比较耗费性能的反射放在了编译阶段。所以在使用之前我们还需要build一下项目,生成对应的辅助类(不build不会生成辅助类!!)

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    Person person;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);
        Log.e(TAG, person.hashCode() + "");
    }
}

DaggerMainComponent这个类不是我们建立的,是Dagger2在编译时自动生成的MainComponent的实现类。调用DaggerMainComponent.create().inject(this);这行代码之后,Person类的实例就被注入到MainActivity中了。我们就可以使用person属性了。看下打印的结果:


在这里插入图片描述

好吧,让我们来梳理下流程。

  • MainAcivity需要一个Person类的实例,所以用@Inject注解标记在person变量上。同时Person类的构造方法也用@Inject注解标记了,表示它可以提供一个Person类的实例。
  • 有需求也有供给,但是还需要一个中介,@Component注解标记的接口可以看做是一个中介。它负责把供给方提供的东西给到需求方。

这就是其中一种方式:@Inject构造方法+@Inject属性+Component
刚刚说@Inject还可以注解在方法上(非构造方法)实现类似于setXX的效果。Person的代码无需改动,MainActivity代码稍作修改:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    private Person person;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);
        Log.e(TAG, person.hashCode() + "");
    }

    @Inject
    public void setPerson(Person person) {
        this.person = person;
    }
}

唯一不同的是,把属性上的@Inject挪到方法上了,实现类似于SetXX的效果,结果是一样的。

这样就完成了Dagger2的基本使用,Dagger2的其它用法都是基于此演变而来,万变不离其宗。

现在有这么一个场景,我在MainActivity里面需要一个ArrayList实例。有人说,在ArrayList的构造方法上和MainActivity上加注解,然后再弄个Component呗。然而,ArrayList的源码你能修改么?那可是java utils包下的源码,无法修改。凡是第三方的类,我们都不能修改,可是我又想用Dagger2,怎么搞?放心,你想到的问题Dagger2的开发者们都想到了,于是他们又推出了一个新概念:@Module 用于对@Inject做一个补充,解决我们无法在第三方类的构造方法中添加@Inject注解,而导致无法进行依赖注入的问题。

我们只需记住,@Module注解和@Inject注解所起到的作用是一致的,都是提供依赖的实例,只不过@Module注解更加全面,用法稍微不同而已。

那么怎么用呢?还是上面的问题,我们在MainActivity需要一个ArrayList实例,而我们不想去采用直接new的方式,就想采用Dagger2这种高逼格的方式进行依赖注入咋搞?
首先我们要定义一个类,一般名称叫做XXModule,这里因为和MainActivity关联,就起名MainModule,然后用@Module注解注解类名。表示这个类是用来提供依赖的,但依赖不是凭空生成的,需要在MainModule中定义一个提供依赖的方法,再用@Provides注解注解该方法表示这个方法是用来生成依赖实例的。

@Module
public class MainModule {
    @Provides
    ArrayList provideArrayList() {
        return new ArrayList<>();
    }
}

一般方法的起名都是provideXXX,这样能够顾名思义。其实Module就是一个普通的类,类中有一个提供依赖的方法,类名和方法分别用@Module和@Provides。这两个注解是给Dagger2看的。MainModule定义好了,我们的MainComponent还需要做稍微的修改:

@Component(modules = {MainModule.class})
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

这里引用注射器的比喻,我们的MainActivity相当于一个病人,他需要一个药物ArrayList,而Component就相当于一个注射器,负责把药物注射到病人体内,而注射器本身是没有药物的,所以需要告诉注射器这个药物是来自于哪里的。@Component(modules = {MainModule.class})就是告诉Component告诉注射器病人需要的药物在某某地方,执行的时候,会先从病人的需求开始,病人需要哪种药物,我们通过@Component(modules = {MainModule.class})告诉了注射器病人需要的药物在MainModule类中,于是注射器会去寻找MainModule中是否存在一个返回值为ArrayList的方法,如果找到了,则会用该方法生成药物交给注射器,注射器再注射给病人,如果没有找到,则会报错,注射器就会说,你框我呢,我在你指定的地方根本没找到病人所需要的药物-。-

然后再看下MainActivity中的代码:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    ArrayList arrayList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);
        Log.e(TAG,arrayList.hashCode()+"");
    }
}

MainActivity的使用方法和之前几乎一样,DaggerMainComponent是我们build项目生成的MainComponent接口的实现类。看下结果:


在这里插入图片描述

说明我们的ArrayList对象已经成功注入到MainActivity中了。这里modules = {MainModule.class}是一个{},表明{}中可以放多个Module的class,表示可以提供不同的药物,举个例子?新建一个PersonModule:

@Module
public class PersonModule {
    @Provides
    Person providePerson() {
        return new Person();
    }
}

Component中的代码:

@Component(modules = {MainModule.class,PersonModule.class})
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

MainActivity中的代码:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    ArrayList arrayList;

    @Inject
    Person person;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);
        Log.e(TAG, arrayList.toString());
        Log.e(TAG, person.hashCode() + "");
    }
}

看下结果


在这里插入图片描述

可以看到ArrayList和Person实例都被注入到MainActivity中了。这应该很好理解。

上面我们说的都是类的注入,如果说我们MainActivity中需要的属性是一个接口呢,我们想注入一个接口实现类的实例,可不可以呢?新建一个People接口:

public interface People {
    void work();
}

定义两个实现类:

public class Student implements People {
    @Override
    public void work() {
        Log.e("TAG------","好好学习,天天向上~");
    }
}

public class Teacher implements People {
    @Override
    public void work() {
        Log.e("TAG------","我是一个园丁~");
    }
}


Student和Teacher都实现了People接口,然后我们MainModule代码:

@Module
public class MainModule {
    @Provides
    Student provideStudent() {
        return new Student();
    }

    @Provides
    Teacher provideTeacher() {
        return new Teacher();
    }
}

MainActivity代码:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    People people;

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

这时我们编译一下,会发现


在这里插入图片描述

我擦,报错了,为啥呢?其实也很简单,MainActivity需要一个People接口的实现类的对象,但是People接口的实现类可能不止一个,Component怎么知道你要哪个实现类的实例呢?但是,接口的引用指向子类的对象,这是一种很常用的用法,要如何实现呢?

引出@Named和@Qualifier

作用:
1.标记在provide方法上,用于区分来源。
2.标记在字段上,表示使用哪个来源。

先说@Named,MainModule代码如下:

@Module
public class MainModule {
    @Provides
    @Named ("student")
    People provideStudent() {
        return new Student();
    }

    @Provides
    @Named ("teacher")
    People provideTeacher() {
        return new Teacher();
    }
}

MainActivity中的代码:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    @Named("student")
    People student;

    @Inject
    @Named("teacher")
    People teahcer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);
        Log.e(TAG, student.getClass().getName());
        Log.e(TAG, teahcer.getClass().getName());
    }
}

看下结果


在这里插入图片描述

可以看到两个接口的引用分别指向了实现类Student和Teacher。如何区分提供哪个实现类,就是@Named注解起的作用。我们来分析一下,MainActivity需要一个People接口的引用,我们不知道他需要哪个实现类的实例,这时他用 @Named 注解标记了一个名字,Component去Module寻找提供依赖的方法的时候,就会寻找被这个名字标记的方法,如果找到,则用这个方法提供实例,找不到的话就会报错。这就是 @Named 注解的作用,标记来源,用以区分。

@Qualifier
其实作用和@Named是一样的,只不过@Named注解是用字符串来标记的,而@Qualifier是用注解来标记的。首先定义两个注解:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentQualifier {
    
}


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface TeacherQualifier {
    
}

这两个注解StudentQualifier 和TeacherQualifier 都用@Qualifier标记了,我们怎么用它实现和@Named相同的功能呢?MainModule:

@Module
public class MainModule {
    @Provides
    @StudentQualifier
    People provideStudent() {
        return new Student();
    }

    @Provides
    @TeacherQualifier
    People provideTeacher() {
        return new Teacher();
    }
}

再看MainActivtiy。

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    @StudentQualifier
    People student;

    @Inject
    @TeacherQualifier
    People teahcer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);
        
        Log.e(TAG, student.getClass().getName());
        Log.e(TAG, teahcer.getClass().getName());
    }
}

是不是和@Named的原理一样,同时标记MainActivity和对应的provideXXX方法即可。结果依旧能同时注入,这里就不贴了。

@Scope
Scope的意思是域、范围的意思。被Scope标注的类,在一定的范围内是单例的,即只创建一次,多次使用都是同一个对象。举个例子?在MainActivity中需要注入两个Peron实例,但我希望两个Person实例用的同一个对象。这里首先看一下@Scope注解的源码:

@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Scope {}

可以看到Scope的作用域是注解,即只能标注在注解上,也就是我们所说的元注解。如果对注解的概念还不太清楚,请移步注解详解 。既然只能标注在注解上,那我们自定义一个注解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyScope {

}

自定义一个注解,并用@Scope注解标记。这个定义的注解是来帮助我们实现单例的需求的。那这个自定义的注解怎么去用呢?两种用法:

1. 给Component和provideXX方法同时加上该注解。
2. 给Component和Person类加上该注解。

无论如何,Component都需要添加该注解。:

@MyScope
@Component(modules = {MainModule.class})
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

provideXX方法添加该注解:

@Module
public class MainModule {
    @Provides
    @MyScope
    Person providePerson() {
        
        return new Person();
    }
}

MainActivity代码:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    Person person1;

    @Inject
    Person person2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);
        Log.e(TAG,person1.hashCode()+"");
        Log.e(TAG,person2.hashCode()+"");

    }

}

看下结果:


在这里插入图片描述

可以看到两个对象的哈希值是一样的,说明两个对象是同一个对象。那我们刚所说的在一定范围内是单例的是什么意思呢?现在我们在MainActivity中新增一个按钮,点击跳转到SecondActivity。代码就不贴了,只是加了一个点击事件, SecondActivity代码:

public class SecondActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    Person person1;

    @Inject
    Person person2;

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

        DaggerMainComponent.create().inject(this);

        Log.e(TAG+"SecondActivity", person1.hashCode() + "");
        Log.e(TAG+"SecondActivity", person2.hashCode() + "");

    }
}

和MainAcivity几乎相同,没啥好说的,唯一不同的是Log的时候加上了Activity的名称,便于区分Log。我们看一下MainActivity和SecondActivity所注入的单例是否是同一个对象。


在这里插入图片描述

可以看到,两个Activity都实现了所需依赖的单例,但这个单例不是全局的,换了一个Activity就会重新初始化依赖对象,这就是在一定范围的意思。

第二种方式,给Person类和Component添加注解:

@MyScope
public class Person {
    @Inject
    public Person() {
    
    }
}

效果是相同的哈~

Lazy< T >
Lazy?懒加载?其实差不多就是这个意思。即使用的时候再去加载。我们之前@Inject T t并注入之后,Activity所需的依赖对象就已经创建了,我们拿到就可以直接用,但是@Inject Lazy< T > t并不会直接创建依赖对象,需要先get一下。看代码:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    Lazy<Person> person;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);

        Log.e(TAG, person.get().hashCode() + "");

    }
}

Lazy的get方法获取传入泛型的实例对象,第一次调用get会去创建Person对象,再次调用则会从缓存中取,不会去新建Person对象。多个Lazy之间不会共享缓存。

Provider< T >
作用和Lazy< T >相似,注入的时候不会立即创建依赖对象,等到调用get方法的时候再去创建依赖对象,和Lazy< T >不同的是,它不会缓存依赖对象,每次get都会去重新创建依赖对象。

@IntoSet
顾名思义,就是把提供的依赖对象加入到Set集合中。用法:
1.@IntoSet标注provideXX方法,将提供的依赖放入Set集合中。
2.需求方通过@Inject Set< T > set获取取得该Set集合。

@Module
public class MainModule {
    //将提供的依赖放入Set集合中
    @Provides
    @IntoSet
    Person providePerson() {

        return new Person();
    }
}


public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    //获取Set<Person>对象。
    @Inject
    Set<Person> personSet;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);

        for (Person person : personSet) {
            Log.e(TAG, person.hashCode() + "");
        }

    }
}

结果:


在这里插入图片描述

这里说明三点:

  • 相同返回值的provideXX方法会存入同一个Set集合中。
  • 不同返回值的provideXX方法会存入不同的Set中。
  • Component 注入多个目标类时,所有的目标类共享Set。

@IntoMap,顾名思义,是将提供的依赖存入Map中,以key和value的形式存入。用法和@IntoSet类似。key值用另一个标签 @StringKey,Module代码:

@Module
public class MainModule {
    //将提供的依赖放入Map集合中
    @Provides
    @IntoMap
    @StringKey("person1")
    Person providePerson() {

        return new Person();
    }

    //将提供的依赖放入Map集合中
    @Provides
    @IntoMap
    @StringKey("person2")
    Person providePerson2() {

        return new Person();
    }
}

Activity代码:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    //获取Map<String, Person>对象。
    @Inject
    Map<String, Person> map;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);

        for (Map.Entry<String, Person> entry : map.entrySet()) {
            Log.e(entry.getKey(), entry.getValue().toString());
        }
    }
}

看下结果:


在这里插入图片描述

可以看到,我们提供了两个依赖对象,都存到同一个Map中了。不过@Stringkey只能标注String的key,标注自定义类型的Key下面这再说。
几点说明:

  1. 一个Component中存标注了多个Module,则所有的Module公用一个Map。
  2. Map中的值只能是具体的类型,不可以是接口类型。
  3. 两个Component之间的Map不共享,相互独立。

如何自定义Key的类型?
1.自定义一个注解,用@MapKey标记:

@MapKey
public @interface PersonKey {
    Class value();
}

2.用@IntoMap和自定义的PersonKey注解标记provideXX方法:

@Module
public class MainModule {
    //将提供的依赖放入Map集合中
    @Provides
    @IntoMap
    @PersonKey(Person.class)
    String provideStr1() {

        return "person1";
    }
    
}


public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";

    @Inject
    Map<Person, String> map;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainComponent.create().inject(this);

        for (Map.Entry<Person, String> entry : map.entrySet()) {
            Log.e(entry.getKey().hashCode()+"", entry.getValue().toString());
        }

    }
}

结果:


在这里插入图片描述
可以看到,key是Person类的哈希值,value是我们提供的一串字符串。自定义key支持哪些类型呢?

1.基本数据类型
2.String
3.Class
4.枚举类型
5.注解类型
6.以上数据类型的数组

我们刚用的是Class类型。其它的各位看官感兴趣可以逐一试一试。常用的也就1、2、4,其它的用的比较少。

@Component的dependencies
我们之前说过,Component中可以标记多个Module,用法是@Component(modules = {MainModule.class}),里面可以放多个Module.class,这样Component就可以注射不同的依赖了。@Component还可以通过@Component(dependencies = {})的方式来提供不同的依赖,效果类似,不同的是{}中对应的是XXComponent.class。比如:

@Component(dependencies = {PersonComponent.class})
public interface MainComponent {
    void inject(MainActivity mainActivity);

    void inject(SecondActivity secondActivity);
}

@Subcomponent
看名字是子Component,实际上也是这个意思。Component也可以有类似java类的继承关系。比如有一个MainComponent,可以提供一串字符串依赖,有一个PersonComponent继承自MainComponent,它既有MainComponent的功能,自己本身又能提供一个Person依赖:

//注意这里是@Subcomponent
@Subcomponent(modules = {PersonModule.class})
public interface PersonComponent {

    void inject(MainActivity mainActivity);

}

@Module
public class PersonModule {
    @Provides
    Person providePerson() {
        return new Person();
    }
}

@Component(modules = {MainModule.class})
public interface MainComponent {
    //添加子Component。
    PersonComponent subMainComponent();

    void inject(MainActivity mainActivity);

    void inject(SecondActivity secondActivity);
}

@Module
public class MainModule {
    @Provides
    String provideStr1() {

        return "person1";
    }

}

这里的写法不是PersonComponent extents MainComponent ,而是在MainComponent中进行标记。并用@Subcomponent标记PersonModule,表示这是一个子Component。看下Activity中的调用:

public class MainActivity extends AppCompatActivity {
    public final static String TAG = "TAG_MAIN";
    @Inject
    String mainModuleProvideStr;//String来自于MainComponent

    @Inject
    Person personModuleProvidePerson;//person来自于PersonComponent

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取到子Component,并将子Component中提供的依赖注入进来。
        DaggerMainComponent.create().subMainComponent().inject(this);
        Log.e(TAG,mainModuleProvideStr);
        Log.e(TAG,personModuleProvidePerson.hashCode()+"");
    }
}

但是一定要调用DaggerMainComponent.create().subMainComponent(),否则获取不到子Component。 看起来不像继承,更准确的说应该是依附关系。虽说是一种继承关系,但就这个写法来看,说依附可能更好理解一点。

总结:,Dagger2确实解决了程序中依赖耦合的问题,是一款非常好的依赖注入框架,用法也很丰富,可以解决各种需求下的依赖注入问题,但说实话,上手的成本有点高,是我目前用过的框架最难上手的一款,没有之一,而且出了错误也不是很直观,用与不用还需要做一个权衡。

文章就先写到这里,如果文章中有什么错误或者不严谨的地方,欢迎批评指正。

完~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值