依賴注入框架--Dagger2

依賴注入(DI, Dependency Injection)份企,目的是為了解決類之間的耦合称近,方便測試块攒。

減少耦合的一個原則是,一個類A需要的類B的對象的實(shí)例化不應(yīng)該在A中實(shí)現(xiàn)南蹂,而是通過一定的方式從外部傳入B的實(shí)例,這樣就解決了類AB之間的耦合犬金。

(PS:寫這篇文章的目的不是說自己已經(jīng)掌握了Dagger2,其實(shí)還有很多沒有深刻理解的地方。把自己研究這幾天的成果寫出來晚顷,給想了解Dagger2的同學(xué)做個參考峰伙,同時也作為自己的一個備忘。有錯誤的地方该默,請大家斧正瞳氓。)

舉個例子:

public class ClassA {
    public ClassA() {
        //do some init work
    }
}

public class ClassB {
    
    private ClassA mInstanceA;

    public ClassB() {
        //ClassA的實(shí)例化在ClassB的構(gòu)造方法中執(zhí)行
        instanceA = new ClassA();
    }
}

如上,ClassB依賴于ClassA栓袖,并且在ClassB的構(gòu)造方法中完成了ClassA的實(shí)例化匣摘。這樣看上去似乎沒有什么問題,但是當(dāng)CLassA的構(gòu)造方法改變裹刮,則必須要修改ClassB的代碼音榜。這時ClassAClassB之間有耦合,牽一發(fā)動全身捧弃。

依賴注入可以很好的解決這個問題囊咏。有很多種注入依賴的方法:

1.通過接口注入依賴。

重構(gòu)一下ClassB的寫法:

public interface IClassB {
        
    void setClassA(ClassA a);

}
public class ClassB implements IClassB {
    
    private ClassA mInstanceA;
    
    @Override
    void setClassA(ClassA a) {
        mInstanceA = a;
    }
    
}

2.通過setter方法注入依賴塔橡。

public class ClassB {
    
    private ClassA mInstanceA;
    
    public void setInstanceA(ClassA a) {
        mInstanceA = a;
    }
    
}

3.通過構(gòu)造方法注入依賴梅割。

public class ClassB {
    
    private ClassA mInstanceA;
    
    public ClassB(ClassA a) {
        mInstanceA = a;
    }
    
}

4.通過依賴注入框架注入依賴。

開始講這篇文章的主角--Dagger2葛家。

Dagger2是目前Android上比較流行的一個依賴注入框架户辞,Google出品。之前在項(xiàng)目中@劉青也用到了這個框架癞谒,非常適合與MVP架構(gòu)配合使用底燎。

大家知道在MVP架構(gòu)中,View持有Presenter的引用弹砚,Presenter持有Model的引用双仍,其實(shí)說白了就是依賴。使用Dagger2可以很方便的注入這些依賴桌吃。

貼出部分代碼:

public class LoginFragment extends BaseFragment implements LoginView {
    
    @Inject 
    LoginPresenter mLoginPresenter;
    
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        injectDependencies();
    }
    
    private void injectDependencies() {
        DaggerLoginComponent.builder()
                .appComponent(ComponentHolder.getAppComponent())
                .loginModule(ModuleProvider.getInstance().provideLoginModule(this, getActivity()))
                .build()
                .inject(this);
    }

}

可以看出朱沃,LoginFragment依賴于LoginPresenter。在mLoginPresenter上添加@Inject注解茅诱,然后在onCreate方法中做了某種操作后逗物,LoginPresenter就被注入到LoginFragment中了,具體是哪種操作瑟俭,我們往下看翎卓。

首先看下Dagger2中常見的幾個注解:

1.@Inject

在字段或構(gòu)造方法前加上這個注解,表明這個字段需要被注入依賴或者標(biāo)注依賴對應(yīng)的構(gòu)造方法摆寄。

2.@Module

@Module標(biāo)注的類作為依賴的提供者失暴。

3.@Provide

在方法前加上這個注解坯门,表明這個方法提供依賴,通常用在被@Module注解的類的方法上逗扒。舉個例子:

@Provides
InjectEntity provideInjectEntity() {
    return new InjectEntity();
}

表明這個方法提供InjectEntity的依賴田盈。

@Component

Component是一個注入器。@Inject需要注入缴阎,@Module提供注入允瞧,Component就是這兩者之間的橋梁。被@Component注解的一定是接口蛮拔。

@Scope

@Scope可以自定義注解述暂,通過自定義注解可以限定注解的作用域。

@Qualifier

當(dāng)類的類型不足以鑒別一個依賴的時候建炫,我們就可以使用這個注解標(biāo)示畦韭。

代碼

拿Google官方的demo來講:

@Module
public class AndroidModule {
  private final DemoApplication application;

  public AndroidModule(DemoApplication application) {
    this.application = application;
  }

  @Provides 
  @Singleton
  @ForApplication
  Context provideApplicationContext() {
    return application;
  }

  @Provides 
  @Singleton
  LocationManager provideLocationManager() {
    return (LocationManager) application.getSystemService(LOCATION_SERVICE);
  }
}

這是一個Module,用來提供依賴的肛跌∫张洌看provideApplicationContext方法,@Provides注解表明它提供Context的依賴衍慎。當(dāng)某個Context類型的字段被@Inject注解時转唉,Dagger2會到Module中尋找返回值為Context,并且被@Provides標(biāo)注的方法稳捆,通過這個方法將依賴注入到需要的類中赠法。

看到這里你可能會有疑問,如果一個Module中有多個方法被@Provides標(biāo)注乔夯,并且返回值是Context類型時砖织,該如何選擇?這時就需要用到@Qualifier標(biāo)注了末荐,先看下@ForApplication的具體寫法:

@Qualifier @Retention(RUNTIME)
public @interface ForApplication {
}

在提供依賴的方法以及需要依賴注入的字段前都用@ForApplication標(biāo)注侧纯,這兩個就構(gòu)成了一一對應(yīng)關(guān)系,如果Module中還有一個返回值為Context的方法甲脏,但是沒有用@ForApplication標(biāo)注眶熬,就可以將其排除。

@ForApplication只是一個標(biāo)記剃幌,你可以改成別的名字聋涨,只要能區(qū)別開其他有相同返回值的方法即可。

如下是被依賴字段的@Qualifier寫法:

@Named("ForApplication") 
@Provides
Person provideContext(Context context) {
    return new Person(context);
}
另一種寫法

其實(shí)還有一種簡單的负乡,不用定義新注解的寫法:

@Named("Context")
@Provides
Person provideApplicationContext() {
 
    return new application;
    
}
@Named("Context")
@Inject
Context context;

在提供依賴的方法以及需要依賴注入的字段前都加上@Named標(biāo)注,只要@Name里面的字符串相同脊凰,則構(gòu)成一一對應(yīng)的關(guān)系抖棘。這種寫法會比較簡便茂腥,但是字符串的標(biāo)記容易導(dǎo)致不匹配,字符串寫錯了是不會報錯的切省。

再看@Singleton注解最岗。這是Dagger2自帶的一個注解,用于保持在同一個Component中的對象是單例的朝捆。其實(shí)你去看@Singleton的源碼般渡,就簡單的幾行:

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

事實(shí)上,如果你自己定義一個注解芙盘,把Singleton名字換掉驯用,效果和@Singleton是一樣的。

可以下這樣一個結(jié)論:

只要Component上添加的@Scope注解和Module中提供依賴的方法上添加的@Scope注解一樣儒老,就會保持這個方法返回的對象在該Component實(shí)例中是單例的蝴乔。

這也就是很多使用Dagger2的例子中使用的@PerApp@PerActivity的來歷驮樊。

public class DemoApplication extends Application {
  
  @Singleton
  @Component(modules = AndroidModule.class)
  public interface ApplicationComponent {
    void inject(DemoApplication application);
    void inject(HomeActivity homeActivity);
    void inject(DemoActivity demoActivity);
  }
  
  @Inject LocationManager locationManager;
  
  private ApplicationComponent component;

  @Override public void onCreate() {
    super.onCreate();
    component = DaggerDemoApplication_ApplicationComponent.builder()
        .androidModule(new AndroidModule(this))
        .build();
    component().inject(this);
  }

  public ApplicationComponent component() {
    return component;
  }
}

DemoApplication中有一個接口ApplicationComponent薇正,用@Component注解表示這是一個Component,后面的(modules = AndroidModule.class)指明了與其相關(guān)聯(lián)的Module囚衔。

需要注意的是挖腰,Component必須是一個接口,而且需要引用到目標(biāo)類的實(shí)例练湿,上面代碼中的inject()方法就是為Component提供目標(biāo)類的曙聂,之所以這樣是因?yàn)?code>Component需要目標(biāo)類的實(shí)例來尋找目標(biāo)類中被@Inject標(biāo)記的字段,以便為這些字段到Module找相應(yīng)的提供依賴的方法鞠鲜。舉個例子:

上述Component中有這個方法

void inject(DemoApplication application);

Component拿到DemoApplication的實(shí)例后會在其中找到下面這個字段:

 @Inject LocationManager locationManager;

然后宁脊,Component會到其對應(yīng)的Module中去尋找用@Provides標(biāo)記的,并且返回值是LocationManager的方法贤姆,通過找到的這個方法得到LocationManager的實(shí)例榆苞。到這里也就完成了依賴的注入。

繼續(xù)看HomeActivity的寫法:

public class HomeActivity extends DemoActivity {
  @Inject LocationManager locationManager;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ((DemoApplication) getApplication()).component().inject(this);

    Log.d("HomeActivity", locationManager.toString());
  }
}

繼承自DemoActivity不用管霞捡,對理解Dagger沒什么意義坐漏。

可以看到有一個被@Inject注解的字段locationManager,表示這個字段需要依賴注入碧信。在onCreate中調(diào)用了Componentinject方法赊琳,傳入目標(biāo)類的引用。流程就像我們上面分析的那樣砰碴,到Component到Module中尋找@Provides方法躏筏,不再贅述。

注意到之前AndroidModule中提供LocationManager依賴的方法是加入了Singletion依賴的呈枉,說明所提供的LocationManagerComponent中是單例的趁尼。我們可以再加一個LocationManager驗(yàn)證一下是否真的是單例(提示一下埃碱,這里有個坑,不要被我?guī)нM(jìn)去了)酥泞。

HomeActivity代碼改成如下所示:

public class HomeActivity extends DemoActivity {
  @Inject LocationManager locationManager;
  //新加一個相同類型的字段砚殿,用來驗(yàn)證是否真的是單例的
  @Inject LocationManager locationManager2;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ((DemoApplication) getApplication()).component().inject(this);

    Log.d("HomeActivity", locationManager.toString());
    Log.d("HomeActivity", locationManager2.toString());
  }
}

運(yùn)行之后看下打印:

04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf

嗯芝囤,的確是一樣的似炎。那如果將Singletion去掉是不是應(yīng)該就不一樣了呢?去掉試下:

04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf

還是一樣的悯姊?
額羡藐,只能說Google這個例子選的不好,為什么一定要用LocationManager這個類來舉例子呢挠轴?翻上去看看provideLocationManager()這個方法咋寫的:

getSystemService(LOCATION_SERVICE);

Android中的系統(tǒng)級別服務(wù)采用集合形式的單例模式,所以不管你調(diào)多少次传睹,返回的都是同一個實(shí)例。其實(shí)可以在provideLocationManager()方法中加一個打印岸晦,你會發(fā)現(xiàn)加上@Singleton注解欧啤,provideLocationManager()只調(diào)用一次,去掉會調(diào)用兩次启上,這樣就符合我們預(yù)期了邢隧。

組件之間的依賴

看一個例子:

@Module
public class SubModule {
    
    @Provides
    public InjectEntity provideEntity(Context context) {
        return new InjectEntity();
    }
    
}

很顯然,SubModule提供一個InjectEntity的依賴冈在,但是提供依賴的同時provideEntity方法還依賴一個Context對象倒慧,如果在SubModule里有@Provides方法返回Context對象,就像下面這樣:

@Module
public class SubModule {

    private Context mContext;

    public SubModule(Context context) {
        mContext = context;
    }
    
    @Provides
    public Context provideContext() {
        return mContext;
    }
    
    @Provides
    public InjectEntity provideEntity(Context context) {
        return new InjectEntity();
    }
    
}

這樣provideEntity方法會從provideContext方法中獲得Context對象的依賴包券。但是問題是如果SubModule中就是沒有提供Context依賴的方法怎么辦呢纫谅?這時會到其所依賴的Component中尋找:

@Component(modules = MainModule.class)
public Interface MainComponent {

    void inject(DemoApplication application);
    
}
@Module
public class MainModule {
    
    private Context mContext;
    
    public MainModule(Context context) {
        mContext = context;
    }
    
    @Provide
    public Context provideContext() {
        return mContext;
    }
    
}
@Component(dependencies = MainComponent.class, Module = SubModule.class)
public interface SubComponent {

    void inject(HomeActivity activity);
    
}

上面代碼邏輯很清晰,SubComponent依賴于MainComponent溅固,在SubModule中找不到的依賴會通過MainComponentMainModule中去尋找签孔。

這里有一個需要注意的點(diǎn):

一個沒有作用域(unscoped )的組件不可以依賴有作用域的組件.

組件之間的依賴在Android中一般體現(xiàn)為ActivityFragment作用域的Component依賴于App作用域的Component然想,后者為前兩者提供Application實(shí)例或一些需要在App全局使用的單例對象藕溅。


以上

參考:

google dagger2 官方參考文檔:

https://google.github.io/dagger/

google官方demo地址:

https://github.com/google/dagger/tree/master/examples/android-simple

其他文章:

http://blog.csdn.net/lisdye2/article/details/51942511

http://www.reibang.com/p/39d1df6c877d

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末齿尽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亮元,更是在濱河造成了極大的恐慌猛计,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爆捞,死亡現(xiàn)場離奇詭異奉瘤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嵌削,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門毛好,熙熙樓的掌柜王于貴愁眉苦臉地迎上來望艺,“玉大人苛秕,你說我怎么就攤上這事肌访。” “怎么了艇劫?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵吼驶,是天一觀的道長。 經(jīng)常有香客問我店煞,道長蟹演,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任顷蟀,我火速辦了婚禮酒请,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸣个。我一直安慰自己羞反,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布囤萤。 她就那樣靜靜地躺著昼窗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涛舍。 梳的紋絲不亂的頭發(fā)上澄惊,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音富雅,去河邊找鬼掸驱。 笑死,一個胖子當(dāng)著我的面吹牛没佑,可吹牛的內(nèi)容都是我干的毕贼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼图筹,長吁一口氣:“原來是場噩夢啊……” “哼帅刀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起远剩,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤扣溺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓜晤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锥余,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年痢掠,在試婚紗的時候發(fā)現(xiàn)自己被綠了驱犹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘲恍。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雄驹,靈堂內(nèi)的尸體忽然破棺而出佃牛,到底是詐尸還是另有隱情,我是刑警寧澤医舆,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布俘侠,位于F島的核電站,受9級特大地震影響蔬将,放射性物質(zhì)發(fā)生泄漏爷速。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一霞怀、第九天 我趴在偏房一處隱蔽的房頂上張望惫东。 院中可真熱鬧,春花似錦毙石、人聲如沸廉沮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽废封。三九已至,卻和暖如春丧蘸,著一層夾襖步出監(jiān)牢的瞬間漂洋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工力喷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刽漂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓弟孟,卻偏偏與公主長得像贝咙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拂募,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

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