博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android -- 带你从源码角度领悟Dagger2入门到放弃(二)
阅读量:6910 次
发布时间:2019-06-27

本文共 17266 字,大约阅读时间需要 57 分钟。

1,接着我们继续介绍,在上一篇我们介绍了简单的@Inject和@Component的结合使用,现在我们继续以老师和学生的例子,我们知道学生上课的时候都会有书籍来辅助听课,先来看看我们之前的Student代码

package com.qianmo.rxjavatext;import android.util.Log;import javax.inject.Inject;/** * Created by Administrator on 2017/4/17 0017. * E-Mail:543441727@qq.com */public class Student {    private int id;    private String name;    private Course[] course;    @Inject    public Student() {        System.out.println("Student create!!!");    }    public Student(int id, String name, Course[] course) {        this.id = id;        this.name = name;        this.course = course;    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Course[] getCourse() {        return course;    }    public void setCourse(Course[] course) {        this.course = course;    }    public void startLessons() {        System.out.println("开始上课了");    }}

  添加书籍对象,在类中有书籍的姓名属性,还有变黑动作(这里是瞎加上这个动作的),Book的代码如下:

/** * Created by Administrator on 2017/4/20 0020. * E-Mail:543441727@qq.com */public class Book {    private String name;    public Book(String name) {        this.name = name;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public void changeBlack() {        System.out.println(name + "我一本新书被翻黑了。。。。");    }}

  然后我们student中的构造函数也要添加了如下代码,且以前的构造函数需要删掉

package com.qianmo.rxjavatext;import android.util.Log;import javax.inject.Inject;/** * Created by Administrator on 2017/4/17 0017. * E-Mail:543441727@qq.com */public class Student {    private int id;    private String name;    private Course[] course;    private Book book;    @Inject    public Student(Book book) {        System.out.println("Student create with book!!!");    }    public Student(int id, String name, Course[] course) {        this.id = id;        this.name = name;        this.course = course;    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Course[] getCourse() {        return course;    }    public void setCourse(Course[] course) {        this.course = course;    }    public void startLessons() {        System.out.println("开始上课了");        book.changeBlack();    }}

  这时候我们会有一个猜想,当我们一个类中存在两个构造函数被标记了@Inject标记呢,那我们来试试代码如下:

  @Inject    public Student() {        System.out.println("Student create!!!");    }    @Inject    public Student(Book book) {        System.out.println("Student create with book!!!");    }

  但是你最后运行的时候会直接报错了,报错信息如下,翻译过来意思就是说Student类中只能包含一个@Inject标记修饰构造函数

Error:(19, 12) 错误: Types may only contain one @Inject constructor.

  OK,我们肯定现在脑袋了有这种疑问,要是我真的是想使用两个或者多个构造函数呢,别急,后面会和大家在实例中来讲解怎么使用的,好了还是回到我们正题上来,现在我们Student构造函数中出现了一个book对象,我们可能会想,有可能Dagger2会帮我们继续new一个Book对象放入Student构造函数中呐,好,我们抱着它能有这么智能我们来运行一下工程。

  哦哦,sorry,报错了,我们来看一下报错提示,说不定我们能从报错信息来找到解决方法呐

Error:(12, 9) Gradle: 错误: com.qianmo.rxjavatext.Book cannot be provided without an @Inject constructor or from an @Provides-annotated method.

  从上面你的信息我们知道它说book对象中没有提供一个标注为@Inject的构造方法,或者@Provides的方法。

  好的我们就先用第一种方法试试,标记Book构造函数为@Inject,并修改在Student类中的成员变量book也标记成@Inject,运行一下项目,运行结构如下

Student create with book!!!开始上课了我一本新书被翻黑了。。。。

  呃,可以了,没想到就这样可以了,666啊  ,这是我们使用的第一个方法,我们现在根据它之前报错信息的第二个方法来实现提供一个@Provides标记的方法

2,@Module和@Provides的使用

   而使用@Provides标记就要使用@Module标签了,先来回顾一下这两个注解标签的定义

@module,标注用于提供需要注入的实例的类,当我们要提供第三方的依赖时,使用Inject注解类的构造函数很明显不现实,这时就可以使用这个注解,在module中提供。@Provides,使用在用@module标注的类里面,告诉dagger2来这里找依赖。

  除了构造函数提供依赖,还能用Module提供。所以这里我们要创建一个Module,并创建@Provides注解标签修饰的对应的提供该对象的方法

@Modulepublic class TeacherModule {    @Provides    Book provideBook() {        return new Book();    }}

  这里需要解释一波了,首先provideBook这个方法名是固定的吗,当然不是固定的,不过我们为了逻辑清晰,一般采用然后修改provide开头,后面再加上类名,切记一定要加上@Provide标签,要让桥接器知道在这里找依赖。

  然后要修改TeacherComponent中的module引用

package com.qianmo.rxjavatext;import dagger.Component;/** * Created by Administrator on 2017/4/20 0020. * E-Mail:543441727@qq.com */@Component(modules = TeacherModule.class)public interface TeacherComponent {    void injectA(Teacher teacher);}

  修改调用方法

DaggerTeacherComponent.builder().teacherModule(new TeacherModule()).build().injectA(this);

  看一下运行效果

Student create with book!!!开始上课了我一本新书被翻黑了。。。。

  OK,打印结果没问题,这里我们想着既然Module能够提供依赖,那么我们把之前的Student的构造函数依赖添加到这里,代码如下:

package com.qianmo.rxjavatext;import dagger.Module;import dagger.Provides;/** * Created by Administrator on 2017/4/20 0020. * E-Mail:543441727@qq.com */@Modulepublic class TeacherModule {    @Provides    Book provideBook() {        return new Book();    }    @Provides    Student provideStudent(Book book){        return new Student(book);    }}

  这时候我们会有一个疑问,我们在Student构造函数加过@Inject注解又在Module中提供了依赖,这两个会不会冲突啊,我们心里面先带着这个疑问,运行一下项目,发现没什么问题运行结果如下:

Student create with book!!!开始上课了我一本新书被翻黑了。。。。

  那么我们现在会有一个疑问了,到底我们是我们通过@Inject注解标记Student构造函数起了作用还是我们的Module中的provideStudent方法起作用了呢?那么是时候看一波我们的源码了,我们这次一共涉及到四个类DaggerTeacherComponent、Teacher_MembersInjector、TeacherModule_ProvideStudentFactory、TeacherModule_ProvideBookFactory,前面两个类和我们之前一篇文章源码分析类似,后面两个类从之前的StudentFactory变成了TeacherModule_ProvideStudentFactory、TeacherModule_ProvideBookFactory这两个类,从字面上我们也可以理解一个是从TeacherModule中获取Student对象的提供工厂,一个是从TeacherModule中获取Book对象的提供工厂。不多说,我们继续先来看看DaggerTeacherComponent类的源码

package com.qianmo.rxjavatext;import dagger.MembersInjector;import dagger.internal.Preconditions;import javax.annotation.Generated;import javax.inject.Provider;@Generated(  value = "dagger.internal.codegen.ComponentProcessor",  comments = "https://google.github.io/dagger")public final class DaggerTeacherComponent implements TeacherComponent {  private Provider
provideBookProvider; private Provider
provideStudentProvider; private MembersInjector
teacherMembersInjector; private DaggerTeacherComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static TeacherComponent create() { return builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.provideBookProvider = TeacherModule_ProvideBookFactory.create(builder.teacherModule); this.provideStudentProvider = TeacherModule_ProvideStudentFactory.create(builder.teacherModule, provideBookProvider); this.teacherMembersInjector = Teacher_MembersInjector.create(provideStudentProvider); } @Override public void injectA(Teacher teacher) { teacherMembersInjector.injectMembers(teacher); } public static final class Builder { private TeacherModule teacherModule; private Builder() {} public TeacherComponent build() { if (teacherModule == null) { this.teacherModule = new TeacherModule(); } return new DaggerTeacherComponent(this); } public Builder teacherModule(TeacherModule teacherModule) { this.teacherModule = Preconditions.checkNotNull(teacherModule); return this; } }}

  可以看到和我们之前的DaggerTeacherComponent类源码有所不同,多了teacherModule、provideBookProvider、provideStudentProvider三个成员变量,多了teacherModule()方法。

  ok,继续按照我们之前的方法分析首先调用DaggerTeacherComponent.builder()创建出一个Builder对象出来,再调用DaggerTeacherComponent.builder().teacherModule(new TeacherModule()),注意了,请看teacherModule()方法里面的源码

public Builder teacherModule(TeacherModule teacherModule) {      this.teacherModule = Preconditions.checkNotNull(teacherModule);      return this;    }

  这里先检查传递的teacherModule对象是否为空,让不为空的话将将值赋值到成员变量this.teacherModule上。

  OK,我们继续往下看DaggerTeacherComponent.builder().teacherModule(new TeacherModule()).build(),接下来调用而是build方法,来看看具体的源码

public TeacherComponent build() {      if (teacherModule == null) {        this.teacherModule = new TeacherModule();      }      return new DaggerTeacherComponent(this);    }private DaggerTeacherComponent(Builder builder) {    assert builder != null;    initialize(builder);  }private void initialize(final Builder builder) {    this.provideBookProvider = TeacherModule_ProvideBookFactory.create(builder.teacherModule);    this.provideStudentProvider =        TeacherModule_ProvideStudentFactory.create(builder.teacherModule, provideBookProvider);    this.teacherMembersInjector = Teacher_MembersInjector.create(provideStudentProvider);  }

  从上面你的源码可以看到,首先我们在build方法判断teacherModule属性是否为空,如果为空则自己new一个TeacherModule对象(那这里是不是表示我们之前的teacherModule(new TeacherModule())方法可以偷懒不用写?  大家可以去试一试,的确可以的,手动微笑....),然后调用DaggerTeacherComponent类中的构造函数,在构造函数中调用initialize()方法,好了这里是重点了,在这里我们赋值了provideBookProvider、provideStudentProvider两个成员对象,所以我们现在看看TeacherModule_ProvideBookFactory 、TeacherModule_ProvideStudentFactory中的create方法干了什么

package com.qianmo.rxjavatext;import dagger.internal.Factory;import dagger.internal.Preconditions;import javax.annotation.Generated;@Generated(  value = "dagger.internal.codegen.ComponentProcessor",  comments = "https://google.github.io/dagger")public final class TeacherModule_ProvideBookFactory implements Factory
{ private final TeacherModule module; public TeacherModule_ProvideBookFactory(TeacherModule module) { assert module != null; this.module = module; } @Override public Book get() { return Preconditions.checkNotNull( module.provideBook(), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory
create(TeacherModule module) { return new TeacherModule_ProvideBookFactory(module); }}

  

package com.qianmo.rxjavatext;import dagger.internal.Factory;import dagger.internal.Preconditions;import javax.annotation.Generated;import javax.inject.Provider;@Generated(  value = "dagger.internal.codegen.ComponentProcessor",  comments = "https://google.github.io/dagger")public final class TeacherModule_ProvideStudentFactory implements Factory
{ private final TeacherModule module; private final Provider
bookProvider; public TeacherModule_ProvideStudentFactory(TeacherModule module, Provider
bookProvider) { assert module != null; this.module = module; assert bookProvider != null; this.bookProvider = bookProvider; } @Override public Student get() { return Preconditions.checkNotNull( module.provideStudent(bookProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory
create(TeacherModule module, Provider
bookProvider) { return new TeacherModule_ProvideStudentFactory(module, bookProvider); }}

  大家请看这两个类中的get方法中的参数!!!!  首先TeacherModule_ProvideBookFactory中调用的是module.provideBook(),获取到我们的book创建的对象,在看TeacherModule_ProvideStudentFactory中的get方法首先拿到TeacherModule_ProvideStudentFactory中的book对象,然后在调用 module.provideStudent(bookProvider.get())方法,拿到TeacherModule中创建的Student对象。

  ok现在基本流程清楚了,最后调用injectA(this)方法在Teacher_MembersInjector类中把当前的Teacher对象中的student属性被赋值到TeacherModule_ProvideStudentFactory.get()上去,这样我们的赋值就完成了。

  那么这里我们可以有一下结论

我们有两种方式可以提供依赖,一个是注解了@Inject的构造方法,一个是在Module里提供的依赖,那么Dagger2是怎么选择依赖提供的呢,规则是这样的:步骤1:查找Module中是否存在创建该类的方法。步骤2:若存在创建类方法,查看该方法是否存在参数步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束概括一下就是从注解了@Inject的对象开始,从Module和注解过的构造方法中获得实例,若在获取该实例的过程中需要其他类的实例,则继续获取被需要类的实例对象的依赖,同样是从Module和标注过的构造方法中获取,并不断递归这个过程直到所有被需要的类的实例创建完成,在这个过程中Module的优先级高于@Inject注解过的构造方法。

  这样我们就懂了为什么同事存在Module和@Inject都不会报错,且它对Module和@Inject提供对象实例的优先级关系了。

3、@Qulifier的使用

  现在继续扩展业务,由于学校扩招,现在任课老师下面由之前的一个学生变成了两个学生,且新来的那个学生是没有书籍的,那么在我们java代码中怎么表示这种场景呢?

package com.qianmo.rxjavatext;import javax.inject.Inject;/** * Created by Administrator on 2017/4/20 0020. * E-Mail:543441727@qq.com */public class Teacher {    //想持有学生对象    @Inject    Student student1; //带了书的    @Inject    Student student2; //没带了书的    public Teacher() {        DaggerTeacherComponent.builder().teacherModule(new TeacherModule()).build().injectA(this);    }    public void teacher() {        student1.startLessons();        student2.startLessons();    }    public static void main(String[] args) {        new Teacher().teacher();    }}

  自该修改Student中的构造函数,添加不带书的构造方法

public Student(Book book) {        System.out.println("Student create with book!!!");        this.book = book;    }    public Student() {        System.out.println("Student create without book!!!");        this.book = new Book("对不起我没有书啊");    }

  这时候我们想是想student2获取的是Student()的无参构造的对象,所以我们相当然的在我们的Module中添加对应的提供方法,代码如下:

package com.qianmo.rxjavatext;import dagger.Module;import dagger.Provides;/** * Created by Administrator on 2017/4/20 0020. * E-Mail:543441727@qq.com */@Modulepublic class TeacherModule {    @Provides    Book provideBook() {        return new Book("我是真实的书籍呢...");    }    /**     * 提供有书的学生     * @param book     * @return     */    @Provides    Student provideStudentA(Book book) {        return new Student(book);    }    /**     * 提供没书的学生     * @return     */    @Provides    Student provideStudentB() {        return new Student();    }}

  然后你开开心心的运行项目,会发现直接报错了,报错如下:

Error:(10, 9) Gradle: 错误: com.qianmo.rxjavatext.Student is bound multiple times:@Provides com.qianmo.rxjavatext.Student com.qianmo.rxjavatext.TeacherModule.provideStudentA(com.qianmo.rxjavatext.Book)@Provides com.qianmo.rxjavatext.Student com.qianmo.rxjavatext.TeacherModule.provideStudentB()

  明面字义翻译过来就是“绑定的时候迷失了自己在provideStudentA、provideStudentB方法之间”,我们专业术语叫“做依赖迷失”,因为Dagger2是根据返回类型来进行依赖注入的,但是这里我们提供了A、B两个方法返回相同的对象,这时候我们的student1、studnet2不知道他们自己到底是要创建有书的对象呢还是没有书的对象呢。

  so,为了解决这个问题,我们的Dagger2提供了@Qulifier,可以通过自定义注解,来告诉Dagger2我这次到底是想依赖那个方法,这里不会注解的同学可以去看一下我之前写的,那我们就来创建StudentA和StudnetB自定义注解

package com.qianmo.rxjavatext;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import javax.inject.Qualifier;/** * Created by Administrator on 2017/4/21 0021. * E-Mail:543441727@qq.com */@Qualifier@Retention(RetentionPolicy.RUNTIME)public @interface StudentA {}

  

package com.qianmo.rxjavatext;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import javax.inject.Qualifier;/** * Created by Administrator on 2017/4/21 0021. * E-Mail:543441727@qq.com */@Qualifier@Retention(RetentionPolicy.RUNTIME)public @interface StudentB {}

  然后在Module中添加这个注解

/**     * 提供有书的学生     * @param book     * @return     */    @StudentA    @Provides    Student provideStudentA(Book book) {        return new Student(book);    }    /**     * 提供没书的学生     * @return     */    @StudentB    @Provides    Student provideStudentB() {        return new Student();    }

  在需要使用student对象的地方添加

//想持有学生对象    @Inject    @StudentA    Student student1; //带了书的    @Inject    @StudentB    Student student2; //没带了书的

  ok,运行一下项目,看一下打印效果

Student create with book!!!Student create without book!!!开始上课了我一本新书被翻黑了。。。。我是真实的书籍呢...开始上课了我一本新书被翻黑了。。。。对不起我没有书啊

  没问题,这里我们Dagger2为了让我们使用方便实际上是提供了一个@Name标签供我们使用的,看一下它里面的内容

@Qualifier@Documented@Retention(RUNTIME)public @interface Named {    /** The name. */    String value() default "";}

  在底层也是使用了我们@Qualifier标签的,所以如果使用@Name标签来实现我们上面的效果的话是这样的

/**     * 提供有书的学生     * @param book     * @return     */    @Provides    @Named("StudentA")    Student provideStudentA(Book book) {        return new Student(book);    }    /**     * 提供没书的学生     * @return     */    @Provides    @Named("StudentB")    Student provideStudentB() {        return new Student();    }

  

//想持有学生对象    @Inject    @Named("StudentA")    Student student1; //带了书的    @Inject    @Named("StudentB")    Student student2; //没带了书的

  哈哈哈,看到这儿有没有同学想说我是马后炮的(其实,只能我们自己知道了它是怎么实现的才能更好的使用),说你直接介绍@Name标签不就行了,但是不利于我们之后的深层次的学习。

4,@Scope的使用

  这个标签不是很好理解,给的解释是该注解能够使同一个Component中的对象保持唯一,即单例(一定要记住是局部单例)。

  那么我们来写一个栗子试试

  首先继续自定义注解,并添加

package com.qianmo.rxjavatext;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import javax.inject.Qualifier;import javax.inject.Scope;/** * Created by Administrator on 2017/4/21 0021. * E-Mail:543441727@qq.com */@Scope@Retention(RetentionPolicy.RUNTIME)public @interface StudentOnlyOne {}

  在B中添加方法标签的引用

/**     * 提供没书的学生     * @return     */    @Provides    @StudentOnlyOne    @Named("StudentB")    Student provideStudentB() {        return new Student();    }

  修改Teacher中的两个学生变量的使用,都是用B方法提供Student对象

@Inject    @Named("StudentB")    Student student1; //带了书的    @Inject    @Named("StudentB")    Student student2; //没带了书的

  最后,很重要!!!在你的component中添加上面标注!!!!,不然你就等着bug满天飞吧(在这儿趟坑了很久)

package com.qianmo.rxjavatext;import dagger.Component;/** * Created by Administrator on 2017/4/20 0020. * E-Mail:543441727@qq.com */@StudentOnlyOne@Component(modules = TeacherModule.class)public interface TeacherComponent {    void injectA(Teacher teacher);}

  ok,运行一下,看一下打印效果,

Student create without book!!!开始上课了我一本新书被翻黑了。。。。对不起我没有书啊开始上课了我一本新书被翻黑了。。。。对不起我没有书啊

  书的确只被创建了一次,ok,其实我们Dagger2中也提供了封装好了的@Scope,就是我们的@Singleton,看一下它的源码

@Scope@Documented@Retention(RUNTIME)public @interface Singleton {}

  也是应用了@Scope标签,对不起,又马后炮了一次(哈哈哈........)

  到这里我们的Dagger2的标签基本上就全部学习完了,后面一篇我将和大家一起看看,在Android中的Activity ,Dagger2能帮我们做些什么

  最后!!!还有一个很关键事:最近打算辞职回北京,有没有大神同学推荐工作,带带啊啊啊啊啊

   下一篇:

 

转载地址:http://zzfcl.baihongyu.com/

你可能感兴趣的文章
Java实现文件复制
查看>>
Spark修炼之道(基础篇)——Linux大数据开发基础:第三节:用户和组
查看>>
Linux下Power Management开发总结
查看>>
安装服务器安全狗教程
查看>>
使用CodePush实时更新 React Native 和 Cordova 应用
查看>>
jstree -- 使用JSON 数据组装成树
查看>>
VM 安装 linux Enterprise_R5_U4_Server_I386_DVD教程图解
查看>>
PHP怎么获取系统信息和服务器详细信息
查看>>
iOS开发如何学习前端(1)
查看>>
『科学计算』层次聚类实现
查看>>
selenium用jquery改变元素属性
查看>>
一个支付流程要考虑到哪些测试点?
查看>>
某书2018笔试题之薯券
查看>>
对request.getSession(false)的理解(附程序员常疏忽的一个漏洞)
查看>>
手机端仿ios的日期组件脚本一
查看>>
Appium做Android功能自动化测试
查看>>
reentrantlock用于替代synchronized
查看>>
Nginx 安装部署
查看>>
三目运算符详解
查看>>
HTML中button和input button的区别
查看>>