Dagger2笔记--讲道理我差点就放弃了

为什么要使用Dagger2呢

其实是自己立的flag , 哭着也要学完(这是三个月之前的flag)
曾经我哭着看老大些的dagger2代码问他 , 你丫的直接new不就行了么骚什么骚。直到我后来自己封装网络框架的时候发现,dagger2是真的舒服,直接省去了new一大坨东西的烦恼,而且对应不同的场景也能快速做出适应,一个@Inject就可以用到你需要用的东西,而针对目标对象的修改是和调用完全没有关系的。你想怎么改怎么改,反正我一个inject就能用。

从最简单的例子来入手

别上来就被一大堆眼花缭乱的注解吓到了,咱们由浅入深来看。
需求: 调用一个对象,省略实例化的过程
首先创建一个对象Student,提供一个方法输出log。

public class Student {

    Student() {
    }

    public void syso() {
        Log.e("syso", "syso: ");
    }
}

接下来使用dagger来辅助我们实例化对象。首先我们要接触两个注解

  1. @Inject
  2. @Component

怎么用啊这玩意儿,用在哪里呢?
直接看看Inject的实现,这是一个注解接口,代码也非常少

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}

说明了Annotation所修饰的对象范围。这里标注了范围为:方法,构造,变量
Rentention:在运行时有效(即运行时保留)
这就说明我们以后再使用@Inject的时候,可能会出现在三个地方。
首先,我们对Student进行改造,在构造方法标上@inject的注解

public class Student {
    @Inject
    Student() {
    }

    public void syso() {
        Log.e("syso", "syso: ");
    }
}

然后我们看看Component的代码

@Retention(RUNTIME) // Allows runtimes to have specialized behavior interoperating with Dagger.
@Target(TYPE)
@Documented
public @interface Component {

  Class<?>[] modules() default {};
  Class<?>[] dependencies() default {};

  @Target(TYPE)
  @Documented
  @interface Builder {}
}

首先看Target,用于描述类、接口(包括注解类型) 或enum声明。
然后内部还有三个方法,这个以后再说。
然后我们创建一个自定义的StuComponent用来作为注解接口,同时加注解

@Component
public interface StuComponent {
}

有了这些准备工作之后,我们首先构建一次工程make project,让apt自动生成相关的代码,然后编写测试类College,同时在StuComponent中编写任意方法返回void,传入参数为测试类。

@Component
public interface StuComponent {
    void inject(College college);
}

public class College {
    @Inject
    Student student;

    public College() {
        // DaggerStuComponent为make之后自动生成的代码,build之后调用注册的方法,将自己传进去。
        DaggerStuComponent.builder().build().inject(this);
    }

    public void test(){
        student.syso();
    }
}

最后直接调用College的test方法,即可拿到输出结果


是不是感觉一愣一愣的,这里来小结一下:

  1. 首先对于想要实例化的对象,在构造函数处标注@Inject
  2. 创建接口并标注@Component
  3. 构建工程(这个过程会自动生成代码)
  4. 在Component中编写范围值为void,传入参数为A的函数,A为需要使用对象的类
  5. 在A中使用Dagger,同时直接@Inject声明对象,即可使用了

全程没有通过实例化目标对象,但真正的拿到了对象的实例,这就是Dagger2的最基本的使用了。接下来我们看看在Make的时候都发生了什么事。
首先回到College类中, 查看College在哪里被使用了, 我们发现了一个叫College_MembersInjector的类 , 点开该类,我们可以找到一个叫injectMembers的方法,在这个方法中,判空之后使用instance.student = studentProvider.get();来对student赋值。那么此时的student是谁呢,继续跟踪student这个变量,发现我们回到了College中的被标记的变量。这就说明@Inject标记的变量不能声明为private,否则Injector无法访问。
接下来看Student这个类的构造函数,可以跟踪到一个枚举类Student_Factory

public enum Student_Factory implements Factory<Student> {
  INSTANCE;

  @Override
  public Student get() {
    return new Student();
  }

  public static Factory<Student> create() {
    return INSTANCE;
  }
}

一看名字就知道是个工厂类,在枚举的get方法实例化对象。好了,找到对象的创建以及使用的地方,那么这两者是如何同时工作的呢?回到我们创建的Component中,跟踪接口的实现:

public final class DaggerStuComponent implements StuComponent {
  private MembersInjector<College> collegeMembersInjector;

  private DaggerStuComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static StuComponent create() {
    return builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {
    this.collegeMembersInjector = College_MembersInjector.create(Student_Factory.create());
  }

  @Override
  public void inject(College college) {
    collegeMembersInjector.injectMembers(college);
  }

  public static final class Builder {
    private Builder() {}

    public StuComponent build() {
      return new DaggerStuComponent(this);
    }
  }
}

这个类不就是我们在College中使用Dagger的类么,来看看我们怎么用的
DaggerStuComponent.builder().build().inject(this);据此,我们来跟一边代码:

首先Builder,build()方法,构建了一个DaggerStuComponent的实例,那么在DaggerStuComponent的构造方法又调用了initialize,然后又this.collegeMembersInjector = College_MembersInjector.create(Student_Factory.create());穿件了College_MembersInjector,并传入Student_Factory的单例,最终实例化MembersInjector这个注解器。构建完毕后又调用了inject方法,继续跟进,发现最后回到了College_MembersInjector的injectMembers方法。这个方法是不是有点眼熟?之前我们在上文已经见过了,这就是最终实例化对象的地方。
饶了一大圈,我们简单概括一下:
inject标记在构造函数上,用来声明可以被引用;标记在变量上意味着引用,Component意味着实例化与调用之间的桥梁。具体的构建方法类似于工厂模式。


第二个例子:无法修改构造函数的对象

比如我们需要使用Retrofit,哎呀,这是库啊,源码是反编译后的,不能修改,这种情况下能不能使用Dagger呢?我们就需要使用一些新的注解了。
需求:调用一个对象,省略实例化的过程,且不修改构造函数

  1. 创建一个对象,创建好就不允许修改了
public class Car {
    public Car() {
    }
    public void log(){
        Log.e("Car", "Car: ");
    }
}
  1. 创建一个带Moudle注解的类,并提供Car的实例化:
@Module
public class CarMoudle {

    @Provides
    Car providerCar() {
        return new Car();
    }
}
  1. 创建Component,用来搭建实例化与调用的桥梁,并制定Moudle
@Component(modules = CarMoudle.class)
public interface CarComponent {
    void inject(CarTest car);
}
  1. 测试,同样需要先用建造者模式构建Dagger,然后就可以直接inject了
public class CarTest {
    @Inject Car car;

    public void test(){
        DaggerCarComponent.builder().carMoudle(new CarMoudle()).build().inject(this);
        car.log();
    }
}

运行结果如图所示



这个过程同样涉及到了三个类。首先来看Moudle类

  1. Moudle注解,细节上面说过了。这个注解可以给不能修改构造函数的类提供依赖
  2. @Providers同样类似,内部提供枚举。被Providers标记的方法返回的对象,就是需要依赖的对象的实例
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Module {
  Class<?>[] includes() default {};
}
  1. 看Component类同样作为桥梁,和上面类似但是有不太一样,单独摘出来
    @Component(modules = CarMoudle.class)
    在上一个demo中,我们提到了Component可以接收以下的参数
  • modules: 指定对应的modules
  • dependencies: 指定继承

这里我们指定了modules = CarMoudle.class

  1. 看测试类的Dagger的使用也有一些不一样,因为DaggerCarComponent是基于Component来生成的,但是使用到modules,所以在使用时额外要配置DaggerCarComponent.builder().carMoudle(new CarMoudle()).build().inject(this);

总结下使用方法:

  1. 新建Module类,标记@Module注解,同时编写返回目标对象的方法,标记@Providers注解
  2. 创建Component接口,标记@Component,指定参数modules
  3. 在测试类中构建Dagger,一定要传入module,然后注入。之后@Inject需要调用的对象
  4. 在Component中编写注入方法

接下来我们分析过程中的代码。
首先是CarModule类(我们无法进入Car类内部,所以从这个类开始分析),在这个类中我们跟踪到了相关的Factory类

public final class CarMoudle_ProviderCarFactory implements Factory<Car> {
  private final CarMoudle module;

  public CarMoudle_ProviderCarFactory(CarMoudle module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public Car get() {
    return Preconditions.checkNotNull(
        module.providerCar(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<Car> create(CarMoudle module) {
    return new CarMoudle_ProviderCarFactory(module);
  }
}

构造函数中实例化Moudle,然后get方法返回了Car实例,与之前的类似。
然后跟进CarComponent的实现类:

public final class DaggerCarComponent implements CarComponent {
  private Provider<Car> providerCarProvider;

  private MembersInjector<CarTest> carTestMembersInjector;

  private DaggerCarComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static CarComponent create() {
    return builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.providerCarProvider = CarMoudle_ProviderCarFactory.create(builder.carMoudle);

    this.carTestMembersInjector = CarTest_MembersInjector.create(providerCarProvider);
  }

  @Override
  public void inject(CarTest car) {
    carTestMembersInjector.injectMembers(car);
  }

  public static final class Builder {
    private CarMoudle carMoudle;

    private Builder() {}

    public CarComponent build() {
      if (carMoudle == null) {
        this.carMoudle = new CarMoudle();
      }
      return new DaggerCarComponent(this);
    }

    public Builder carMoudle(CarMoudle carMoudle) {
      this.carMoudle = Preconditions.checkNotNull(carMoudle);
      return this;
    }
  }
}

同样,我们在测试类中使用的方法正式这个类的一些方法。
DaggerCarComponent.builder().carMoudle(new CarMoudle()).build().inject(this);不一样的是这里传入了一个Module,最终还是通过build方法创建了CarComponent的对象。然后注入,调用了CarTest_MembersInjector的injectMembers方法注入。注入之后,通过instance.car = carProvider.get();将实例化的值赋值给测试类的对象。
基本上和@Inject的过程是一模一样的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容