依賴注入框架Dagger2詳解(六)救斑,高級篇

依賴注入框架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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闻镶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丸升,更是在濱河造成了極大的恐慌铆农,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狡耻,死亡現(xiàn)場離奇詭異墩剖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酝豪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門涛碑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孵淘,你說我怎么就攤上這事蒲障。” “怎么了瘫证?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵揉阎,是天一觀的道長。 經(jīng)常有香客問我背捌,道長毙籽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任毡庆,我火速辦了婚禮坑赡,結(jié)果婚禮上烙如,老公的妹妹穿的比我還像新娘。我一直安慰自己毅否,他們只是感情好亚铁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著螟加,像睡著了一般徘溢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捆探,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天然爆,我揣著相機(jī)與錄音,去河邊找鬼黍图。 笑死曾雕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的助被。 我是一名探鬼主播翻默,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恰起!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趾牧,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤检盼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后翘单,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吨枉,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年哄芜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了貌亭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡认臊,死狀恐怖圃庭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情失晴,我是刑警寧澤剧腻,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站涂屁,受9級特大地震影響书在,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拆又,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一儒旬、第九天 我趴在偏房一處隱蔽的房頂上張望栏账。 院中可真熱鬧,春花似錦栈源、人聲如沸挡爵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽了讨。三九已至,卻和暖如春制轰,著一層夾襖步出監(jiān)牢的瞬間前计,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工垃杖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留男杈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓调俘,卻偏偏與公主長得像伶棒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子彩库,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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