Dagger2

Dagger2 是一個(gè)Android依賴注入框架,由谷歌開發(fā),最早的版本Dagger1 由Square公司開發(fā)。依賴注入框架主要用于模塊間解耦,提高代碼的健壯性和可維護(hù)性。

Dragger2 通過(guò)注解來(lái)生成代碼,定義不同的角色,主要的注解有:@Inject、@Module、@Component、@Provides、@Scope、@SubComponent等。

  • @Inject:通常在需要依賴的地方使用這個(gè)注解。換句話說(shuō),你用它告訴Dragger這個(gè)類或者字段需要依賴注入。這樣,Dragger就會(huì)構(gòu)造一個(gè)這個(gè)類的實(shí)例并滿足他們的依賴。
  • @Module: Modules類里面的方法專門提供依賴,所以我們定義一個(gè)類,用@Module注解,這樣Dragger在構(gòu)造類的實(shí)例的時(shí)候,就知道從哪里去找到需要的依賴。modules一個(gè)重要特性是他們?cè)O(shè)計(jì)為分區(qū)并組合在一起(比如說(shuō),在我們的app中可以有多個(gè)組成在一起的modules)
  • @Provides:在modules中,我們定義的方法使用這個(gè)注入器,以此來(lái)告訴Dragger我們想要構(gòu)造對(duì)象并提供這些依賴。
  • @Component: Components從根本上來(lái)說(shuō)就是一個(gè)注入器,也可以說(shuō)是@Inject和@Module的橋梁,它的主要作用就是連接這兩部分。Commponents可以提供所有定義了的類型的實(shí)例。比如:我們必須用@Commponent注解一個(gè)接口然后列出所有的@Modules組成該組件,如果缺失了任何一塊都會(huì)在編譯的時(shí)候報(bào)錯(cuò)。所有組件都可以通過(guò)它的modules知道以來(lái)的范圍。
  • @Scope:Dragger2可以通過(guò)自定義注解限定注解作用域。一般說(shuō)來(lái)每一個(gè)Component都有一個(gè)自己的作用域
  • @Qualifier: 當(dāng)類的類型不足以鑒別一個(gè)依賴的時(shí)候,我們就可以使用這個(gè)注解標(biāo)示。例如:在Android中,我們會(huì)需要不同類型的context,所以我們就可以定義qulifier注解"@perApp"和"@perActivity:,這樣當(dāng)注入一個(gè)context的時(shí)候,我們就可以告訴Dagger我們想要那哪種類型的context。

結(jié)構(gòu)

Dagger2要實(shí)現(xiàn)一個(gè)完整的依賴注入,必不可少的元素有三種:Module,Component和Container。

Paste_Image.png

為了便于理解,其實(shí)可以把component想象成針管,module是注射瓶,里面的依賴對(duì)象是注入的藥水,build方法是插進(jìn)患者(Container),inject方法的調(diào)用是推動(dòng)活塞。

一個(gè)簡(jiǎn)單的例子

實(shí)現(xiàn)module

@Module // 注明本類是Module
public class MyModule{
    @Provides  // 注明該方法是用來(lái)提供依賴對(duì)象的方法
    public B provideB(){
        return new B();
    }
}

實(shí)現(xiàn)Component

@Component(modules={ MyModule.class}) // 指明Component查找Module的位置
public interface MyComponent{    // 必須定義為接口,Dagger2框架將自動(dòng)生成Component的實(shí)現(xiàn)類,對(duì)應(yīng)的類名是Dagger×××××,這里對(duì)應(yīng)的實(shí)現(xiàn)類是DaggerMyComponent 
    void inject(A a);   // 注入到A(Container)的方法,方法名一般使用inject
 }

實(shí)現(xiàn)Container

A就是可以被注入依賴關(guān)系的容器

public A{
     @Inject   //標(biāo)記b將被注入
     B b;   // 成員變量要求是包級(jí)可見,也就是說(shuō)@Inject不可以標(biāo)記為private類型。 
     public void init(){
         DaggerMyComponent.create().inject(this); // 將實(shí)現(xiàn)類注入
     }
 }

當(dāng)調(diào)用A的init()方法時(shí),b類自動(dòng)被賦予實(shí)現(xiàn)類的對(duì)象。

更多用法

方法參數(shù)
上面的例子@Provdes標(biāo)注的方法是沒(méi)有輸入?yún)?shù)的,Module中@Provides標(biāo)注的方法是可以帶輸入?yún)?shù)的,其參數(shù)值是可以由Module中其它被@Provides標(biāo)注的方法提供。

@Module
public class MyModule{
    @Provides
    public B provideB(C c){         
        return new B(c);
    }
    @Provides
    pulic C provideC(){
        return new C();
    }
}

如果找不到到@Provides注釋的方法提供對(duì)應(yīng)參數(shù)對(duì)象的話,將自動(dòng)調(diào)用被@Inject注釋的構(gòu)造方法生成相應(yīng)對(duì)象。

@Module
public class MyModule{
    @Provides
    public B provideB(C c){
        return new B(c);
    }
}
public class C{
    @Inject
    Public C(){
    }
}

添加多個(gè)Module

一個(gè)Commponent可以添加多個(gè)Module,這樣Component獲取依賴的時(shí)候會(huì)自動(dòng)從多個(gè)Module中查找獲取。添加多個(gè)Module有兩種方法,一種就是在Component的注解@Component(modules={××××,×××})中添加多個(gè)modules

@Component(modules={ModuleA.class,ModuleB.class,ModuleC.class}) 
public interface MyComponent{
    ...
}

另外一種添加多個(gè)Module的方法可以使用@Module的 includes的方法(includes={××××,×××})

@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class})
public class MyModule{
    ...
}
@Component(modules={MyModule.class}) 
public interface MyComponent{
    ...
}

創(chuàng)建Module實(shí)例

上面簡(jiǎn)單例子中,當(dāng)調(diào)用DaggerMyComponent.create()實(shí)際上等價(jià)于調(diào)用了DaggerMYComponent.builder().build().可以看出DaggerMyComponent使用了Builder構(gòu)造者模式。在構(gòu)建的過(guò)程中,默認(rèn)使用Module無(wú)參構(gòu)造器產(chǎn)生實(shí)例。如果需要傳入特點(diǎn)的Module實(shí)例。可以使用

DaggerMyComponent.builder()
.moduleA(new ModuleA()) 
.moduleB(new ModuleB())
.build()

區(qū)分@Provides方法

這里以Android Context為例。當(dāng)有Context需要注入時(shí),Dagger2就會(huì)在Module中查找返回類型為Context的方法。但是,當(dāng)Container需要依賴兩種不同的Context的時(shí)候,你就需要寫兩個(gè)@Provides方法,并且這兩個(gè)@Provides方法都是返回Context類型,靠跑別返回值得做法就行不通了。這就可以使用@Named注解來(lái)區(qū)分

//定義Module
@Module
public class ActivityModule{
private Context mContext    ;
private Context mAppContext = App.getAppContext();
    public ActivityModule(Context context) {
        mContext = context;
    }
    @Named("Activity")
    @Provides
    public Context provideContext(){  
        return mContext;
    }
    @Named("Application")
    @Provides
    public Context provideApplicationContext (){
        return mAppContext;
    }
}

//定義Component
@Component(modules={ActivityModule.class}) 
interface ActivityComponent{   
    void inject(Container container);   
}

//定義Container
class Container extends Fragment{
    @Named("Activity") 
    @Inject
    Context mContext; 

    @Named("Application") 
    @Inject
    Context mAppContext; 
    ...
    public void init(){
         DaggerActivityComponent.
     .activityModule(new ActivityModule(getActivity()))
     .inject(this);
     }

}

這樣,只有相同的@Named的@Inject成員變量與@Provides方法才可以被對(duì)應(yīng)起來(lái)。

**更常用的方法是使用@Qualifier來(lái)自定義注解

@Qualifier   
@Documented   //起到文檔提示作用
@Retention(RetentionPolicy.RUNTIME)  //注意注解范圍是Runtime級(jí)別
public @interface ContextLife {
    String value() default "Application";  // 默認(rèn)值是"Application"
} 

//定義Module
@Module
public class ActivityModule{
private Context mContext    ;
private Context mAppContext = App.getAppContext();
    public ActivityModule(Context context) {
        mContext = context;
    }
    @ContextLife("Activity")
    @Provides
    public Context provideContext(){  
        return mContext;
    }
    @ ContextLife ("Application")
    @Provides
    public Context provideApplicationContext (){
        return mAppContext;
    }
}

//定義Component
@Component(modules={ActivityModule.class}) 
interface ActivityComponent{   
    void inject(Container container);   
}

//定義Container
class Container extends Fragment{
    @ContextLife ("Activity") 
    @Inject
    Context mContext; 

    @ContextLife ("Application") 
    @Inject
    Context mAppContext; 
    ...
    public void init(){
         DaggerActivityComponent.
     .activityModule(new ActivityModule(getActivity()))
     .inject(this);

     }

}

組件依賴

假設(shè)ActivityComponent依賴ApplicationComponent。當(dāng)使用ActivityComponent注入Container的時(shí)候,如果找不到對(duì)應(yīng)的依賴,就會(huì)到ApplicationCoponent中去查找。但是ApplicationComponent必須顯式把ActivityComponent找不到的依賴提供給ActivityComponent。

//定義ApplicationModule
@Module
public class ApplicationModule {
    private App mApplication;

    public ApplicationModule(App application) {
        mApplication = application;
    }

    @Provides
    @ContextLife("Application")
    public Context provideApplicationContext() {
        return mApplication.getApplicationContext();
    }

}

//定義ApplicationComponent
@Component(modules={ApplicationModule.class})
interface ApplicationComponent{
    @ContextLife("Application")
    Context getApplication();  // 對(duì)外提供ContextLife類型為"Application"的Context
}

//定義ActivityComponent
@Component(dependencies=ApplicationComponent.class, modules=ActivityModule.class)
 interface ActivityComponent{
    ...
}

進(jìn)階用法

單利用法

創(chuàng)建某些對(duì)象有時(shí)候是耗時(shí)、浪費(fèi)資源的或者需要確保其唯一性,這時(shí)就需要使用@Singleton注解標(biāo)注為單利。

@Module
class MyModule{
    @Singleton   // 標(biāo)明該方法只產(chǎn)生一個(gè)實(shí)例
    @Provides
    B provideB(){
        return new B();
    }
}
@Singleton  // 標(biāo)明該Component中有Module使用了@Singleton
@Component(modules=MyModule.class)
class MyComponent{
    void inject(Container container)
}

※注意:Java中,單例通常保存在一個(gè)靜態(tài)域中,這樣的單例往往要等到虛擬機(jī)關(guān)閉時(shí)候,該單例所占用的資源才釋放。但是,Dagger通過(guò)注解創(chuàng)建出來(lái)的單例并不保持在靜態(tài)域上,而是保留在Component實(shí)例中。所以說(shuō)不同的Component實(shí)例提供的對(duì)象是不同的。

自定義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í)級(jí)別

對(duì)于Android,我們通常會(huì)定義一個(gè)針對(duì)整個(gè)App全生命周期的@PerApp的Scope注解和針對(duì)一個(gè)Activity生命周期的@PerActivity注解,如下:

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

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

@PerApp的使用例:
@Module
public class ApplicationModule {
    private App mApplication;

    public ApplicationModule(App application) {
        mApplication = application;
    }

    @Provides
    @PerApp
    @ContextLife("Application")
    public Context provideApplicationContext() {
        return mApplication.getApplicationContext();
    }

}

@PerApp
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
    @ContextLife("Application")
    Context getApplication();

}

// 單例的有效范圍是整個(gè)Application
public class App extends Application {
private static  ApplicationComponent mApplicationComponent;
    public void onCreate() {
        mApplicationComponent = DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule(this))
                .build();
    }

    // 對(duì)外提供ApplicationComponent
    public static ApplicationComponent getApplicationComponent() {
        return mApplicationComponent;
    }
}

@PerActivity的使用例子:

public abstract class BaseActivity extends AppCompatActivity {
    protected ActivityComponent mActivityComponent;
    // 對(duì)外提供ActivityComponent
    public ActivityComponent getActivityComponent() {
        return mActivityComponent;
    }

    public void onCreate() {
        mActivityComponent = DaggerActivityComponent.builder()
                .applicationComponent(App.getApplicationComponent())
                .activityModule(new ActivityModule(this))
                .build();
    }
}

通過(guò)上面的例子可以發(fā)現(xiàn),使用自定義Scope可以很容易區(qū)分單例的有效范圍。

子組件

可以使用@Subcomponent注解拓展原有component。Subcomponent其功能效果優(yōu)點(diǎn)類似component的dependencies。但是使用@Subcomponent不需要在父component中顯式添加子component需要用到的對(duì)象,只需要添加返回子Component的方法即可,子Component能自動(dòng)在父Component中查找缺失的依賴

//父Component:
@Component(modules=××××)
public AppComponent{    
    SubComponent subComponent ();  // 這里返回子Component
}
//子Component:
@Subcomponent(modules=××××)   
public SubComponent{
    void inject(SomeActivity activity); 
}

// 使用子Component
public class SomeActivity extends Activity{
    public void onCreate(Bundle savedInstanceState){
        App.getComponent().subCpmponent().inject(this); // 這里調(diào)用子Component
    }    
}

懶加載

可以使用Lazy來(lái)包裝Container中需要被注入的類型為延遲加載

public class Container{
    @Inject Lazy<B> b; 
    public void init(){
        DaggerComponent.create().inject(this);
        B b=b.get();  //調(diào)用get時(shí)才創(chuàng)建b
    }
}

另外可以使用Provider實(shí)現(xiàn)強(qiáng)制加載,每次調(diào)用get都會(huì)調(diào)用Module的Provides方法一次,和懶加載模式正好相反。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評(píng)論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,855評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,833評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,016評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,273評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評(píng)論 1 288
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,730評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,006評(píng)論 2 374

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