Android 神兵利器Dagger2使用詳解(一)基礎(chǔ)篇
本系列書寫原因:在公司一個新的共同開發(fā)項目中,使用到了Dagger2依賴注入,在使用它的時候,因為框架的原因產(chǎn)生了一些問題(代碼風(fēng)格的不同?),發(fā)現(xiàn)自己對于Dagger2還是有一些沒有理解到位的地方,于是干脆抽個時間搞懂它,從最基礎(chǔ)的使用開始,我們一點點從源碼深入它,去感受依賴注入可以給代碼開發(fā)帶來怎樣的魅力。
本系列所有文章:
Android 神兵利器Dagger2使用詳解(一)基礎(chǔ)使用
Android 神兵利器Dagger2使用詳解(二)Module&Component源碼分析
Android 神兵利器Dagger2使用詳解(三)MVP架構(gòu)下的使用
Android 神兵利器Dagger2使用詳解(四)Scope注解的使用及源碼分析
告別Dagger2模板代碼:DaggerAndroid使用詳解
告別Dagger2模板代碼:DaggerAndroid原理解析
該系列首發(fā)于我的CSDN專欄 :
Android開發(fā):Dagger2詳解
1 什么是依賴注入
依賴注入是一種面向?qū)ο蟮木幊棠J剑某霈F(xiàn)是為了降低耦合性,所謂耦合就是類之間依賴關(guān)系,所謂降低耦合就是降低類和類之間依賴關(guān)系。可能有的人說自己之前并沒有使用過依賴注入,其實真的沒有使用過嗎?當(dāng)我們在一個類的構(gòu)造函數(shù)中通過參數(shù)引入另一個類的對象,或者通過set方法設(shè)置一個類的對象其實就是使用的依賴注入,比如:
//簡單的依賴注入,構(gòu)造方法或者set()方法都屬于依賴注入
public class ClassA {
ClassB classB;
public void ClassA(ClassB b) {
classB = b;
}
}
另外還有一種方式可以作為依賴注入,那就是通過注解的方式注入,Dagger2就是通過注解的方式完成依賴注入的。
使用方式1,添加依賴(在module的build.gradle中添加如下代碼)
compile 'com.google.dagger:dagger:2.7'
annotationProcessor 'com.google.dagger:dagger-compiler:2.7'
使用方式2,最新的AndroidStudio在使用apt插件的時候已經(jīng)會報warn了,但并不是不能使用,我們也可以通過apt插件使用Dagger2:
在你的Project build.gradle中添加如下代碼
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
//添加apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
然后添加依賴(在module的build.gradle中添加如下代碼)
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
compile 'com.google.dagger:dagger:2.7'
apt 'com.google.dagger:dagger-compiler:2.7'
compile 'org.glassfish:javax.annotation:10.0-b28'
...
}
二 基本使用
Dagger2最好的搭配是通過MVP框架進(jìn)行開發(fā),但是我們不急,先從最簡單的一個案例開始入手。
1 我們先創(chuàng)建一個簡單的Student類:
public class Student {
@Inject
public Student() {
}
}
非常簡單,沒有任何屬性,Student類中只有一個空的構(gòu)造方法,不同以往的是,我們在構(gòu)造方法上面添加了一個@Inject注解,這個注解有什么用呢?
我們使用ctrl+F9(mac使用Cmd+F9)進(jìn)行一次編譯,幾秒后編譯結(jié)束,似乎沒有發(fā)生任何事情......
那當(dāng)然是不可能的,我們打開app下的路徑,我的路徑是:app\build\generated\source\apt\debug\com\mei_husky\sample_dagger2\model\Student_Factory.java
唉,我們發(fā)現(xiàn),似乎編譯器幫我們自動生成了一個文件,叫做Student_Factory類,我們點擊進(jìn)去:
@Generated(
value = "dagger.internal.codegen.ComponentProcessor",
comments = "https://google.github.io/dagger"
)
public enum Student_Factory implements Factory<Student> {
INSTANCE;
@Override
public Student get() {
return new Student();
}
public static Factory<Student> create() {
return INSTANCE;
}
}
代碼并不難理解,似乎是一個工廠類,在通過create()創(chuàng)建后,每次調(diào)用get()方法都能獲得一個Student對象。
我們似乎明白了點什么,原來我們通過@Inject注解了一個類的構(gòu)造方法后,可以讓編譯器幫助我們產(chǎn)生一個對應(yīng)的Factory類,通過這個工廠類我們可以通過簡單的get()方法獲取到Student對象!
2.創(chuàng)建一個Activity調(diào)用Student:
public class A01SimpleActivity extends AppCompatActivity {
@BindView(R.id.btn_01)
Button btn01;
@Inject
Student student;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a01_simple);
ButterKnife.bind(this);
}
@OnClick(R.id.btn_01)
public void onViewClicked(View view) {
switch (view.getId()){
case R.id.btn_01: Toast.makeText(this,student.toString(),Toast.LENGTH_SHORT).show();
break;
}
}
}
接下來我們創(chuàng)建一個Activity類,在這個類中創(chuàng)建一個成員變量Student,按照Dagger2給我們的指示,當(dāng)我們需要一個Student,我們只需要在這個成員變量上方加一個@Inject注解,編譯器會自動幫我們產(chǎn)生對應(yīng)的代碼,我們就可以直接使用這個Student對象了!
本案例中我們設(shè)置一個Button,點擊Button后我們打印出這個Student對象。
事實真的如此嗎?我們直接運行代碼,并點擊Button,很遺憾,直接報空指針異常:
顯然,和平常使用的結(jié)果一樣,@Inject并沒有幫助我們初始化對應(yīng)的Student對象,或者說,我們的Activity并沒有使用剛才我們看到的Student_Factory類,不過也可以理解,我們并沒有建立Activity和Student_Factory類之間的關(guān)系嘛。
3. 曲線救國,Component接口
我們接下來創(chuàng)建一個Module類以及一個Component接口:
@Module
public class A01SimpleModule {
private A01SimpleActivity activity;
public A01SimpleModule(A01SimpleActivity activity) {
this.activity = activity;
}
}
@Component(modules = A01SimpleModule.class)
public interface A01SimpleComponent {
void inject(A01SimpleActivity activity);
}
請注意,Module類上方的@Module注解意味著這是一個提供數(shù)據(jù)的【模塊】,而Component接口上方的@Component(modules = A01SimpleModule.class)說明這是一個【組件】(我更喜歡稱呼它為注射器)。
突然出現(xiàn)的這兩個類可以稱得上是莫名其妙,因為我們從代碼上來看并不知道這對于Student和Activity之間關(guān)系有什么實質(zhì)性的進(jìn)展,但假如我們這時在Activty中添加這一段代碼:
public class A01SimpleActivity extends AppCompatActivity {
@BindView(R.id.btn_01)
Button btn01;
@Inject
Student student;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a01_simple);
ButterKnife.bind(this);
//新添代碼
DaggerA01SimpleComponent.builder()
.a01SimpleModule(new A01SimpleModule(this))
.build()
.inject(this);
}
@OnClick(R.id.btn_01)
public void onViewClicked(View view) {
switch (view.getId()){
case R.id.btn_01:
Toast.makeText(this,student.toString(),Toast.LENGTH_SHORT).show();
break;
}
}
}
然后運行代碼點擊Button,神奇的事情發(fā)生了:
顯然,添加了兩個看起來莫名其妙的Module和Component類,然后在Activity中添加一段代碼,被@Inject的Student類被成功依賴注入到了Activity中,我們接下來就可以肆無忌憚使用這個Student對象了!
三 我們?yōu)槭裁词褂靡蕾囎⑷?/h2>
這時候不可避免的,有些同學(xué)會有些疑問,我們?yōu)槭裁匆ㄙM這么大的力氣(時間成本)去學(xué)習(xí)這樣一個看起來很雞肋的Dagger呢?我們需要一個Student對象,完全可以直接通過new的方式創(chuàng)建一個嘛!
當(dāng)然是有必要的,因為通常簡單的代碼具有耦合性,而要想降低這樣的耦合就需要其他的輔助代碼,其實少代碼量和低耦合這兩者并不能同時兼顧。
試想,我們?nèi)绻ㄟ^這樣的方式,在其他的文件中創(chuàng)建了這樣若干個(心大一些,我們是一個大項目的唯一負(fù)責(zé)人),不,1000個文件中使用到了Student對象,我們至少要new 1000個新的Student類對象,這時,新的需求到了,我們的Student需要添加一個String類型的參數(shù)name。
what the fuck? 這意味我需要分別跑這1000個文件中逐個修改new Student()的那行代碼嗎?
如果是Dagger2,當(dāng)然不需要,我們只需要在Student類中做出簡單的修改即可,這在后文中將會提到(因為涉及參數(shù)問題),我們只需要知道只需輕松幾步即可完成Student的構(gòu)造修改問題,達(dá)到低耦合的效果即可。
四 神奇的Module和Component作用詳解
現(xiàn)在我們具體的思考以下一個場景:
我們假設(shè)案例中的Activity代表家庭住址,Student代表某個商品,現(xiàn)在我們需要在家(Activity)中使用商品(Student),我們網(wǎng)購下單,商家(代表著案例中自動生成的Student_Factory工廠類)將商品出廠,這時我們能夠在家直接獲得并使用商品嗎?
當(dāng)然不可能,雖然商品(Student)已經(jīng)從工廠(Factory)生產(chǎn)出來,但是并沒有和家(Activity)建立連接,我們還需要一個新的對象將商品送貨上門,這種英雄級的人物叫做——快遞員(Component,注入器)。
沒錯,我們需要這樣的一種注入器,將已經(jīng)生產(chǎn)的Student對象傳遞到需要使用該Student的容器Activity中,于是我們需要在Activity中增加這樣幾行代碼:
//新添代碼
DaggerA01SimpleComponent.builder()
.a01SimpleModule(new A01SimpleModule(this))
.build()
.inject(this);
這就說明快遞員Component已經(jīng)將對象Inject(注入)到了this(Activity)中了,既然快遞到家,我們當(dāng)然可以直接使用Student啦!
這時有朋友可能會問,原來Component起的作用是這樣,那么Module是干嘛的呢?
事實上,在這個案例中,我們將這行代碼進(jìn)行注釋后運行,發(fā)現(xiàn)我們依然可以使用Student對象:
//新添代碼
DaggerA01SimpleComponent.builder()
//.a01SimpleModule(new A01SimpleModule(this))//注釋掉這行代碼
.build()
.inject(this);
我們可以暫時這樣理解Module,它的作用就好像是快遞的箱子,里面裝載的是我們想要的商品,我們在Module中放入什么商品,快遞員(Component)將箱子送到我們家(Activity容器),我們就可以直接使用里面的商品啦!
為了展示Module的作用,我們重寫Student類,取消了@Inject注解:
public class Student {
public Student() {
}
}
Module中添加這樣一段代碼:
@Module
public class A01SimpleModule {
private A01SimpleActivity activity;
public A01SimpleModule(A01SimpleActivity activity) {
this.activity = activity;
}
//下面為新增代碼:
@Provides
Student provideStudent(){
return new Student();
}
}
然后Component不變,Activity中仍然是這樣:
DaggerA01SimpleComponent.builder()
.a01SimpleModule(new A01SimpleModule(this))
.build()
.inject(this);
然后運行,我們發(fā)現(xiàn),仍然可以點擊使用Student對象!
原因很簡單,雖然@Inject注解取消了,但是我們已經(jīng)在快遞箱子(Module)中通過@Providers放入了一個Student對象,然后讓快遞員(Component)送到了家中(Activity),我們當(dāng)然可以使用Student對象了!
五 小結(jié)
經(jīng)過簡單的使用Dagger2,我們已經(jīng)可以基本有了以下了解:
@Inject : 注入,被注解的構(gòu)造方法會自動編譯生成一個Factory工廠類提供該類對象。
@Component: 注入器,類似快遞員,作用是將產(chǎn)生的對象注入到需要對象的容器中,供容器使用。
@Module: 模塊,類似快遞箱子,在Component接口中通過@Component(modules =
xxxx.class),將容器需要的商品封裝起來,統(tǒng)一交給快遞員(Component),讓快遞員統(tǒng)一送到目標(biāo)容器中。
現(xiàn)在看回來,我們僅僅通過幾個注解就能使用對象,還是很方便的,我會在接下來的文章中對@Component和@Module注解之后,編譯期生成的代碼進(jìn)行解析,看看到底這兩個注解到底起著什么樣的作用。