通过前面一节的介绍,我们学习了关于Dagger2的一些基本概念和简单使用方法,对Dagger2有了一个初步的认识。而对于我们在工程中的实际使用来说,掌握基本用法是远远不够的,接下来我们继续介绍Dagger2的进阶用法。
在介绍Dagger2进阶用法之前,我们先回顾一下使用Dagger2的几个关键步骤: 首先目标类里要使用 Inject表示出自己需要进行注入的对象;然后需要在依赖对象类中使用 Inject来标识构造方法,或者在Module中使用 Provides 标识对象的生成方法。最后需要一个 Component作为桥梁将生成的对象赋值给目标类的依赖成员。由此可见,Dagger中最关键的三要素如下图所示,后续将要介绍的进阶用法也是围绕着这三要素来展开的:
我们在实际的工程开发中,目标类对于依赖对象的需求是多种多样的,比如很多类有多个构造方法,如下代码示例,应该使用哪个构造方法以及如何标识不同的构造方法呢,这个就是接下来将要介绍的 Qualifier 标识符所起的作用。
@Module
public class BodyModule {
@Provides
public Body provideBody() {
return new Body();
}
@Provides
public Body provideBody(Leg leg) {
return new Body(leg);
}
@Provides
public Leg provideLeg() {
return new Leg();
}
}
Qualifier 是元注解,也就是注解的注解,用来定义不同的注解名字来对不同的构造方法进行区分,如下代码所示,我们使用Qualifier标注了两个注解,分别为ProvideBody ,ProvideNewBody :
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideBody {
}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideNewBody {
}
我们需要将上述定义的两个注解分别标识在 Module中两个构造方法上,以此来标识两个不同的构造方法。另外需要在目标类依赖实例的地方标识需要使用哪个构造方法,这样就能使用指定的方法来创建依赖实例了,Qualifier的使用还是比较简单的,代码如下示例:
@Module
public class BodyModule {
@Provides
@ProvideBody
public Body provideBody() {
return new Body();
}
@Provides
@ProvideNewBody
public Body provideNewBody(Leg leg) {
return new Body(leg);
}
@Provides
public Leg provideLeg() {
return new Leg();
}
}
public class People {
@Inject
@ProvideBody
Body mBody;
public People() {
}
}
假如在目标类中依赖的对象要求是单例的,在一定的生命周期内使用同一个对象,使用Dagger2应该如何做呢。根据之前基础使用方法中的介绍,每次我们调用 component 的 inject方法时,都会新创建一个对象来注入。如果我们想使用一个实例,那么就需要在创建了一个实例之后,后续每次使用都返回同一个对象而不是重新创建。如何达到这一目的呢,这里就需要用到 Scope 注解 。Scope 顾名思义是作用域,用于标注一个对象的作用域。Scope也是一个元注解,首先用Scope 来定义一个注解:
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PeopleScope {
}
然后将这个定义好的注解 PeopleScope 标注在 Component 以及 Module 的构造方法上。
@Module
public class BodyModule {
@Provides
@ProvideBody
@PeopleScope
public Body provideBody() {
return new Body();
}
.....
}
@Component(modules = BodyModule.class)
@PeopleScope
public interface PeopleComponent {
void injectPeople(People people);
}
这样标注之后和之前有什么区别呢,这里就需要我们来看Dagger生成的DaggerPeopleComponent源码了,为了精简篇幅,这里我们只截取核心的源码部分
public final class DaggerPeopleComponent implements PeopleComponent {
private Provider<Body> provideBodyProvider;
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.provideBodyProvider =
DoubleCheck.provider(BodyModule_ProvideBodyFactory.create(builder.bodyModule)); // 通过DoubleCheck进行一次包装
}
@Override
public void injectPeople(People people) {
injectPeople2(people);
}
private People injectPeople2(People instance) {
People_MembersInjector.injectMBody(instance, provideBodyProvider.get());
return instance;
}
......
}
通过这段核心代码,我们看到生成的 DaggerPeopleComponent 和之前不同的地方是调用了 DoubleCheck.provider 方法对provider进行了包装,那么 DoubleCheck 做了什么工作呢,继续看DoubleCheck的源码:
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance;
private DoubleCheck(Provider<T> provider) {
this.instance = UNINITIALIZED;
assert provider != null;
this.provider = provider;
}
public T get() { // 实现单例
Object result = this.instance;
if(result == UNINITIALIZED) {
synchronized(this) {
result = this.instance;
if(result == UNINITIALIZED) {
result = this.provider.get();
this.instance = reentrantCheck(this.instance, result);
this.provider = null;
}
}
}
return result;
}
.......
}
看到这段代码,想必大家已经快明白了,这个get方法不就是单例的写法么,如果已经创建过一次实例,后续每次调用get方法,返回的都是第一次创建的实例。如此在 DaggerPeopleComponent 中每次调用inject方法时,都会调用到这个get方法,从而达到使用同一实例的目的。另外强调一句,这里的单例只是对于同一个 Component对象来说的,在同一个 Component对象的生命周期里,持有的依赖对象为同一个。不同的Component对象中的自然就是不同的了。另外强调一下使用Scope的注意事项:Module 中 provide 方法的 Scope 注解和 与之绑定的 Component 的 Scope 需要保持一致,否则作用域不同会导致编译时会报错。
听到这里可能还存在很多疑问,每个目标类依赖都有对应的 Component类,每个Component对象都持有相应的依赖对象,如果某个Component对象希望和另外一个Component对象共享依赖实例,应该如何做呢,接下来我们学习Component的组织关系,用于解决Component。Component拥有两种关系依赖关系和继承关系:一个Component可以依赖于一个或多个Component,依赖关系通过Component中的dependencies属性来实现,具体用法通过一个例子来看一下;
本例中 People 和 Animal中都要依赖一个 Body类型的对象,而且Animal 要和People 共用同一个Body对象。首先要在 PeopleComponent中增加一个 提供Body对象的接口,在AnimalComponent 中要使用dependencies 属性标明 依赖 PeopleComponent , 最后注意 PeopleComponent 和 AnimalComponent都需要标注 Scope,而且两者的Scope不能相同。
@Component(modules = BodyModule.class)
@PeopleScope
public interface PeopleComponent {
void injectPeople(People people);
Body getBody();
}
@Component(dependencies = PeopleComponent.class)
@AnimalScope
public interface AnimalComponent {
void inject(Animal animal);
}
然后编译一下,来详看生成的DaggerAnimalComponent 中的代码,出于篇幅考虑仅贴出部分核心代码,首先我们看到Builder类中增加了一个成员 PeopleComponent, 用于传入PeopleComponent 实例,在inject方法中,获取依赖实例时调用的是传入的peoplecomponent的get方法,因此AnimalComponent 和 PeopleComponent共享了同一个 Body的实例:
public final class DaggerAnimalComponent implements AnimalComponent {
private PeopleComponent peopleComponent;
......
@Override
public void inject(Animal animal) {
injectAnimal(animal);
}
private Animal injectAnimal(Animal instance) {
Animal_MembersInjector.injectMBody(
instance,
Preconditions.checkNotNull(
peopleComponent.getBody(), "Cannot return null from a non-@Nullable component method"));
return instance;
}
public static final class Builder {
private PeopleComponent peopleComponent;
private Builder() {}
public AnimalComponent build() {
if (peopleComponent == null) {
throw new IllegalStateException(PeopleComponent.class.getCanonicalName() + " must be set");
}
return new DaggerAnimalComponent(this);
}
public Builder peopleComponent(PeopleComponent peopleComponent) {
this.peopleComponent = Preconditions.checkNotNull(peopleComponent);
return this;
}
}
}
使用方法如下所示
PeopleComponent peopleComponent = DaggerPeopleComponent.builder().build();
AnimalComponent animalComponent = DaggerAnimalComponent.builder().peopleComponent(peopleComponent).build();
animalComponent.inject(this);
上面介绍的是Component 的依赖关系,Component还有一种继承关系,也可达到 Component之间共享依赖实例的目的。如果使AnimalComponent继承PeopleComponent,使用方式如下,继承关系的使用相对复杂一些。首先 使用 SubComponent标注AnimalComponent,AnimalComponent 中增加一个 Builder接口并用@Subcomponent.Builder标注。PeopleComponent中增加一个方法返回类型为 AnimalComponent.Builder,用于外部创建AnimalComponent 对象使用。最后要在PeopleComponent所使用的module中标识 subcomponents = AnimalComponent.class,自此就建立起来 AnimalComponent 和 PeopleComponent之间的继承关系:
@Component(modules = BodyModule.class)
@PeopleScope
public interface PeopleComponent {
void injectPeople(People people);
AnimalComponent.Builder animalComponent();
}
@Module(subcomponents = AnimalComponent.class)
public class BodyModule {
@Provides
@PeopleScope
public Body provideBody() {
return new Body();
}
}
@Subcomponent
@AnimalScope
public interface AnimalComponent {
void inject(Animal animal);
@Subcomponent.Builder
interface Builder {
AnimalComponent build();
}
}
我们还是编译一下看生成的核心源码, 这里看到有个区别,只生成了 DaggerPeopleComponent 类,没有生成 DaggerAnimalComponent类。创建AnimalComponent 对象的方法在 DaggerPeopleComponent 中,DaggerPeopleComponent中的AnimalComponentImpl 是AnimalComponent 接口的实现类,其中的injectAnimal方法 注入的对象是由DaggerPeopleComponent 中的provideBodyProvider提供的,由此实现了 AnimalComponent和PeopleComponent共享依赖实例的目的。
public final class DaggerPeopleComponent implements PeopleComponent {
private Provider<Body> provideBodyProvider;
......
public static PeopleComponent create() {
return new Builder().build();
}
@Override
public void injectPeople(People people) {
injectPeople2(people);
}
@Override
public AnimalComponent.Builder animalComponent() {
return new AnimalComponentBuilder();
}
private People injectPeople2(People instance) {
People_MembersInjector.injectMBody(instance, provideBodyProvider.get());
return instance;
}
private final class AnimalComponentBuilder implements AnimalComponent.Builder {
@Override
public AnimalComponent build() {
return new AnimalComponentImpl(this);
}
}
private final class AnimalComponentImpl implements AnimalComponent {
private AnimalComponentImpl(AnimalComponentBuilder builder) {}
@Override
public void inject(Animal animal) {
injectAnimal(animal);
}
private Animal injectAnimal(Animal instance) {
Animal_MembersInjector.injectMBody(
instance, DaggerPeopleComponent.this.provideBodyProvider.get());
return instance;
}
}
}
上面介绍了 Component 组织关系的两种方式,在APP的开发中,有非常多的类都有依赖对象,总不能每一个目标类都配一个Component吧。应该以什么样的原则来划分Component呢,Component应该划分为多小的粒度呢,一般会遵循如下的组织原则:
创建一个全局的Component(ApplicationComponent), 在application中对其进行实例化,一般会在这个component中用来管理APP中的全局类实例。
对于每个页面创建一个Component,一个Activity页面定义一个Component,一个Fragment定义一个Component,使这些component继承自applicationComponent。
这部分内容可以参考一下 实例,https://github.com/googlesamples/android-architecture/tree/todo-mvp, 这个实例中展示了如何在APP中使用Dagger2,在此不再展开详述。
通过上面对Dagger2进阶用法的介绍,可能大家心中依然存在一些疑惑,从入门到这里有点想放弃。Dagger2的使用需要在每个目标类里重复写大量的模板代码,这与Dagger2解耦,减少重复劳动的目标是有一定背离的。如何在APP开发中更加简洁,方便的使用Dagger2呢,接下来将要继续介绍的Dagger-android框架,将会继续详细阐述这一问题的解决方法,使Dagger2在APP开发中的使用更加简洁。