Dagger2:上手就愛不釋手

概覽:

  • 什么是Dagger2例获?
    既然有Dagger2那么必然有Dagger1的存在妇多,Dagger1是大名鼎鼎的Square公司受到Guice啟發(fā)而開發(fā)的依賴注入框架啼辣,而Dagger2是Dagger1的分支,由谷歌公司接手開發(fā)畏腕。

  • 為什么使用Dagger2?
    1.有利于模塊間的解耦,組件依賴的注入獨(dú)立于業(yè)務(wù)模塊之外
    2.能夠清晰地管理實(shí)例的有效范圍
    3.因?yàn)閷ο蟮膶?shí)例化獨(dú)立于業(yè)務(wù)茉稠,所以當(dāng)對象的實(shí)例化方法改變時(shí)描馅,不需要大量地修改業(yè)務(wù)代碼
    4.在編譯期即完成依賴的注入,完全靜態(tài)(說白了就是拋棄了反射)

深入研究

前言講了那么多而线,接下來著重從原理和代碼方面進(jìn)行分析

引入Dagger2

Dagger2的引入也非常方便铭污,首先在項(xiàng)目的gradle中配置:

 dependencies { 
         classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' 
}```   
然后在app的gradle中配置:

apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
apt 'com.google.dagger:dagger-compiler:2.0'
compile 'com.google.dagger:dagger:2.0'
}

可以看到gradle中出現(xiàn)了apt相關(guān)的配置恋日,可以猜到Dagger2的生成與apt技術(shù)相關(guān),即通過定義編譯期的注解嘹狞,再通過繼承Proccesor生成代碼邏輯岂膳,有興趣的同學(xué)可以深入學(xué)習(xí)一番

###  使用詳解

引入了Dagger2之后,首先解釋一下Dagger2中的四種常用用法
- _ @Inject_  
主要有兩種用法:一是標(biāo)注相關(guān)的構(gòu)造方法磅网,那么Dagger2在使用時(shí)會(huì)找到標(biāo)注有@Inject的構(gòu)造方法來實(shí)例化谈截;第二種是標(biāo)注在相關(guān)的變量,告知Dagger2為其提供依賴

- _@Component_ 
@Component用于標(biāo)注接口涧偷,是依賴需求方和依賴提供方之間的橋梁簸喂。被Component標(biāo)注的接口在編譯時(shí)會(huì)生成該接口的實(shí)現(xiàn)類(如果@Component標(biāo)注的接口為ApplicationComponent,則編譯期生成的實(shí)現(xiàn)類為DaggerApplicationComponent),我們通過調(diào)用這個(gè)實(shí)現(xiàn)類的方法完成依賴注入燎潮;
- _@Module_ 
@Module用于標(biāo)注提供依賴的類喻鳄,但涉及到一些第三方包中的構(gòu)造方法,或者有參數(shù)的構(gòu)造方法時(shí)跟啤,我們無法使用@Inject诽表,轉(zhuǎn)而使用@Module進(jìn)行標(biāo)注

- _@Provide_
@Provide
用于標(biāo)注@Module中的方法,提供實(shí)例化的操作隅肥,并在需要時(shí)將依賴注入標(biāo)注了@Inject的變量

隨后這幾種注解結(jié)合上述基本用法竿奏,有意想不到的妙用
- _@Qulifier_ 
@Qulifier 為了區(qū)分不同使用不同類型生成的實(shí)例,比如@ForActivity 或@ForApplication

- _@Scope_ 
可限定變量的生效范圍腥放,形成局部單例

- _@Singleton_ 
這個(gè)就比較好理解了泛啸,Application級別的單例

只是介紹了基本用法,可能理解起來還是一頭霧水秃症,我們接下來結(jié)合代碼進(jìn)行進(jìn)一步闡述

1.僅使用@Inject
首先定義實(shí)體類Coke,并用@Inject標(biāo)注構(gòu)造方法

public class Coke {
@Inject
public Coke() {
Log.d("process", "a cup of coke was made");
}
}


第二步創(chuàng)建Component候址,命名為CokeComponent。inject方法即代表依賴注入至MainActivity

@Component
public interface CokeComponent
{
void inject(MainActivity activity);
}


在MainActivity中定義變量种柑,并用@Inject標(biāo)注
```java
public class MainActivity extends AppCompatActivity 
{   
//定義變量并標(biāo)注
@Inject  Coke coke;    
@Override    
protected void onCreate(Bundle savedInstanceState) 
{
  super.onCreate(savedInstanceState);        
  setContentView(R.layout.activity_main); 
//DaggerCokeComponent由CokeComponent生成岗仑,調(diào)用下面的方法實(shí)現(xiàn)依賴注入
  DaggerCokeComponent.builder().build().inject(this);    
}
}

運(yùn)行程序,則log中打印出:

D/process: a cup of coke was made

然而事實(shí)上聚请,我們更多遇到是構(gòu)造函數(shù)含參或者無法直接打上@Inject標(biāo)記的情況荠雕,那么此時(shí)就要使用@Module進(jìn)行依賴注入

2.結(jié)合@Module使用

假設(shè)我們現(xiàn)在制造一瓶可樂的構(gòu)造方法為:public Coke(String ingredient),然后創(chuàng)建一個(gè)Module驶赏,并命名為CokeModule炸卑。用@Provide標(biāo)注提供實(shí)例的方法,并在方法體內(nèi)加入實(shí)例化相關(guān)的代碼

@Module  
public class CokeModule
{    
public CokeModule() {} 
   
@Provides    
Coke provideCoke()  {        
    return new Coke("with suger");    
  }  
}

并對Component進(jìn)行一點(diǎn)修改,告知Dagger2使用CokeModule提供依賴

@Component (modules = {CokeModule.class})
public interface CokeComponent {    
void inject(MainActivity activity);
}

運(yùn)行程序煤傍,結(jié)果為:

D/process: a cup of coke was made with suger

3.結(jié)合@Qulifier使用

首先用@Qulifier定義兩個(gè)注解

@Qualifier
@Retention (RetentionPolicy.RUNTIME)
public @interface QulifierNonSuger {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QulifierSuger {}

其次對CokeModule進(jìn)行一點(diǎn)改造盖文,支持兩種實(shí)例化方式

@Module
public class CokeModule {   
  public CokeModule() {}    
@QulifierSuger    
@Provides   
Coke provideCokeWithSuger() {        
return new Coke("with suger");   
 }    
@QulifierNonSuger    
@Provides    
Coke provideCokeWithoutSuger() {        
    return new Coke("without suger");    
 }
}

在MainActivity創(chuàng)建兩個(gè)Coke變量,并分別用兩種Qulifier注解

@QulifierSuger 
@Inject
Coke cokeA;

@QulifierNonSuger
@Inject
Coke cokeB;

運(yùn)行程序蚯姆,兩杯不同的可樂就制造出來啦

D/process: a cup of coke was made with suger
D/process: a cup of coke was made without suger

@Qulifier.png

通過上圖五续,我們可以清晰地看出洒敏,Module中通過兩種Qulifier注解表示了兩種實(shí)例化對象的方法,Component通過Module中提供的方法將依賴注入標(biāo)注了不同Qulifier的兩個(gè)變量

4.結(jié)合@Scope使用

好吧返帕,我們繼續(xù)進(jìn)行改造~
第一步桐玻,定義注解ActivityScope

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

然后用該Scope標(biāo)注Component和Module

@Module
public class CokeModule {        
public CokeModule() {}    
@ActivityScope    
@Provides    
Coke provideCokeWithSuger() {        
return new Coke("with suger");    
  }
}
@ActivityScope
@Component(modules = {CokeModule.class})
public interface CokeComponent {   
 void inject(MainActivity activity);
}

我們在MainActivity中定義了兩個(gè)Coke變量,但可以看到Log只打印了一次荆萤,即代表Coke變量在MainActivity范圍內(nèi)是局部單例的

5.擴(kuò)展應(yīng)用

Dependent Components.png

如圖镊靴,Component之間也可以有依賴關(guān)系,Dagger2的實(shí)際運(yùn)用中链韭,我們常定義一個(gè)應(yīng)用級別單例的ApplicationComponent來保持對Application的依賴偏竟,根據(jù)不同粒度(Activity級別或者頁面級別)創(chuàng)建依賴于ApplicationComponent的其余Component進(jìn)行整個(gè)應(yīng)用的依賴注入控制

至此,一個(gè)簡單的依賴注入使用案例已經(jīng)結(jié)束敞峭,但大多數(shù)的同學(xué)應(yīng)該還意猶未盡吧踊谋,因此我們接下來從Dagger2自動(dòng)生成的代碼層面深入了解Dagger2是如何工作的。

原理探究

這里我們以例子中的情況2作樣本旋讹,Dagger2生成的代碼可以在Build->generated->source->apt中找到

  • CokeModule_ProvideCokeWithSugerFactory

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class CokeModule_ProvideCokeWithSugerFactory implements Factory<Coke> { 
 private final CokeModule module; 
 public CokeModule_ProvideCokeWithSugerFactory(CokeModule module) {     
  assert module != null;    
  this.module = module;  
  }  
@Override  public Coke get() {  Coke provided = module.provideCokeWithSuger();     
 if (provided == null) {     
 throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");    
}    
  return provided;  }  
public static Factory<Coke> create(CokeModule module) {      
return new CokeModule_ProvideCokeWithSugerFactory(module);  
  }
}

代碼比較簡單殖蚕,構(gòu)造方法中傳入了CokeModule,get()中通過我們在Module創(chuàng)建的provideCokeWithSugaer()拿到Coke的實(shí)例沉迹,而create方法我們稍后進(jìn)行解釋

  • MainActivity_MembersInjector
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {  
private final MembersInjector<AppCompatActivity> supertypeInjector;  
private final Provider<Coke> cokeAndCokeAProvider;  
public MainActivity_MembersInjector(MembersInjector<AppCompatActivity> supertypeInjector, Provider<Coke> cokeAndCokeAProvider) {     
assert supertypeInjector != null;    
this.supertypeInjector = supertypeInjector;    
assert cokeAndCokeAProvider != null;   
this.cokeAndCokeAProvider = cokeAndCokeAProvider;  
  }  
@Override  public void injectMembers(MainActivity instance) {     
 if (instance == null) {      
throw new NullPointerException("Cannot inject members into a null reference");    }    
//依賴注入
supertypeInjector.injectMembers(instance);     
instance.coke = cokeAndCokeAProvider.get();  
  }  
public static MembersInjector<MainActivity> create(MembersInjector<AppCompatActivity> supertypeInjector, Provider<Coke> cokeAndCokeAProvider) {       
 return new MainActivity_MembersInjector(supertypeInjector, cokeAndCokeAProvider);  
  }
}

構(gòu)造方法中傳入了Injector和Provider睦疫,injectMembers()中將instance(MainActivity)通過回調(diào)傳給Injector,并從Provider中取出Coke的實(shí)例賦值給instance鞭呕。代碼最后又出現(xiàn)了create蛤育,我們可以肯定有一個(gè)橋梁將兩者串聯(lián)在一起,那就是——請繼續(xù)往下看

  • DaggerCokeComponent
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerCokeComponent implements CokeComponent {  
private Provider<Coke> provideCokeWithSugerProvider;  
private MembersInjector<MainActivity> mainActivityMembersInjector;  
private DaggerCokeComponent(Builder builder) {      
assert builder != null;    
initialize(builder);  
}  
public static Builder builder() {      
return new Builder();  
}  
public static CokeComponent create() {      
return builder().build();  
}  
private void initialize(final Builder builder) {      
this.provideCokeWithSugerProvider = ScopedProvider.create(CokeModule_ProvideCokeWithSugerFactory.create(builder.cokeModule));    
this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideCokeWithSugerProvider);  }  
@Override  public void inject(MainActivity activity) {   
  mainActivityMembersInjector.injectMembers(activity);  
}  
public static final class Builder {    
private CokeModule cokeModule;      
private Builder() {      }     
public CokeComponent build() {       
 if (cokeModule == null) {        
this.cokeModule = new CokeModule();      
}      
return new DaggerCokeComponent(this);    
}      
public Builder cokeModule(CokeModule cokeModule) {        
if (cokeModule == null) {        
throw new NullPointerException("cokeModule");      
}      
this.cokeModule = cokeModule;      
return this;    
    }  
  }
}

謎底揭開了葫松,結(jié)合我們在MainActivity中注入依賴的方法:
DaggerCokeComponent.builder().cokeModule(new CokeModule()).build().inject(this)
在Builder中瓦糕,傳入創(chuàng)建的CokeModule,可以看到Dagger2貼心地幫我們做了缺省設(shè)置腋么,直接調(diào)用build()也可以生成CokeModule咕娄。然后調(diào)用DaggerCokeComponent的構(gòu)造方法,在initialize中我們終于看到了剛才的兩個(gè)create方法珊擂,也就是通過這座橋梁完成了實(shí)例的提供和注入谭胚。

后記

1.文中的圖摘自CodePath Android Cliffnotes,原文為:Dependency Injection with Dagger 2
2.注意事項(xiàng):當(dāng)更新Dagger2的版本時(shí)未玻,需重新clean項(xiàng)目,否則會(huì)出現(xiàn)actual and former argument lists different in length

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胡控,一起剝皮案震驚了整個(gè)濱河市扳剿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昼激,老刑警劉巖庇绽,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锡搜,死亡現(xiàn)場離奇詭異,居然都是意外死亡瞧掺,警方通過查閱死者的電腦和手機(jī)耕餐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辟狈,“玉大人肠缔,你說我怎么就攤上這事『咦” “怎么了明未?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長壹蔓。 經(jīng)常有香客問我趟妥,道長,這世上最難降的妖魔是什么佣蓉? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任披摄,我火速辦了婚禮,結(jié)果婚禮上勇凭,老公的妹妹穿的比我還像新娘疚膊。我一直安慰自己,他們只是感情好套像,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布酿联。 她就那樣靜靜地躺著,像睡著了一般夺巩。 火紅的嫁衣襯著肌膚如雪贞让。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天柳譬,我揣著相機(jī)與錄音喳张,去河邊找鬼。 笑死美澳,一個(gè)胖子當(dāng)著我的面吹牛销部,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播制跟,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼舅桩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了雨膨?” 一聲冷哼從身側(cè)響起擂涛,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎聊记,沒想到半個(gè)月后撒妈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恢暖,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年狰右,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杰捂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棋蚌,死狀恐怖嫁佳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情附鸽,我是刑警寧澤脱拼,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站坷备,受9級特大地震影響熄浓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜省撑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一赌蔑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竟秫,春花似錦娃惯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馒稍,卻和暖如春皿哨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纽谒。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工证膨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鼓黔。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓央勒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親澳化。 傳聞我的和親對象是個(gè)殘疾皇子崔步,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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

  • Dagger2 入門 2016-12-21 更新:添加@Subcomponent注解以及Lazy與Provider...
    fxzou閱讀 28,530評論 77 331
  • 部分內(nèi)容參考自:[Android]使用Dagger 2依賴注入 - DI介紹(翻譯)[Android]使用Dagg...
    AItsuki閱讀 47,252評論 66 356
  • ****(說在最前:閱讀本篇之前,希望大家對Dagger2已經(jīng)有了一個(gè)初步的了解缎谷。從而幫助感覺似是而非的同學(xué)進(jìn)一步...
    我是昵稱閱讀 908評論 3 6
  • 畢業(yè)后進(jìn)入了圖書出版行業(yè)刷晋,對象主要是高校教,但是總覺得應(yīng)該還不錯(cuò),整天和老師打交道眼虱。起初的時(shí)候很難,我不...
    mi婭的coffee閱讀 175評論 0 0
  • 在大學(xué)的時(shí)候有人向自己告白席纽。關(guān)注著我,很久很久,反正當(dāng)時(shí)覺得挺感動(dòng)的,但是最后還是沒有答應(yīng).因?yàn)槲也恢? 一時(shí)的...
    怕1噗噗噗噗閱讀 235評論 0 0