依賴注入框架Dagger2詳解(一)稍走,依賴注入和控制反轉(zhuǎn)的深入理解
依賴注入框架Dagger2詳解(二),Java依賴注入標(biāo)準(zhǔn)JSR-330規(guī)范
依賴注入框架Dagger2詳解(三)朗鸠,Java注解處理器APT入門
依賴注入框架Dagger2詳解(四),初級篇
依賴注入框架Dagger2詳解(五)础倍,中級篇
依賴注入框架Dagger2詳解(六)烛占,高級篇
這篇主要介紹Dagger單例,延遲加載沟启,強(qiáng)制加載忆家,以及使用Subcomponent等一些高級用法
實(shí)現(xiàn)單例
創(chuàng)建某些對象有時(shí)候是耗時(shí)浪費(fèi)資源或者沒有完全必要的,這時(shí)候Component沒有必要重復(fù)地使用Module來創(chuàng)建這些對象德迹。舉個(gè)例子芽卿,當(dāng)我們需要榨果汁時(shí),我們榨蘋果汁與榨香蕉汁可以使用相同一臺(tái)榨果汁機(jī)器胳搞,我們只需要?jiǎng)?chuàng)建出一臺(tái)榨果汁機(jī)器卸例。我們可以使用@Singleton來緩存“榨果汁機(jī)器”,這樣在下一次需要“榨果汁機(jī)器”時(shí)會(huì)直接使用上一次的緩存肌毅,如下
@Module
class MachineModule{
@Singleton //1.添加@Singleton標(biāo)明該方法產(chǎn)生只產(chǎn)生一個(gè)實(shí)例
@Provides
Machine provideFruitJuiceMachine(){
return new FruitJuiceMachine();
}
}
@Singleton //2.添加@Singleton標(biāo)明該Component中有Module使用了@Singleton
@Component(modules=MachineModule.class)
class JuiceComponent{
void inject(Container container)
}
public class Test{
public static void main(String... args){
JuiceComponent c1=DaggerJuiceComponent.create();
c1.inject(container1);
c1.inject(container2);
//由于制造machine的方法使用了@Singleton筷转,所以先后注入container1,container2中的machine相同
System.out.println(conainter1.machine==conainter2.machine);//true
}
}
上面的例子可以看到,實(shí)現(xiàn)單例需要兩步
- 1.在Module對應(yīng)的Provides方法標(biāo)明@Singleton
- 2.同時(shí)在Component類標(biāo)明@Singleton
這樣悬而,JuiceComponent每次注入Container中的Machine都是同一個(gè)FruitJuiceMachine對象呜舒。
單例的保存位置
Java中,單例通常保存在一個(gè)靜態(tài)域中摊滔,這樣的單例往往要等到虛擬機(jī)關(guān)閉時(shí)候阴绢,該單例所占用的資源才釋放。但是艰躺,Dagger通過Singleton創(chuàng)建出來的單例并不保持在靜態(tài)域上呻袭,而是保留在Component實(shí)例中。要理解這一點(diǎn)腺兴,請看下面代碼左电,續(xù)上文中的例子
public class Test{
public static void main(String... args){
//c1,c2是不同對象,它們各自緩存machine
JuiceComponent c1=DaggerJuiceComponent.create();
JuiceComponent c2=DaggerJuiceComponent.create();
c1.inject(container1);
c1.inject(container2);
c2.inject(container3);
c2.inject(container4);
System.out.println(conainter1.machine==conainter2.machine);//true
System.out.println(conainter2.machine==conainter3.machine);//false
System.out.println(conainter3.machine==conainter4.machine);//true
System.out.println(conainter4.machine==conainter1.machine);//false
}
}
c1前后兩次分別注入container1,container2篓足,每個(gè)Component對象保留各自的單例對象段誊,而container1,container2都是使用c1來注入machine栈拖,所以他們的machine對象是相同的连舍。而container2與container3分別使用c1,c2來注入machine涩哟,所以他們的machine對象是不同的索赏。
自定義Scope
@Singleton就是一種Scope注解,也是Dagger2唯一自帶的Scope注解,下面是@Singleton的源碼
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton{}
可以看到定義一個(gè)Scope注解贴彼,必須添加以下三部分:
@Scope :注明是Scope
@Documented :標(biāo)記在文檔
@Retention(RUNTIME) :運(yùn)行時(shí)級別
對于Android潜腻,我們通常會(huì)定義一個(gè)針對整個(gè)APP全生命周期的@PerApp的Scope注解,通過仿照@Singleton
@Scope
@Documented
@Retention(RUNTIME)
public @interface PerApp{}
一般來說,我們通常還會(huì)定義一個(gè)針對一個(gè)Activity生命周期的@PerActivity的Scope注解器仗,類似地:
@Scope
@Documented
@Retention(RUNTIME)
public @interface PerActivity{}
為何我們要定義多個(gè)Scope融涣,使用自帶的Singleton不好么?這是因?yàn)槭褂肧cope有兩方面的好處:
一方面是為了給Singleton定義一個(gè)別名精钮,這樣看到這個(gè)別名威鹿,你就知道這個(gè)Singleton的有效范圍。
比如你可以定義一個(gè)@PerApp
@Scope
@Documented
@Retention(RUNTIME)
public @interface PerApp{}
@Module
class MachineModule{
@PerApp//1.添加@PerApp標(biāo)明該方法產(chǎn)生只產(chǎn)生一個(gè)實(shí)例
@Provides
Machine provideFruitJuiceMachine(){
return new FruitJuiceMachine();
}
}
@PerApp//2.添加@PerApp標(biāo)明該Component中有Module使用了@PerApp
@Component(modules=MachineModule.class)
class JuiceComponent{
void inject(Container container)
}
//3.單例的有效范圍隨著其依附的Component杂拨,為了使得@PerApp的作用范圍是整個(gè)Application专普,你需要添加以下代碼
public class CustomApp extends Application{
private static JuiceComponent mComponent;// 注意是靜態(tài)
public void onCreate(){
mComponent=DaggerJuiceComponent.create();
}
public static JuiceComponent getComponent(){ //供給調(diào)用
return mComponent;
}
}
類似的,你也可以定義一個(gè)@PerActivity弹沽,有效范圍是當(dāng)前這個(gè)Activity,如下:
//限于篇幅有限檀夹,只寫出對應(yīng)@PerActivity所對應(yīng)的Component的存儲(chǔ)位置
public class CustomActivity extends Activity{
private PerActivityComponent mComponent; //非靜態(tài),除了針對整個(gè)App的Component可以靜態(tài)策橘,其他一般都不能是靜態(tài)的炸渡。
public void onCreate(bundle savedInstanceState){
mComponent=DaggerPerActivityComponent.create();
}
}
另一方面,如果兩個(gè)Component間有依賴關(guān)系丽已,那么它們不能使用相同的Scope蚌堵。如果使用相同的Scope會(huì)帶來語義混亂∨嬗ぃ考慮以下情況:
Component1 c1 = Dagger_Component1.create();
Component2 c2_a = Dagger_Component2.builder().component1(c1).build();
Component2 c2_b = Dagger_Component2.builder().component1(c1).build();
我們先設(shè)
1.c1中有單例V
2.假設(shè)Component1與Component2有相同的Scope
3.Component2依賴Component1
推出以下矛盾
1.由于Component1跟Component2具有相同的Scope吼畏,而c2_a,c2_b是Component2的不同實(shí)例,所以c2_a,c2_b應(yīng)該具備不同的V
2.由于c2_a,c2_b的V都是存在c1中嘁灯,而且在c1中應(yīng)該具備唯一的V泻蚊,所以c2_a,c2_b應(yīng)該具備相同的V。
所以推出矛盾丑婿,證明依賴的Component間不能使用相同的Scope性雄。
Subcomponent
如果一個(gè)Component的功能不能滿足你的需求没卸,你需要對它進(jìn)行拓展,一種辦法是使用Component(dependencies=××.classs)秒旋。另外一種是使用@Subcomponent约计,Subcomponent用于拓展原有component。同時(shí)迁筛,這將產(chǎn)生一種副作用——子component同時(shí)具備兩種不同生命周期的scope煤蚌。子Component具備了父Component擁有的Scope,也具備了自己的Scope瑰煎。
Subcomponent其功能效果優(yōu)點(diǎn)類似component的dependencies铺然。但是使用@Subcomponent不需要在父component中顯式添加子component需要用到的對象,只需要添加返回子Component的方法即可酒甸,子Component能自動(dòng)在父Component中查找缺失的依賴。
//父Component:
@PerApp
@Component(modules=××××)
public AppComponent{
SubComponent subcomponent(); //1.只需要在父Component添加返回子Component的方法即可
}
//子Component:
@PerAcitivity //2.注意子Component的Scope范圍小于父Component
@Subcomponent(modules=××××) //3.使用@Subcomponent
public SubComponent{
void inject(SomeActivity activity);
}
//使用
public class SomeActivity extends Activity{
public void onCreate(Bundle savedInstanceState){
...
App.getComponent().subCpmponent().inject(this);//4.調(diào)用subComponent方法創(chuàng)建出子Component
}
}
通過Subcomponent赋铝,子Component就好像同時(shí)擁有兩種Scope插勤,當(dāng)注入的元素來自父Component的Module,則這些元素會(huì)緩存在父Component革骨,當(dāng)注入的元素來自子Component的Module农尖,則這些元素會(huì)緩存在子Component中。
Lazy與Provider
Lazy和Provider都是用于包裝Container中需要被注入的類型良哲,Lazy用于延遲加載盛卡,Provide用于強(qiáng)制重新加載,具體如下:
public class Container{
@Inject Lazy<Fruit> lazyFruit; //注入Lazy元素
@Inject Provider<Fruit> providerFruit; //注入Provider元素
public void init(){
DaggerComponent.create().inject(this);
Fruit f1=lazyFruit.get(); //在這時(shí)才創(chuàng)建f1,以后每次調(diào)用get會(huì)得到同一個(gè)f1對象
Fruit f2=providerFruit.get(); //在這時(shí)創(chuàng)建f2,以后每次調(diào)用get會(huì)再強(qiáng)制調(diào)用Module的Provides方法一次筑凫,根據(jù)Provides方法具體實(shí)現(xiàn)的不同滑沧,可能返回跟f2是同一個(gè)對象,也可能不是巍实。
}
}
值得注意的是滓技,Provider保證每次重新加載,但是并不意味著每次返回的對象都是不同的棚潦。只有Module的Provide方法每次都創(chuàng)建新實(shí)例時(shí)令漂,Provider每次get()的對象才不相同。
Multibindings
Multibindings的應(yīng)用場景比較少丸边,主要用于插件化的實(shí)現(xiàn)叠必,Multibindings分成Set與Map,Map的Multibindings目前還是Beta版本妹窖,也就是說還是在試驗(yàn)階段纬朝,所以只介紹簡單的Set的Multibindings。
通過Multibindings嘱吗,Dagger可以將不同Module產(chǎn)生的元素整合到同一個(gè)集合玄组。更深層的意義是滔驾,Module在此充當(dāng)?shù)牟寮慕巧蛻舳送ㄟ^這些插件的不同而獲取到不同的集合俄讹。
舉個(gè)例子哆致,一個(gè)機(jī)器可以安裝不同插件來做不同的事,安裝掃地插件則可以掃地患膛,安裝煮飯插件則可以煮飯摊阀,下面是個(gè)完整例子。
//一個(gè)煮飯的Module踪蹬,代表煮飯插件
@Module
public class CookPlugin{
@Provides(type=Type.SET)//type是SET時(shí)胞此,改方法返回一個(gè)元素
public String provideCook(){
return "cook";
}
}
//一個(gè)清潔的Module,代表清潔插件
@Module
public class CleanPlugin{
@Provides(type=Type.SET_VALUES) //type是SET_VALUES時(shí)跃捣,該方法返回Set集合
public Set<String> provideCook(){
Set<String> set=new HashSet<String>();
set.add("clean");
return set;
}
}
//安裝煮飯插件與清潔插件漱牵,通過指定不同的插件,機(jī)器人將具備不同的能力
@Component(modules={CookPlugin.class,CleanPlugin.class})
public class RobotComponent{
public void inject(Robot robot);
}
//機(jī)器人
public class Robot{
@Inject Set<String> abilities; //注入后集合中包含cook疚漆,clean
public void init(){
DaggerRbotComponent.creaete().inject(this);
}
public void printAbilities(){
System.out.println(abilities);//輸出cook,clean
}
}
上文是使用Multibindings實(shí)現(xiàn)Set的整合酣胀,此外Multibindings還支持Map,由于Dagger對Map的支持還處在試驗(yàn)階段,所以不深入介紹娶聘。所以有興趣的話可以直接閱讀官方文檔
JSR-330 標(biāo)準(zhǔn)
https://docs.oracle.com/javaee/7/api/javax/inject/package-summary.html
Dagger2.2 JavaDoc
https://google.github.io/dagger/api/2.0/
Dagger2 源碼
https://github.com/google/dagger