Dagger2 系列(二)Dagger2 進階使用

通過前面一節(jié)的介紹,我們學習了關于Dagger2的一些基本概念和簡單使用方法拦止,對Dagger2有了一個初步的認識糜颠。而對于我們在工程中的實際使用來說萧求,掌握基本用法是遠遠不夠的其兴,接下來我們繼續(xù)介紹Dagger2的進階用法夸政。

在介紹Dagger2進階用法之前元旬,我們先回顧一下使用Dagger2的幾個關鍵步驟: 首先目標類里要使用 Inject表示出自己需要進行注入的對象守问;然后需要在依賴對象類中使用 Inject來標識構造方法,或者在Module中使用 Provides 標識對象的生成方法耗帕。最后需要一個 Component作為橋梁將生成的對象賦值給目標類的依賴成員穆端。由此可見仿便,Dagger中最關鍵的三要素如下圖所示体啰,后續(xù)將要介紹的進階用法也是圍繞著這三要素來展開的:


Dagger2三要素

我們在實際的工程開發(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應該劃分為多小的粒度呢拦惋,一般會遵循如下的組織原則:

  1. 創(chuàng)建一個全局的Component(ApplicationComponent), 在application中對其進行實例化安寺,一般會在這個component中用來管理APP中的全局類實例。

  2. 對于每個頁面創(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ā)中的使用更加簡潔甫贯。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叫搁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渴逻,老刑警劉巖疾党,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惨奕,死亡現(xiàn)場離奇詭異,居然都是意外死亡梨撞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門卧波,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人幽勒,你說我怎么就攤上這事∩度荩” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵咪惠,是天一觀的道長。 經常有香客問我遥昧,道長,這世上最難降的妖魔是什么炭臭? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮鞋仍,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己落午,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布溃斋。 她就那樣靜靜地躺著,像睡著了一般梗劫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上在跳,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音猫妙,去河邊找鬼。 笑死割坠,一個胖子當著我的面吹牛,可吹牛的內容都是我干的彼哼。 我是一名探鬼主播对妄,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼敢朱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拴签?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蚓哩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后岸梨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡曹阔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赃份。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出园蝠,到底是詐尸還是另有隱情渺蒿,我是刑警寧澤彪薛,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站善延,受9級特大地震影響少态,放射性物質發(fā)生泄漏易遣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一豆茫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧揩魂,春花似錦幽邓、人聲如沸火脉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畸颅。三九已至妒峦,卻和暖如春重斑,著一層夾襖步出監(jiān)牢的瞬間肯骇,已是汗流浹背窥浪。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工笛丙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胚鸯。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坦冠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內容