通過前面一節(jié)的介紹,我們學習了關于Dagger2的一些基本概念和簡單使用方法拦止,對Dagger2有了一個初步的認識糜颠。而對于我們在工程中的實際使用來說萧求,掌握基本用法是遠遠不夠的其兴,接下來我們繼續(xù)介紹Dagger2的進階用法夸政。
在介紹Dagger2進階用法之前元旬,我們先回顧一下使用Dagger2的幾個關鍵步驟: 首先目標類里要使用 Inject表示出自己需要進行注入的對象守问;然后需要在依賴對象類中使用 Inject來標識構造方法,或者在Module中使用 Provides 標識對象的生成方法耗帕。最后需要一個 Component作為橋梁將生成的對象賦值給目標類的依賴成員穆端。由此可見仿便,Dagger中最關鍵的三要素如下圖所示体啰,后續(xù)將要介紹的進階用法也是圍繞著這三要素來展開的:
我們在實際的工程開發(fā)中探越,目標類對于依賴對象的需求是多種多樣的,比如很多類有多個構造方法钦幔,如下代碼示例常柄,應該使用哪個構造方法以及如何標識不同的構造方法呢,這個就是接下來將要介紹的 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 是元注解,也就是注解的注解喷市,用來定義不同的注解名字來對不同的構造方法進行區(qū)分相种,如下代碼所示品姓,我們使用Qualifier標注了兩個注解,分別為ProvideBody 腹备,ProvideNewBody :
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideBody {
}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideNewBody {
}
我們需要將上述定義的兩個注解分別標識在 Module中兩個構造方法上,以此來標識兩個不同的構造方法植酥。另外需要在目標類依賴實例的地方標識需要使用哪個構造方法弦牡,這樣就能使用指定的方法來創(chuàng)建依賴實例了,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方法時淤毛,都會新創(chuàng)建一個對象來注入。如果我們想使用一個實例低淡,那么就需要在創(chuàng)建了一個實例之后,后續(xù)每次使用都返回同一個對象而不是重新創(chuàng)建蔗蹋。如何達到這一目的呢何荚,這里就需要用到 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);
}
這樣標注之后和之前有什么區(qū)別呢,這里就需要我們來看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 做了什么工作呢掂器,繼續(xù)看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() { // 實現(xiàn)單例
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方法不就是單例的寫法么狞谱,如果已經創(chuàng)建過一次實例禁漓,后續(xù)每次調用get方法跟衅,返回的都是第一次創(chuàng)建的實例播歼。如此在 DaggerPeopleComponent 中每次調用inject方法時,都會調用到這個get方法秘狞,從而達到使用同一實例的目的叭莫。另外強調一句烁试,這里的單例只是對于同一個 Component對象來說的,在同一個 Component對象的生命周期里减响,持有的依賴對象為同一個。不同的Component對象中的自然就是不同的了支示。另外強調一下使用Scope的注意事項:Module 中 provide 方法的 Scope 注解和 與之綁定的 Component 的 Scope 需要保持一致,否則作用域不同會導致編譯時會報錯颂鸿。
聽到這里可能還存在很多疑問,每個目標類依賴都有對應的 Component類嘴纺,每個Component對象都持有相應的依賴對象,如果某個Component對象希望和另外一個Component對象共享依賴實例颖医,應該如何做呢,接下來我們學習Component的組織關系裆蒸,用于解決Component熔萧。Component擁有兩種關系依賴關系和繼承關系:一個Component可以依賴于一個或多個Component僚祷,依賴關系通過Component中的dependencies屬性來實現(xiàn),具體用法通過一個例子來看一下辙谜;
本例中 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惧眠,用于外部創(chuàng)建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();
}
}
我們還是編譯一下看生成的核心源碼, 這里看到有個區(qū)別,只生成了 DaggerPeopleComponent 類秀存,沒有生成 DaggerAnimalComponent類。創(chuàng)建AnimalComponent 對象的方法在 DaggerPeopleComponent 中或链,DaggerPeopleComponent中的AnimalComponentImpl 是AnimalComponent 接口的實現(xiàn)類,其中的injectAnimal方法 注入的對象是由DaggerPeopleComponent 中的provideBodyProvider提供的澳盐,由此實現(xiàn)了 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的開發(fā)中掀淘,有非常多的類都有依賴對象油昂,總不能每一個目標類都配一個Component吧。應該以什么樣的原則來劃分Component呢冕碟,Component應該劃分為多小的粒度呢拦惋,一般會遵循如下的組織原則:
創(chuàng)建一個全局的Component(ApplicationComponent), 在application中對其進行實例化安寺,一般會在這個component中用來管理APP中的全局類實例。
對于每個頁面創(chuàng)建一個Component挑庶,一個Activity頁面定義一個Component,一個Fragment定義一個Component迎捺,使這些component繼承自applicationComponent举畸。
這部分內容可以參考一下 實例凳枝,https://github.com/googlesamples/android-architecture/tree/todo-mvp, 這個實例中展示了如何在APP中使用Dagger2岖瑰,在此不再展開詳述。
通過上面對Dagger2進階用法的介紹蹋订,可能大家心中依然存在一些疑惑,從入門到這里有點想放棄露戒。Dagger2的使用需要在每個目標類里重復寫大量的模板代碼,這與Dagger2解耦玫锋,減少重復勞動的目標是有一定背離的。如何在APP開發(fā)中更加簡潔撩鹿,方便的使用Dagger2呢,接下來將要繼續(xù)介紹的Dagger-android框架节沦,將會繼續(xù)詳細闡述這一問題的解決方法键思,使Dagger2在APP開發(fā)中的使用更加簡潔甫贯。