Dagger2 Android應用:@Scope和@Subcomponent

這部分會介紹Dagger2中比較莫名的概念,同樣也不涉及Android的具體代碼祝辣。
Dagger2使用中的核心技巧包括@Subcomponent和@Scope贴妻,這兩個注解對架構(gòu)的層次關系有非常重要的作用。

Dagger2的作用域 @Scope

作用域是Dagger2中一個比較重要的概念蝙斜,各種奇淫巧技名惩,比如單例,都是通過它來實現(xiàn)的孕荠。
按照Google的官方定義娩鹉,Scope應該作為作用域來理解,然而很多中文資料將它翻譯為生命周期稚伍。
這對很多Android開發(fā)者來說就導致概念模糊弯予,這生命周期跟Android的生命周期又是個什么關系?
所以@Scope是什么个曙,它的作用又是什么呢锈嫩。

什么是@Scope

@Scope 的官方定義 Subcomponent & Scope

One reason to break your application’s component up into Subcomponents is to use scopes. With normal, unscoped bindings, each user of an injected type may get a new, separate instance. But if the binding is scoped, then all users of that binding within the scope’s lifetime get the same instance of the bound type.

這個解釋比較抽象,它說明一個概念就是,在不使用@Scope的情況下呼寸,可能每次注入的對象都會是一個新的不同的對象艳汽,而@Scope能限制被注入的對象,在同一個@Scope的生命周期(lifetime)中都只存在一個且僅有一個對象(這不就是單例么)对雪。
是的其實在Dagger2中河狐,單例對象的實現(xiàn)方式就是用@Scope,Dagger2給開發(fā)者提供了一個默認的已經(jīng)定義好的單例注解慌植,@Singleton甚牲。
@Singleton 的源碼是這樣的

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton{}

看上面的代碼可以基本明白,@Scope它提供了一種自定義注解的方法蝶柿,它本身并不能直接使用,開發(fā)者結(jié)合自己的需求非驮,用@Scope來定義所需要的注解交汤。
像@Singleton用來做單例注解的,Dagger2已經(jīng)替我們做好劫笙,只要直接使用就行芙扎。

@Subcomponent

在嘗試解釋@Scope 的時候,我發(fā)現(xiàn)它實際上很難單獨拎出來理解填大。@Scope所描述的作用域戒洼,是需要跟Component的層級結(jié)合才能使用的。這時候就需要結(jié)合@Subcomponent 來一起理解這兩個東西了允华。

像之前的例子代碼里圈浇,Machine被劃分為Component,Heater和Pumper是作為Module來提供給Component依賴的靴寂。然而一個Componenta(組件)同時可以作為Module存在磷蜀,反過來一個Module也可以作為Component存在。

理解了這個概念后百炬,還需要理清一個點是褐隆,什么時候需要拆分為不同的層級(Component),不同層級之間是又是什么關系呢剖踊?

Google官方的建議庶弃,除了在需要使用@Scope的情況下使用@Subcomponent,還有一種情況可以拆分層級

Another reason to use Subcomponents is to encapsulate different parts of your application from each other. For example, if two services in your server (or two screens in your application) share some bindings, say those used for authentication and authorization, but each have other bindings that really have nothing to do with each other, it might make sense to create separate Subcomponents for each service or screen, and to put the shared bindings into the parent component.

意思是說德澈,如果不同組件間互相沒有依賴或者關聯(lián)歇攻,那么可以把他們的共同使用到的部分放到 parent component中,而這倆就可以作為Subcomponent存在圃验。
這段話說明
· child component 可以使用 parent component 中的依賴
· parent component 可以有多個 child component掉伏,而且互相無關

使用 @Scope @Subcomponent 來拆分層級

在不使用@Subcomponent的情況下修改之前的代碼

我們回到之前CoffeeMachine的例子。老板要加個咖啡師(CoffeeMaker),咖啡師操作Heater來加熱斧散,而且咖啡師是屬于 CoffeeMachine 層級的供常,就是說它應該是Machine的成員,或者以依賴存在鸡捐。
Heater的構(gòu)造方法此時就需要加入Maker對象栈暇,且必須以入?yún)⒎绞酱嬖冢荒茉跇?gòu)造方法里直接new一個箍镜,原因相信思考一下就明白源祈。
我們需要修改兩處地方,一個是Heater的構(gòu)造方法色迂,一個是HeaterModule的provide方法香缺。還記得provide的作用么,它是用來提供依賴的對象來注入到Component的歇僧,通過它图张,Heater的實例化從CoffeeMachine中剝離,單獨存在于Module中诈悍。

public class ElectricHeater implements Heater {

    Cooker mCooker;

    private static final String TAG = PublicConstant.PUB + "ElectricHeater";

    public ElectricHeater(Cooker cooker) {
        this.mCooker = cooker;
    }

    @Override
    public void heat() {
        Log.d(TAG, "electric heating...");
    }
}

Module類則變成了這樣祸轮,

@Module
public class HeaterModule {

    Cooker mCooker;

    public HeaterModule(Cooker cooker) {
        this.mCooker = cooker;
    }

    @Provides
    public Heater provideHeater() {
        return new ElectricHeater(this.mCooker);
    }
//    @Provides
//    public Heater provideHeater() {
//        return new ElectricHeater(new Cooker());
//    }
}

實際上Module類的provide方法也可以像注釋掉的代碼那部分這么寫,一樣也可以做到給ElectricHeater注入Cooker的目的侥钳,然而這樣很明顯是錯誤的适袜,因為這樣一來Machine就不能管理Cooker,還記得Cooker是屬于Machine層級這個設定嗎舷夺?

接下來需要修改Machine的代碼了苦酱,它所需要的只是在build component的時候指定HeaterModule并傳入Cooker實例,

public class CoffeeMachine {
    private static final String TAG = PublicConstant.PUB + "CoffeeMachine";

    @Inject Heater mHeater;
    @Inject Pumper mPumper;
    Cooker mCooker;

    public void makeCoffee() {
        mCooker = new Cooker();
        DaggerMachineComponent.builder()
                .build()
                .inject(this);
        mPumper.pump();
        mHeater.heat();
        Log.d(TAG, "Coffee ready");
    }
}

有些人就會疑惑,萬一忘了修改Machine的注入代碼冕房,沒有傳個HeaterModule會怎樣呢躏啰?
Dagger2會在運行期判斷這種情況,如果是一個不需要參數(shù)的Module耙册,那么它在沒有傳入module實例的時候沒有任何問題给僵,Dagger2幫你實例化一個module對象;對于需要參數(shù)的module而我們又忘了設详拙,那么它會丟個IllegalStateException出來帝际。

使用@Subomponent 和 @Scope 拆分層級

雖然在不用@Subcomponent的情況下也可以實現(xiàn)從Machine里將Cooker傳給Heater,但這種方式代碼的層級不夠明確饶辙。于是我們用這兩個注解來進一步拆分蹲诀。
現(xiàn)在可以看出來Heater也需要依賴Cooker了,我們將Machine和Heater的關系重新審視一下弃揽,看成是 parent component 和 child component 的關系脯爪。實際上這種例子在Andorid開發(fā)中常常是以 Application 和 Activity 的形式存在则北,我們經(jīng)常遇到的會像在Activity中需要用到全局的SharePreference,或者LogUtils工具痕慢,或者像圖片框架/線程池/網(wǎng)絡請求框架等情況尚揣,這時候我們會把這些東西放到Application中去。

定義Scope

在這個例子里我們至少需要定義兩個Scope掖举,為了整體代碼的統(tǒng)一快骗,我們定義三個Scope,分別是Machine/Heater/Pumper層級塔次。需要注意的是方篮,Machine是相對于另外兩個更高的層級,可以理解為Parent Scope励负,它的范圍更大藕溅,另外的Heater和Pumper是同一個層級。這里我們是這么假設的熄守。

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerMachine {
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerHeater {
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerPumper {
}

定義@Scope之后蜈垮,有對應@Scope的module就只能在注解了同樣@Scope中的Component里使用了,而且Subcomponent不能使用Parent Component同樣的@Scope,這樣代碼的層級就明確的劃分了開來裕照。

定義Subcomponent

首先我們把Heater獨立為一個Component,這里面會有一個問題调塌,
Component在注入的時候不能用父類或者接口作為參數(shù)晋南,在我們這個例子里,必須以ElectricHeater 作為參數(shù)羔砾,而不能用Heater负间。

@PerHeater
@Subcomponent
public interface HeaterSubcomponent {

    void inject(ElectricHeater heater);
}

我們用@Subcomponent來表示這是個子組件,它所在的層級由@PerHeater來表示姜凄,而它所需要注入的組件是 EletricHeater
再次注意inject()方法的參數(shù)不能用接口也不能用父類

在定義了Subcomponent之后政溃,它是怎么跟Parent Compoenent聯(lián)系起來的呢?這里扯個題外話态秧,在Dagger1的時候董虱,并沒有@Subcomponent注解,它是用depenencies來表明Subcomponent的申鱼。在Dagger2中愤诱,我們只需要在Parent Component中顯式的定義一個Subcomponent的接口即可。

@PerMachine
@Component(modules = { HeaterModule.class,
        PumperModule.class,
        CookerModule.class})
public interface MachineComponent {

    HeaterSubcomponent heaterComponent();//表明Subcomponent

    void inject(CoffeeMachine machine);
}

@PerMachine 是用來表示它的層級的捐友,如果Subcomponent注解了同樣的層級淫半,那么編譯時就會報錯。CookerModule是新增的module匣砖,用來提供Cooker給Machine科吭,之后包括Machine和Heater都可以使用這個實例昏滴,可以把Cooker認為是一個"全局"實例。

@Module
public class CookerModule {

    @PerMachine
    @Provides
    public Cooker provideCooker() {
        return new Cooker();
    }

}

這里可以看到@PerMachine也用來注解provide方法对人,這樣一來就表示Cooker會存在于Machine層級中谣殊,從代碼角度來說,Cooker() 實例會在MachineComponent中存在规伐,而且只要是通過MachineComponent去獲取的蟹倾,那么就只有一個。
注意猖闪,在Dagger2中的單例鲜棠,應該理解為在它所注解的@Scope的Component中只存在一個,這與通過private constructor + getInstance()來實現(xiàn)的單例有所不同

然后我們回到CoffeeMachine的代碼培慌,我們在這里通過注入增加一個Cooker的對象

public class CoffeeMachine {
    private static final String TAG = PublicConstant.PUB + "CoffeeMachine";

    @Inject
    Pumper mPumper;
    @Inject
    Cooker mCooker;
    @Inject
    Heater mHeater;

    public CoffeeMachine() {
    }

    static MachineComponent component;

    public void makeCoffee() {
        component = DaggerMachineComponent.create();
        component.inject(this);
        mPumper.pump();
        mHeater.heat();
        Log.d(TAG, "Coffee ready, cooked by: " + mCooker);
    }

    //通過靜態(tài)方法向子類暴露Component
    public static MachineComponent getComponent() {
        return component;
    }
}

需要注意這個注釋的地方豁陆,子類需要通過父類的靜態(tài)方法來獲取Parent Component,進而對自己進行注入吵护。然后來看ElectricHeater的代碼盒音,這時候我們需要直接注入Cooker實例到ElectricHeater中

public class ElectricHeater implements Heater {
    private static final String TAG = PublicConstant.PUB + "ElectricHeater";

    @Inject
    Cooker mCooker;

    public ElectricHeater() {
      //DaggerMachineComponent.create().heaterComponent().inject(this);
      //注意這里我們用Parent的靜態(tài)方法獲取Component
      CoffeeMachine.getComponent().heaterComponent().inject(this);
    }

    @Override
    public void heat() {
        mCooker.cook();
        Log.d(TAG, "electric heating... by: " + mCooker);
    }
}

現(xiàn)在我們在ElectricHeater中也獲取了Cooker,而且是從Machine拿到的Cooker馅而。為了驗證我們通過log來看是否是同一個對象祥诽,


Dagger的單例

聰明的你應該已經(jīng)明白這是怎么回事了,這也是Dagger2中單例的實現(xiàn)方式瓮恭。

有人可能有疑問雄坪,為什么必須用Parent的靜態(tài)方法來獲取Component進行注解呢?其實靜態(tài)方法不是必須的屯蹦,只要能拿到Parent的同一個Component就可以维哈。
那么是否可以直接用DaggerMachineComponent來注入呢?這是個有意思的問題登澜,我們修改代碼來嘗試下阔挠。

public ElectricHeater() {
  DaggerMachineComponent.create().heaterComponent().inject(this);
  //CoffeeMachine.getComponent().heaterComponent().inject(this);
}

我們注釋掉用靜態(tài)方法獲取Component的方式,通過DaggerMachineComponent來獲取Subcomponent進行注入脑蠕。最后我們會看到log如下


重復實例化的結(jié)果

所以明白為什么需要獲取Parent Component的同一個Component了吧购撼。因為Cooker是保存在Component中的,如果我們重新用DaggerMachineComponent來進行注入空郊,那么就會產(chǎn)生新的Component份招,同樣Cooker也會是新的。

很多資料用@Singleton來說明單例狞甚,其實像之前說的锁摔,@Singleton也是自定義注解,只是它是Dagger2提前給我封裝好可以直接用而已哼审。對于開發(fā)來說谐腰,想讓某個對象成為單例孕豹,同樣要用@Singleton來同時注解Module的Provide方法和Component。而這個跟我們用@PerMachine來注解Cooker和Machine是一個道理十气。

總結(jié)

到現(xiàn)在應該可以理解什么時候需要用@Scope和@Subomponent了励背。
當需要劃分層級的時候,我們會把某個組件作為單獨的Component劃分出去砸西,這時候它通過@Scope定義為比Parent Component范圍小的作用域叶眉,它可以在不聲明依賴的module的情況下使用Parent Component中的依賴。
但是有個限制芹枷,Parent Component的依賴只能往下暴露一層衅疙,這意味著如果有多層結(jié)構(gòu)的話,每個Subcomponent都需要手動聲明一個向下暴露依賴的接口鸳慈。
同時Component中的inject方法不能是接口也不能是父類饱溢,因此這會有個限制,拿MVP來做例子走芋,通常只能是其中的M/V/P以層的方式來作為Component绩郎。這個概念需要在實踐中才能體會。翁逞。

所有這個例子中的代碼已經(jīng)可以從這里下載: MyDagger2

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肋杖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子挖函,更是在濱河造成了極大的恐慌兽愤,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挪圾,死亡現(xiàn)場離奇詭異,居然都是意外死亡逐沙,警方通過查閱死者的電腦和手機哲思,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吩案,“玉大人棚赔,你說我怎么就攤上這事∨枪” “怎么了靠益?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長残揉。 經(jīng)常有香客問我胧后,道長,這世上最難降的妖魔是什么抱环? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任壳快,我火速辦了婚禮纸巷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘眶痰。我一直安慰自己瘤旨,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布竖伯。 她就那樣靜靜地躺著存哲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪七婴。 梳的紋絲不亂的頭發(fā)上祟偷,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音本姥,去河邊找鬼肩袍。 笑死,一個胖子當著我的面吹牛婚惫,可吹牛的內(nèi)容都是我干的氛赐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼先舷,長吁一口氣:“原來是場噩夢啊……” “哼艰管!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蒋川,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤牲芋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后捺球,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缸浦,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年氮兵,在試婚紗的時候發(fā)現(xiàn)自己被綠了裂逐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡泣栈,死狀恐怖卜高,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情南片,我是刑警寧澤掺涛,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站疼进,受9級特大地震影響薪缆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颠悬,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一矮燎、第九天 我趴在偏房一處隱蔽的房頂上張望定血。 院中可真熱鬧,春花似錦诞外、人聲如沸澜沟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茫虽。三九已至,卻和暖如春既们,著一層夾襖步出監(jiān)牢的瞬間濒析,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工啥纸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留号杏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓斯棒,卻偏偏與公主長得像盾致,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荣暮,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

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