這部分會介紹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來看是否是同一個對象祥诽,
聰明的你應該已經(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如下
所以明白為什么需要獲取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