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

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

本系列書寫原因:在公司一個新的共同開發(fā)項目中,使用到了Dagger2依賴注入,在使用它的時候,因為框架的原因產(chǎn)生了一些問題(代碼風格的不同?),發(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?,它的出現(xiàn)是為了降低耦合性,所謂耦合就是類之間依賴關(guān)系,所謂降低耦合就是降低類和類之間依賴關(guān)系??赡苡械娜苏f自己之前并沒有使用過依賴注入,其實真的沒有使用過嗎?當我們在一個類的構(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框架進行開發(fā),但是我們不急,先從最簡單的一個案例開始入手。

1 我們先創(chuàng)建一個簡單的Student類:

public class Student {

    @Inject
    public Student() {
    }
}

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

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

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

唉,我們發(fā)現(xiàn),似乎編譯器幫我們自動生成了一個文件,叫做Student_Factory類,我們點擊進去:

@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)生一個對應的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給我們的指示,當我們需要一個Student,我們只需要在這個成員變量上方加一個@Inject注解,編譯器會自動幫我們產(chǎn)生對應的代碼,我們就可以直接使用這個Student對象了!

本案例中我們設(shè)置一個Button,點擊Button后我們打印出這個Student對象。

事實真的如此嗎?我們直接運行代碼,并點擊Button,很遺憾,直接報空指針異常:

這里寫圖片描述

顯然,和平常使用的結(jié)果一樣,@Inject并沒有幫助我們初始化對應的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ì)性的進展,但假如我們這時在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>

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

當然是有必要的,因為通常簡單的代碼具有耦合性,而要想降低這樣的耦合就需要其他的輔助代碼,其實少代碼量和低耦合這兩者并不能同時兼顧。

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

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

如果是Dagger2,當然不需要,我們只需要在Student類中做出簡單的修改即可,這在后文中將會提到(因為涉及參數(shù)問題),我們只需要知道只需輕松幾步即可完成Student的構(gòu)造修改問題,達到低耦合的效果即可。

四 神奇的Module和Component作用詳解

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

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

當然不可能,雖然商品(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)中了,既然快遞到家,我們當然可以直接使用Student啦!

這時有朋友可能會問,原來Component起的作用是這樣,那么Module是干嘛的呢?

事實上,在這個案例中,我們將這行代碼進行注釋后運行,發(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),我們當然可以使用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)一送到目標容器中。

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

GitHub傳送門,點我看源碼

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容