Dagger2:Scope, Component間依賴和subComponent

在上一篇文章(http://www.reibang.com/p/f56d5b7e8b4d)中碳却,我們接觸到了Dagger2的基本用法元莫。然而在實際Android開發(fā)當(dāng)中卵凑,還會有更進(jìn)一步的需求:需要有多個不同生命周期的多個Component,例如,有些依賴圖是全局單例的拖陆,而有些依賴圖會與Activity/Fragment同周期障本,或者有些依賴圖是要與用戶登錄同周期教届。在這些情況下,我們就需要自定義Scope來維護(hù)多個Component各自依賴圖的生命周期驾霜。更進(jìn)一步的案训,我們討論利用Component間的依賴和subComponent兩種方法來創(chuàng)建多個Component。

1. Scope

Scope注解是JSR-330標(biāo)準(zhǔn)中的粪糙,該注解是用來修飾其他注解的强霎。 前篇文章提到的@Singleton就是一個被Scope標(biāo)注的注解,是Scope的一個默認(rèn)實現(xiàn)蓉冈。
Scope的注解的作用脆栋,是在一個Component的作用域中,依賴為單例的洒擦。也就是說同一個Component對象向各個類中注入依賴時候椿争,注入的是同一個對象,而并非每次都new一個對象熟嫩。
在這里秦踪,我們再介紹自定義的Scope注解,如:

@Scope
public @interface ActivityScope {
}

如上掸茅,ActivityScope就是一個我們自己定義的Scope注解椅邓,其使用方式與上篇文章中我們用Singleton的用法類似的。顧名思義昧狮,在實際應(yīng)用中Singleton代表了全局的單例景馁,而我們定義ActivityScope代表了在Activity生命周期中相關(guān)依賴是單例的。
Scope的注解具體用法如下:
(1). 首先用其修飾Component
(2). 如果依賴由Module的Provides或Binds方法提供逗鸣,且該依賴在此Component中為單例的合住,則用Scope相關(guān)注解修飾Module的Provides和Binds方法绰精。
(3). 如果依賴通過構(gòu)造方式注入,且該依賴在此Component中為單例的透葛,則要用Scope修飾其類笨使。
我們通過如下例子詳細(xì)說明,并進(jìn)行測試和分析其原理:

以Singleton這個Scope是實現(xiàn)為例:
首先用它來修飾Component類:

@Singleton
@Component
public interface AppComponent {
    void inject(MainActivity mainActivity);
}

用通過構(gòu)造方法注入依賴圖的僚害,用@Singleton修飾其類:

@Singleton
public class InfoRepository {
    private static final String TAG = "InfoRepository";

    @Inject
    InfoRepository() {
    }

    public void test() {
        Log.d(TAG, "test");
    }
}

實際注入到Activity類中如下:

public class MainActivity extends Activity {
    @Inject
    InfoRepository infoRepositoryA;
    @Inject
    InfoRepository infoRepositoryB;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyApplication.getComponent().inject(this);
        setContentView(R.layout.activity_main);
        Log.d("test", "a:"+infoRepositoryA.hashCode());
        Log.d("test", "b:"+infoRepositoryB.hashCode());
    }
}

如上代碼測試結(jié)果如下:

10-19 19:25:08.253 26699-26699/com.qt.daggerTest D/test: a:442589
10-19 19:25:08.254 26699-26699/com.qt.daggerTest D/test: b:442589

如上可見硫椰,兩次注入的InfoRepository對象為同一個。
如果將上面修飾InfoRepository的@Singleton注解去掉萨蚕,結(jié)果是什么呢靶草?經(jīng)過測試如下:

10-19 19:23:00.092 23014-23014/com.qt.daggerTest D/test: a:442589
10-19 19:23:00.092 23014-23014/com.qt.daggerTest D/test: b:160539730

也就是說在不加@Singleton注解時候,每次注入的時候都是new一個新的對象岳遥。
注意爱致,如果我們將上面所有的@Singleton替換成我們自己的Scope注解其結(jié)果也是一致的,如替換成上文的ActivityScope寒随。

下面糠悯,我們通過分析Dagger自動生成的代碼來分析如何實現(xiàn)單例的:

在注入類不加@Singleton注解時,生成的DaggerAppComponent類的initialize()方法如下:

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {
    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(InfoRepository_Factory.create());
  }

而加@Singleton注解后的initialize()方法變成了:

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

    this.infoRepositoryProvider = DoubleCheck.provider(InfoRepository_Factory.create());

    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(infoRepositoryProvider);
  }

也是就是說提供InfoRepository的InfoRepositoryProvider替換成了DoubleCheck.provider(InfoRepository_Factory.create())妻往。用DoubleCheck包裝了原來對象的Provider互艾。DoubleCheck顧名思義,應(yīng)該是通過雙重檢查實現(xiàn)單例讯泣,我們看源碼確實如此:

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 = UNINITIALIZED;

  private DoubleCheck(Provider<T> provider) {
    assert provider != null;
    this.provider = provider;
  }

  @SuppressWarnings("unchecked") // cast only happens when result comes from the provider
  @Override
  public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          /* Get the current instance and test to see if the call to provider.get() has resulted
           * in a recursive call.  If it returns the same instance, we'll allow it, but if the
           * instances differ, throw. */
          Object currentInstance = instance;
          if (currentInstance != UNINITIALIZED && currentInstance != result) {
            throw new IllegalStateException("Scoped provider was invoked recursively returning "
                + "different results: " + currentInstance + " & " + result + ". This is likely "
                + "due to a circular dependency.");
          }
          instance = result;
          /* Null out the reference to the provider. We are never going to need it again, so we
           * can make it eligible for GC. */
          provider = null;
        }
      }
    }
    return (T) result;
  ...
  }

其get方法就用了DoubleCheck方式保證了單例纫普,其中還判斷如果存在循環(huán)依賴的情況下拋出異常。

注意好渠,Scope只的單例是在Component的生命周期中相關(guān)依賴是單例的昨稼,也就是同一個Component對象注入的同類型的依賴是相同的。按上面例子拳锚,如果我們又創(chuàng)建了個AppComponent假栓,它注入的InfoRepository對象與之前的肯定不是一個。

2. Component間依賴

在Android應(yīng)用中霍掺,如果我們需要不止一個Component匾荆,而Component的依賴圖中有需要其他Component依賴圖中的某些依賴時候,利用Component間依賴(Component Dependency)方式是個很好的選擇杆烁。在新建Component類時候可以在@Component注解里設(shè)置dependencies屬性牙丽,確定其依賴的Component。在被依賴的Component中兔魂,如果要暴露其依賴圖中的某個依賴給其他Component烤芦,要顯示的在其中定義方法,使該依賴對其他Component可見如:

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    Application application();
}
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
    void inject(MainActivity mainActivity);
}

在ActivityComponent中析校,就可以使用AppComponent依賴圖中暴露出的Application依賴了构罗。
非常清晰铜涉,Component依賴(Component Dependency)的方式適用于Component只將某個或某幾個依賴暴露給其他Component的情況下。

3.subComponent

定義subComponent是另一種方式使Component將依賴暴露給其他Component绰播。當(dāng)我們需要一個Component骄噪,它需要繼承另外一個Component的所有依賴時尚困,我們可以定義其為subComponent蠢箩。
具體用法如下:首先在父Component的接口中定義方法,來獲得可以繼承它的subComponent:

@Singleton
@Component(
        modules = {AppModule.class}
)
public interface AppComponent {

    UserComponent plus(UserModule userModule);

}

其次事甜,其subComponent被@SubComponent注解修飾谬泌,如下:

@UserScope
@Subcomponent(
        modules = {UserModule.class}
)
public interface UserComponent {
  ...
}

Dagger會實現(xiàn)上面在接口中定義的plus()方法,我們通過調(diào)用父Component的plus方法或者對應(yīng)的subComponent實例逻谦,具體如下:

userComponent = appComponent.plus(new UserModule(user));

這樣掌实,我們就獲得了userComponent對象,可以利用他為其他類注入依賴了邦马。
注意贱鼻,subComponent繼承了其父Component的所有依賴圖,也就是說被subComponent可以向其他類注入其父Component的所有依賴滋将。

4. 用多個Component組織你的Android應(yīng)用

前面幾部分中邻悬,我們介紹了如何創(chuàng)建多個Component/subComponent并使其獲得其他Component的依賴。這就為我們在應(yīng)用中用多個Component組織組織應(yīng)用提供了條件随闽。文章http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/ 提供了一個常用的思路:我們大概需要三個Component:AppComponent父丰,UserComponent,ActivityComponent掘宪,如下:

摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

上文介紹了蛾扇,各個Component要有自己的Scope且不能相同,所以這幾個Component對應(yīng)的Scope分別為@Singleton 魏滚,@UserScop镀首,@ActivityScope。也就是說鼠次,依賴在對應(yīng)的Component生命周期(同個Component中)中都是單例的蘑斧。而三個Component的生命周期都不同:AppComponent為應(yīng)用全局單例的,UserComponent的生命周期對應(yīng)了用戶登錄的生命周期(從用戶登錄一個賬戶到用戶退出登錄)须眷,ActivityComponent對應(yīng)了每個Activity的生命周期竖瘾,如下:

Scopes lifecycle 摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

在具體代碼中,我們通過控制Component對象的生命周期來控制依賴圖的周期花颗,以UserComponent為例捕传,在用戶登錄時候創(chuàng)建UserComponent實例,期間一直用該實例為相關(guān)類注入依賴扩劝,當(dāng)其退出時候?qū)serComponent實例設(shè)為空庸论,下次登錄時候重新創(chuàng)建個UserComponent职辅,大致如下:

    public class MyApplication extends Application {
      ...
    private void initAppComponent() {
        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
    }

    public UserComponent createUserComponent(User user) {
        userComponent = appComponent.plus(new UserModule(user));
        return userComponent;
    }

    public void releaseUserComponent() {
        userComponent = null;
    }

    public AppComponent getAppComponent() {
        return appComponent;
    }

    public UserComponent getUserComponent() {
        return userComponent;
    }

}

createUserComponent和releaseUserComponent在用戶登入和登出時候調(diào)用,所以在不同用戶中用的是不同UserComponent對象注入聂示,注入的依賴也不同域携。而AppComponent對象只有一個,所以其依賴圖中的依賴為全局單例的鱼喉。而對于ActivityComponent秀鞭,則可以在Activity的onCreate()中生成ActivityComponent對象來為之注入依賴。

5.多Component情況下Scope的使用限制

Scope和多個Component在具體使用時候有一下幾點限制需要注意:
(1). Component和他所依賴的Component不能公用相同的Scope扛禽,每個Component都要有自己的Scope锋边,編譯時會報錯,因為這有可能破壞Scope的范圍编曼,可詳見https://github.com/google/dagger/issues/107#issuecomment-71073298豆巨。這種情況下編譯會報錯:

Error:(21, 1) 錯誤:com.qt.daggerTest.AppComponent depends on scoped components in a non-hierarchical scope ordering:
@com.qt.daggerTest.ActivityScope com.qt.daggerTest.AppComponent
@com.qt.daggerTest.ActivityScope com.qt.daggerTest.ActivityComponent

(2). @Singleton的Component不能依賴其他Component。這從意義和規(guī)范上也是說的通的掐场,我們希望Singleton的Component應(yīng)為全局的Component往扔。這種情況下編譯時會報錯:

Error:(23, 1) 錯誤: This @Singleton component cannot depend on scoped components:
@Singleton com.qt.daggerTest.AppComponent

(3). 無Scope的Component不能依賴有Scope的Component,因為這也會導(dǎo)致Scope被破壞熊户。這時候編譯時會報錯:

Error:(20, 2) 錯誤: com.qt.daggerTest.ActivityComponent (unscoped) cannot depend on scoped components:
@com.qt.daggerTest.ActivityScope com.qt.daggerTest.AppComponent

(4). Module以及通過構(gòu)造函數(shù)注入依賴圖的類和其Component不可有不相同的Scope萍膛,這種情況下編譯時會報:

Error:(6, 1) 錯誤: com.qt.daggerTest.AppComponent scoped with @com.qt.daggerTest.ActivityScope may not reference bindings with different scopes:
@Singleton class com.qt.daggerTest.InfoRepository

總結(jié)

在上一篇Dagger2介紹與使用(http://www.reibang.com/p/f56d5b7e8b4d)的基礎(chǔ)之上,本文圍繞著Android中多個Component情況下如何使用的問題展開了分析敏弃。首先說明了如何通過Scope和Component管理依賴的生命周期卦羡,再介紹了通過Component間依賴和subComponent兩種方式完成一個Component將自己的依賴圖暴露給其他Component的過程。然后介紹如一般在Android應(yīng)用中如何劃分Component麦到,維護(hù)不同生命周期的依賴绿饵。
經(jīng)過兩篇文章的介紹,Dagger2的使用基本清晰瓶颠。Dagger2在處理Android應(yīng)用的依賴非常得心應(yīng)手拟赊,通過依賴注入的方式實現(xiàn)依賴反轉(zhuǎn),用容器(Component)來控制了所有依賴粹淋,使應(yīng)用各個組件的依賴更加清晰吸祟。Dagger2的各種功能也比較靈活,能夠應(yīng)付Android應(yīng)用依賴關(guān)系的各種復(fù)雜場景桃移。

參考:
https://stackoverflow.com/questions/28411352/what-determines-the-lifecycle-of-a-component-object-graph-in-dagger-2
http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
https://stackoverflow.com/questions/36447251/dagger-2-constructor-injection-scope
http://www.reibang.com/p/5ce4dcc41ec7
http://www.reibang.com/p/fbd62868a07b
https://my.oschina.net/rengwuxian/blog/287892

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屋匕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子借杰,更是在濱河造成了極大的恐慌过吻,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔗衡,死亡現(xiàn)場離奇詭異纤虽,居然都是意外死亡乳绕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門逼纸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洋措,“玉大人,你說我怎么就攤上這事杰刽〔しⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵专缠,是天一觀的道長雷酪。 經(jīng)常有香客問我淑仆,道長涝婉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任蔗怠,我火速辦了婚禮墩弯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寞射。我一直安慰自己渔工,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布桥温。 她就那樣靜靜地躺著引矩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侵浸。 梳的紋絲不亂的頭發(fā)上旺韭,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音掏觉,去河邊找鬼区端。 笑死,一個胖子當(dāng)著我的面吹牛澳腹,可吹牛的內(nèi)容都是我干的织盼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼酱塔,長吁一口氣:“原來是場噩夢啊……” “哼沥邻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起羊娃,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤唐全,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后迁沫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芦瘾,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡捌蚊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了近弟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缅糟。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祷愉,靈堂內(nèi)的尸體忽然破棺而出窗宦,到底是詐尸還是另有隱情,我是刑警寧澤二鳄,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布赴涵,位于F島的核電站,受9級特大地震影響订讼,放射性物質(zhì)發(fā)生泄漏髓窜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一欺殿、第九天 我趴在偏房一處隱蔽的房頂上張望寄纵。 院中可真熱鬧,春花似錦脖苏、人聲如沸程拭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恃鞋。三九已至,卻和暖如春亦歉,著一層夾襖步出監(jiān)牢的瞬間恤浪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工鳍徽, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留资锰,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓阶祭,卻偏偏與公主長得像绷杜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子濒募,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容