Android 神兵利器Dagger2使用詳解(一)基礎(chǔ)使用

Android 神兵利器Dagger2使用詳解(一)基礎(chǔ)篇

本系列書(shū)寫(xiě)原因:在公司一個(gè)新的共同開(kāi)發(fā)項(xiàng)目中,使用到了Dagger2依賴(lài)注入,在使用它的時(shí)候,因?yàn)榭蚣艿脑虍a(chǎn)生了一些問(wèn)題(代碼風(fēng)格的不同?),發(fā)現(xiàn)自己對(duì)于Dagger2還是有一些沒(méi)有理解到位的地方,于是干脆抽個(gè)時(shí)間搞懂它,從最基礎(chǔ)的使用開(kāi)始,我們一點(diǎn)點(diǎn)從源碼深入它,去感受依賴(lài)注入可以給代碼開(kāi)發(fā)帶來(lái)怎樣的魅力。

本系列所有文章:

Android 神兵利器Dagger2使用詳解(一)基礎(chǔ)使用
Android 神兵利器Dagger2使用詳解(二)Module&Component源碼分析
Android 神兵利器Dagger2使用詳解(三)MVP架構(gòu)下的使用
Android 神兵利器Dagger2使用詳解(四)Scope注解的使用及源碼分析
告別Dagger2模板代碼:DaggerAndroid使用詳解
告別Dagger2模板代碼:DaggerAndroid原理解析
該系列首發(fā)于我的CSDN專(zhuān)欄 :
Android開(kāi)發(fā):Dagger2詳解

1 什么是依賴(lài)注入

依賴(lài)注入是一種面向?qū)ο蟮木幊棠J剑某霈F(xiàn)是為了降低耦合性,所謂耦合就是類(lèi)之間依賴(lài)關(guān)系,所謂降低耦合就是降低類(lèi)和類(lèi)之間依賴(lài)關(guān)系。可能有的人說(shuō)自己之前并沒(méi)有使用過(guò)依賴(lài)注入,其實(shí)真的沒(méi)有使用過(guò)嗎?當(dāng)我們?cè)谝粋€(gè)類(lèi)的構(gòu)造函數(shù)中通過(guò)參數(shù)引入另一個(gè)類(lèi)的對(duì)象,或者通過(guò)set方法設(shè)置一個(gè)類(lèi)的對(duì)象其實(shí)就是使用的依賴(lài)注入,比如:

//簡(jiǎn)單的依賴(lài)注入,構(gòu)造方法或者set()方法都屬于依賴(lài)注入
public class ClassA {
    ClassB classB;
    public void ClassA(ClassB b) {
        classB = b;
    }
}

另外還有一種方式可以作為依賴(lài)注入,那就是通過(guò)注解的方式注入,Dagger2就是通過(guò)注解的方式完成依賴(lài)注入的。

使用方式1,添加依賴(lài)(在module的build.gradle中添加如下代碼)

  compile 'com.google.dagger:dagger:2.7'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.7'

使用方式2,最新的AndroidStudio在使用apt插件的時(shí)候已經(jīng)會(huì)報(bào)warn了,但并不是不能使用,我們也可以通過(guò)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'
 }

然后添加依賴(lài)(在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最好的搭配是通過(guò)MVP框架進(jìn)行開(kāi)發(fā),但是我們不急,先從最簡(jiǎn)單的一個(gè)案例開(kāi)始入手。

1 我們先創(chuàng)建一個(gè)簡(jiǎn)單的Student類(lèi):

public class Student {

    @Inject
    public Student() {
    }
}

非常簡(jiǎn)單,沒(méi)有任何屬性,Student類(lèi)中只有一個(gè)空的構(gòu)造方法,不同以往的是,我們?cè)跇?gòu)造方法上面添加了一個(gè)@Inject注解,這個(gè)注解有什么用呢?

我們使用ctrl+F9(mac使用Cmd+F9)進(jìn)行一次編譯,幾秒后編譯結(jié)束,似乎沒(méi)有發(fā)生任何事情......

那當(dāng)然是不可能的,我們打開(kāi)app下的路徑,我的路徑是:app\build\generated\source\apt\debug\com\mei_husky\sample_dagger2\model\Student_Factory.java

唉,我們發(fā)現(xiàn),似乎編譯器幫我們自動(dòng)生成了一個(gè)文件,叫做Student_Factory類(lèi),我們點(diǎn)擊進(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;
  }
}

代碼并不難理解,似乎是一個(gè)工廠(chǎng)類(lèi),在通過(guò)create()創(chuàng)建后,每次調(diào)用get()方法都能獲得一個(gè)Student對(duì)象。

我們似乎明白了點(diǎn)什么,原來(lái)我們通過(guò)@Inject注解了一個(gè)類(lèi)的構(gòu)造方法后,可以讓編譯器幫助我們產(chǎn)生一個(gè)對(duì)應(yīng)的Factory類(lèi),通過(guò)這個(gè)工廠(chǎng)類(lèi)我們可以通過(guò)簡(jiǎn)單的get()方法獲取到Student對(duì)象!

2.創(chuàng)建一個(gè)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;
        }
    }
}

接下來(lái)我們創(chuàng)建一個(gè)Activity類(lèi),在這個(gè)類(lèi)中創(chuàng)建一個(gè)成員變量Student,按照Dagger2給我們的指示,當(dāng)我們需要一個(gè)Student,我們只需要在這個(gè)成員變量上方加一個(gè)@Inject注解,編譯器會(huì)自動(dòng)幫我們產(chǎn)生對(duì)應(yīng)的代碼,我們就可以直接使用這個(gè)Student對(duì)象了!

本案例中我們?cè)O(shè)置一個(gè)Button,點(diǎn)擊Button后我們打印出這個(gè)Student對(duì)象。

事實(shí)真的如此嗎?我們直接運(yùn)行代碼,并點(diǎn)擊Button,很遺憾,直接報(bào)空指針異常:

這里寫(xiě)圖片描述

顯然,和平常使用的結(jié)果一樣,@Inject并沒(méi)有幫助我們初始化對(duì)應(yīng)的Student對(duì)象,或者說(shuō),我們的Activity并沒(méi)有使用剛才我們看到的Student_Factory類(lèi),不過(guò)也可以理解,我們并沒(méi)有建立Activity和Student_Factory類(lèi)之間的關(guān)系嘛。

3. 曲線(xiàn)救國(guó),Component接口

我們接下來(lái)創(chuàng)建一個(gè)Module類(lèi)以及一個(gè)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);

}

請(qǐng)注意,Module類(lèi)上方的@Module注解意味著這是一個(gè)提供數(shù)據(jù)的【模塊】,而Component接口上方的@Component(modules = A01SimpleModule.class)說(shuō)明這是一個(gè)【組件】(我更喜歡稱(chēng)呼它為注射器)。

突然出現(xiàn)的這兩個(gè)類(lèi)可以稱(chēng)得上是莫名其妙,因?yàn)槲覀儚拇a上來(lái)看并不知道這對(duì)于Student和Activity之間關(guān)系有什么實(shí)質(zhì)性的進(jìn)展,但假如我們這時(shí)在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;
        }
    }
}

然后運(yùn)行代碼點(diǎn)擊Button,神奇的事情發(fā)生了:

這里寫(xiě)圖片描述

顯然,添加了兩個(gè)看起來(lái)莫名其妙的Module和Component類(lèi),然后在Activity中添加一段代碼,被@Inject的Student類(lèi)被成功依賴(lài)注入到了Activity中,我們接下來(lái)就可以肆無(wú)忌憚使用這個(gè)Student對(duì)象了!

三 我們?yōu)槭裁词褂靡蕾?lài)注入

這時(shí)候不可避免的,有些同學(xué)會(huì)有些疑問(wèn),我們?yōu)槭裁匆ㄙM(fèi)這么大的力氣(時(shí)間成本)去學(xué)習(xí)這樣一個(gè)看起來(lái)很雞肋的Dagger呢?我們需要一個(gè)Student對(duì)象,完全可以直接通過(guò)new的方式創(chuàng)建一個(gè)嘛!

當(dāng)然是有必要的,因?yàn)橥ǔ:?jiǎn)單的代碼具有耦合性,而要想降低這樣的耦合就需要其他的輔助代碼,其實(shí)少代碼量和低耦合這兩者并不能同時(shí)兼顧。

試想,我們?nèi)绻ㄟ^(guò)這樣的方式,在其他的文件中創(chuàng)建了這樣若干個(gè)(心大一些,我們是一個(gè)大項(xiàng)目的唯一負(fù)責(zé)人),不,1000個(gè)文件中使用到了Student對(duì)象,我們至少要new 1000個(gè)新的Student類(lèi)對(duì)象,這時(shí),新的需求到了,我們的Student需要添加一個(gè)String類(lèi)型的參數(shù)name。

what the fuck? 這意味我需要分別跑這1000個(gè)文件中逐個(gè)修改new Student()的那行代碼嗎?

如果是Dagger2,當(dāng)然不需要,我們只需要在Student類(lèi)中做出簡(jiǎn)單的修改即可,這在后文中將會(huì)提到(因?yàn)樯婕皡?shù)問(wèn)題),我們只需要知道只需輕松幾步即可完成Student的構(gòu)造修改問(wèn)題,達(dá)到低耦合的效果即可。

四 神奇的Module和Component作用詳解

現(xiàn)在我們具體的思考以下一個(gè)場(chǎng)景:

我們假設(shè)案例中的Activity代表家庭住址,Student代表某個(gè)商品,現(xiàn)在我們需要在家(Activity)中使用商品(Student),我們網(wǎng)購(gòu)下單,商家(代表著案例中自動(dòng)生成的Student_Factory工廠(chǎng)類(lèi))將商品出廠(chǎng),這時(shí)我們能夠在家直接獲得并使用商品嗎?

當(dāng)然不可能,雖然商品(Student)已經(jīng)從工廠(chǎng)(Factory)生產(chǎn)出來(lái),但是并沒(méi)有和家(Activity)建立連接,我們還需要一個(gè)新的對(duì)象將商品送貨上門(mén),這種英雄級(jí)的人物叫做——快遞員(Component,注入器)。

沒(méi)錯(cuò),我們需要這樣的一種注入器,將已經(jīng)生產(chǎn)的Student對(duì)象傳遞到需要使用該Student的容器Activity中,于是我們需要在Activity中增加這樣幾行代碼:

  //新添代碼
  DaggerA01SimpleComponent.builder()
          .a01SimpleModule(new A01SimpleModule(this))
          .build()
          .inject(this);

這就說(shuō)明快遞員Component已經(jīng)將對(duì)象Inject(注入)到了this(Activity)中了,既然快遞到家,我們當(dāng)然可以直接使用Student啦!

這時(shí)有朋友可能會(huì)問(wèn),原來(lái)Component起的作用是這樣,那么Module是干嘛的呢?

事實(shí)上,在這個(gè)案例中,我們將這行代碼進(jìn)行注釋后運(yùn)行,發(fā)現(xiàn)我們依然可以使用Student對(duì)象:

        //新添代碼
        DaggerA01SimpleComponent.builder()
              //.a01SimpleModule(new A01SimpleModule(this))//注釋掉這行代碼
                .build()
                .inject(this);

我們可以暫時(shí)這樣理解Module,它的作用就好像是快遞的箱子,里面裝載的是我們想要的商品,我們?cè)贛odule中放入什么商品,快遞員(Component)將箱子送到我們家(Activity容器),我們就可以直接使用里面的商品啦!

為了展示Module的作用,我們重寫(xiě)Student類(lèi),取消了@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);

然后運(yùn)行,我們發(fā)現(xiàn),仍然可以點(diǎn)擊使用Student對(duì)象!

原因很簡(jiǎn)單,雖然@Inject注解取消了,但是我們已經(jīng)在快遞箱子(Module)中通過(guò)@Providers放入了一個(gè)Student對(duì)象,然后讓快遞員(Component)送到了家中(Activity),我們當(dāng)然可以使用Student對(duì)象了!

五 小結(jié)

經(jīng)過(guò)簡(jiǎn)單的使用Dagger2,我們已經(jīng)可以基本有了以下了解:

@Inject : 注入,被注解的構(gòu)造方法會(huì)自動(dòng)編譯生成一個(gè)Factory工廠(chǎng)類(lèi)提供該類(lèi)對(duì)象。

@Component: 注入器,類(lèi)似快遞員,作用是將產(chǎn)生的對(duì)象注入到需要對(duì)象的容器中,供容器使用。

@Module: 模塊,類(lèi)似快遞箱子,在Component接口中通過(guò)@Component(modules =
xxxx.class),將容器需要的商品封裝起來(lái),統(tǒng)一交給快遞員(Component),讓快遞員統(tǒng)一送到目標(biāo)容器中。

現(xiàn)在看回來(lái),我們僅僅通過(guò)幾個(gè)注解就能使用對(duì)象,還是很方便的,我會(huì)在接下來(lái)的文章中對(duì)@Component和@Module注解之后,編譯期生成的代碼進(jìn)行解析,看看到底這兩個(gè)注解到底起著什么樣的作用。

GitHub傳送門(mén),點(diǎn)我看源碼

Android 神兵利器Dagger2使用詳解(二)Module&Component源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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