为什么要使用Dagger2呢
其实是自己立的flag , 哭着也要学完(这是三个月之前的flag)
曾经我哭着看老大些的dagger2代码问他 , 你丫的直接new不就行了么骚什么骚。直到我后来自己封装网络框架的时候发现,dagger2是真的舒服,直接省去了new一大坨东西的烦恼,而且对应不同的场景也能快速做出适应,一个@Inject就可以用到你需要用的东西,而针对目标对象的修改是和调用完全没有关系的。你想怎么改怎么改,反正我一个inject就能用。
从最简单的例子来入手
别上来就被一大堆眼花缭乱的注解吓到了,咱们由浅入深来看。
需求: 调用一个对象,省略实例化的过程
首先创建一个对象Student,提供一个方法输出log。
public class Student {
Student() {
}
public void syso() {
Log.e("syso", "syso: ");
}
}
接下来使用dagger来辅助我们实例化对象。首先我们要接触两个注解
- @Inject
- @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方法,即可拿到输出结果
是不是感觉一愣一愣的,这里来小结一下:
- 首先对于想要实例化的对象,在构造函数处标注@Inject
- 创建接口并标注@Component
- 构建工程(这个过程会自动生成代码)
- 在Component中编写范围值为void,传入参数为A的函数,A为需要使用对象的类
- 在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呢?我们就需要使用一些新的注解了。
需求:调用一个对象,省略实例化的过程,且不修改构造函数
- 创建一个对象,创建好就不允许修改了
public class Car {
public Car() {
}
public void log(){
Log.e("Car", "Car: ");
}
}
- 创建一个带Moudle注解的类,并提供Car的实例化:
@Module
public class CarMoudle {
@Provides
Car providerCar() {
return new Car();
}
}
- 创建Component,用来搭建实例化与调用的桥梁,并制定Moudle
@Component(modules = CarMoudle.class)
public interface CarComponent {
void inject(CarTest car);
}
- 测试,同样需要先用建造者模式构建Dagger,然后就可以直接inject了
public class CarTest {
@Inject Car car;
public void test(){
DaggerCarComponent.builder().carMoudle(new CarMoudle()).build().inject(this);
car.log();
}
}
运行结果如图所示
这个过程同样涉及到了三个类。首先来看Moudle类
- Moudle注解,细节上面说过了。这个注解可以给不能修改构造函数的类提供依赖
- @Providers同样类似,内部提供枚举。被Providers标记的方法返回的对象,就是需要依赖的对象的实例
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Module {
Class<?>[] includes() default {};
}
- 看Component类同样作为桥梁,和上面类似但是有不太一样,单独摘出来
@Component(modules = CarMoudle.class)
在上一个demo中,我们提到了Component可以接收以下的参数
- modules: 指定对应的modules
- dependencies: 指定继承
这里我们指定了modules = CarMoudle.class
- 看测试类的Dagger的使用也有一些不一样,因为DaggerCarComponent是基于Component来生成的,但是使用到modules,所以在使用时额外要配置
DaggerCarComponent.builder().carMoudle(new CarMoudle()).build().inject(this);
总结下使用方法:
- 新建Module类,标记@Module注解,同时编写返回目标对象的方法,标记@Providers注解
- 创建Component接口,标记@Component,指定参数modules
- 在测试类中构建Dagger,一定要传入module,然后注入。之后@Inject需要调用的对象
- 在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的过程是一模一样的。