帶你進入Dagger2的世界

參考資料:

大力感謝如下文章的作者:
Dagger2 最清晰的使用教程:https://blog.csdn.net/heng615975867/article/details/80355090

前言

由于之前一直主要從事SDK研發(fā),屬于重邏輯輕UI的設計。
正好去年用Kotlin研發(fā)了個應用,接上峰要求 使用MVVP框架,配合Dagger2實現(xiàn),并使用了很多比較新的技術。
硬著頭皮,依葫蘆畫瓢,項目是完成了,但是很多技術沒有深入了解。
這如鯁在喉,一直想弄明白。
本著 "程序員不能讓自己出現(xiàn)明顯的技術短板"(不知道哪位大神說的),現(xiàn)在正好有時間,先從Dagger2下手。

為啥寫這篇文檔:

  1. 網(wǎng)上有挺多資料都是用Kotlin來闡述的,所以換個Java語言,以示區(qū)分。
  2. 看別人的例子,屬于一看就會,一寫就廢。很少有循序漸進的演示例子。因此本文提供完整的Demo演示。
  3. 同時我會說一下創(chuàng)建工程依賴版本的注意點。
  4. Demo里面增加了很多注釋,應該能幫助到有需要的人。
  5. 里面的有些注釋或者內(nèi)容,對于某些人而言,可能比較啰嗦,抱歉了~
小建議:最佳學習方式就是自己寫Demo,哪怕是把參考的照著敲一遍也行。
  1. 這樣會有一個循序漸進的過程。
  2. 能深入理解某行代碼添加所帶來的改變。
  3. 對Dagger2 的注解而言,你就會發(fā)現(xiàn)我上面的說的 "一看就會,一寫就廢"。因為很容易就編譯報錯。

本文檔對應的Demo地址,可以 下載 提取碼:pjyk

對Dagger2 的自我理解:

不使用Dagger2 時,我們一般如何創(chuàng)建提供對象:

  1. 創(chuàng)建一個靜態(tài)對象,類似: XXXHelper.getInstance.getData()。這是全局靜態(tài)的對象。
  2. 還可以通過實現(xiàn)MyApplication類來創(chuàng)建對象,通過類似 (MyApplication) getApplication() 來獲取MyApplication對象,這樣就可以提供全局單例了。
  3. 類似FragmentActivity 需要提供對象多個Fragment時,我們還可以通過參數(shù)傳遞的方式,把FragmentActivity的某個數(shù)據(jù)對象傳遞給 Fragment,這樣多個Fragment可以使用相同的FragmentActivity的數(shù)據(jù)。這是局部單例。
  4. 當然上面所說的對象,不要局限于類似POJO類或者JavaBean之類的,也會是某些復雜的邏輯類。比如NetManager,DBManager等。

Dagger2 的主要作用:

就是為了簡化上面的提供獲取對象的方式

說的再高大上一些,就是使用MVP模式,解耦合,因為上面創(chuàng)建對象的方式,調(diào)用的地方和實現(xiàn)的地方基本是完全耦合的,一但數(shù)據(jù)對象發(fā)生改變,調(diào)用的地方也要調(diào)整。

學習Dagger2 都是為了DaggerAndroid擴展庫打基礎的,如果要單獨使用Dagger2來進行Android研發(fā),請放棄這個念頭,會寫很多重復的代碼,而且任何的修改,一旦引發(fā)編譯錯誤,查找和定位問題,會讓你有砸電腦的沖動?。?!

DaggerAndroid 擴展庫,看完Dagger2 的基礎知識后,可以繼續(xù)看這篇文章,我詳細介紹了?DaggerAndroid擴展庫的使用方式。

進入Dagger2的世界:

1. 創(chuàng)建工程,引入Dagger依賴。

由于我的Demo里面會有很多Module,因此把Dagger2 的依賴提取到了外面統(tǒng)一調(diào)用。

  1. 在項目的build.gradle同級目錄,創(chuàng)建config.gradle。目前能查到的最新版本為 2.32.2。如果還有更新的,請自行替換。如果要查找com.google.dagger下dagger最新版本。http://central.maven.org/maven2/com/google/dagger 就能找到最新版本。
    不要添加Dagger_Android擴展庫,我下一篇會單獨講該擴展庫的用法
    //使用Dagger2,具體的版本在http://central.maven.org/maven2上查找
    //目前采用最新的2.23.2版本,如果有最新版本,請自行替換
    version = [
            dagger : "2.23.2"
    ]
    //這幾個dagger的版本,最好保持一致。
    dependencies = [
            "dagger" : "com.google.dagger:dagger:${version.dagger}",
            "dagger_compiler" : "com.google.dagger:dagger-compiler:${version.dagger}",
    ]
  1. 在項目的 build.gradle 中添加如下代碼。
apply from: "config.gradle"
  1. 在Module下的build.gradle 中添加依賴
    implementation rootProject.ext.dependencies.dagger
    annotationProcessor rootProject.ext.dependencies.dagger_compiler
  1. 當然,也可以不需要config.gradle文件,直接在Module下引入完整的Dagger依賴。
  2. 如果AndroidStudio無法同步工程,或者依賴下載超時后者失敗啥的??梢允褂妹钚衼硐螺d依賴,在同步一下就可以了。
./gradlew clean build

2. 學習@Inject 和@Component

Demo例子來自 DaggerTest 工程的 dagger1。這是Phone Module,可單獨運行。

1. @Inject 說明:

1. 標記在對象類中,標記在某個類的構(gòu)造函數(shù)上,說明如下(下面的接口在不同的類中):

    /**
     * @Inject 告訴Dagger 可以使用這個構(gòu)造器來構(gòu)造Rose對象。
     */
    @Inject
    public Rose(){
    }
    
     /**
     * @Inject 是告訴Dagger使用該接口構(gòu)造Pot類。
     * 同時Dagger會自動找到Rose的構(gòu)造器。
     * 如果存在Rose的構(gòu)造器了,參數(shù)就不需要添加@Inject了。
     * @param rose
     */
    @Inject
    public Pot(@Inject Rose rose){
        this.rose = rose;
    }

2. 標記在需要使用該對象的類中,比如某個Activity,有兩個用法:

    /**
     * @Inject 標注在屬性上。就是屬性注入。
     * Dagger會自動生成該Pot對象。
     * 不能為private,編譯不通過。
     */
    @Inject
    public Pot pot;

    /**
     * 方法注入:
     * Dagger2會在構(gòu)造器執(zhí)行之后,立即調(diào)用這個方法。
     * @param pot
     */
    @Inject
    public void setPot(Pot pot){
        this.pot = pot;
        Logger.logI("setPot pot:%s",pot);
    }

2. @Component類

1. 寫法很簡單:

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

2. 作用:

  1. 提供給需要使用注入對象的類來使用的,比如Activity,Application,F(xiàn)ragment等。
  2. 命名時 采用XXX + Component的方式,Dagger會自動生成 DaggerXXXComponent。
  3. 其他任意命名也可以,只是上面的方式,便于區(qū)分。
  4. inject接口,是最常見的注入器的入口方法。參數(shù)為MainActivity,就是告訴MainActivity,你可以用我提供的方法了。

3. MainActivity的調(diào)用

        /**
         * @Component 定義的類,Dagger會從inject方法,開始查找MainActivity中被@Inject標注的類。
         * 1. 先找到Pot類,再找Pot類中被@Inject標注的構(gòu)造方法。
         * 2. 發(fā)現(xiàn)Pot構(gòu)造方法中有Rose類,再找Rose類中被@Inject標注的方法。
         * 類都找全后。
         * 1. 先構(gòu)造Rose類,在構(gòu)造Pot類。
         * 2. 然后在MainActivity中的@Inject標注的Pot就有了實例。
         */
        DaggerMainActivityComponent.create().inject(this);

        String show = pot.show();
        Logger.logI("test dagger show:%s",show);

3. 學習 @Module 和@Provide

Demo例子來自 DaggerTest 工程的 dagger2。這是Phone Module,可單獨運行。

1. 兩者的關系

  1. @Module注解類,@Provide 注解方法。因此@Provide 必須在@Module注解的類中使用。
  2. @Module 里面 必須至少包含@Provide 或者@Binds(@Binds 后面說)中的一個。
  3. 看個例子,該例子中,提供了一個Rose對象。其實和上節(jié)例子中的 Rose的構(gòu)造函數(shù)中增加@Inject 效果完全一致。
@Module
public class FlowerModule {
    //此處的返回值,只能使用Rose,不能使用Flower。
    @Provides
    public Rose provideFlower(){
        Logger.logD("provideFlower is call");
        return new Rose();
    }
}

//同時,@Component標注的類需要增加該Module的應用
@Component(modules = {FlowerModule.class})
public interface MainActivityComponent {...}

2. 用途:代替@Inject。為啥要代替?

  1. @Inject 只能標記有源碼的類。如果這個類沒有源碼,是個三方庫,就沒法使用@Inject。
  2. @Module 會讓代碼更加清晰,因為@Module 可以提供多個對象的構(gòu)建,減少了每個對象都用@Inject標注。
  3. @Component 可以依賴多個Module,有需要可以自行驗證。
    建議:少用@Inject來標注構(gòu)造函數(shù),統(tǒng)一采用Module來提供。

4. 學習@Qualifier 和 @Named用法

Demo例子來自 DaggerTest 工程的 dagger3。這是Phone Module,可單獨運行。

1. @Qualifier 是用來自定義注解用的,有個統(tǒng)一的名字叫限定符,@Named 是@Qualifier的String類型的實現(xiàn)??碄Named的類就能明白,依葫蘆畫瓢,也能寫出別的自定義@Qualifier注解。

2. 作用:

  1. 如果@Module中提供了兩個相同對象的構(gòu)建方式,那么Dagger就不知道用哪個了。因此需要區(qū)分。
  2. 因此,@Qualifier在提供對象的地方,和使用對象的地方都要添加。
  3. 例子如下:
//1. 來自@Module類的代碼片段,演示如何提供對象。
    @Provides
//    @Named("black") //用字符串也可以。
    @BlackRose //為自定義限定符。
    public Rose provideRoseBlack(){
        Logger.logV("provideFlower black is call");
        return new Rose("Black");
    }

    @Provides
//    @Named("red")
    @RedRose
    public Rose provideRoseRed(){
        Logger.logV("provideFlower Red is call");
        return new Rose("Red");
    }
    
//2. 來自MainActivity 的代碼片段,演示如何獲取對象:
    @Inject
//    @Named("red")
    @RedRose //使用限定符Provide后,在注入的地方,也要表明,兩者必須匹配。
    public Rose rose;

    @Inject
//    @Named("black")
    @BlackRose
    public Rose blackRose;

//3. RedRose的實現(xiàn),@BlackRose類似:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface RedRose {
}

5. 學習@Component中的denpendence用法和@Subcomponent的用法

Demo例子來自 DaggerTest 工程的 dagger4,演示denpendence。這是Phone Module,可單獨運行。
Demo例子來自 DaggerTest 工程的 dagger5,演示@Subcomponent。這是Phone Module,可單獨運行。

1. 用途和作用:

  1. 通過之前的例子可以看出來,MainActivity獲取某個注入對象的時為:MainActivity -> MainActivityComponent -> MainActivityModule -> 某個對象。就要求所有注入的對象都要在@Module類中提供。
  2. 如果把一部分對象放在另外Component + Module中提供,會使得邏輯更加清晰。
  3. 因此denpendence 和 @Subcomponent 就提供了這樣的方式,因此這兩個方法都是用來擴展Component對象的,便于MainActivity 通過注入一個Component就能實現(xiàn)所有對象的注入。

2. 先來看denpendence的用法(工程中Module dagger4):

  1. 我們搭建一個這樣的演示環(huán)境。
    1. 用 XXXComponent + XXXModule + XXX對象 的流程 分別提供 Rose 和Lily兩個對象的獲取。
    2. 讓LilyComponent dependence RoseComponent。
    3. 讓 MainActivityComponent dependence LilyComponent。
  2. 具體代碼演示:
//1. RoseModule 和 RoseComponent類和上面的例子相同。
//2. LilyComponent 修改如下。
@Component(modules = {LilyModule.class},dependencies = {RoseComponent.class})
public interface LilyComponent {
    Lily getLily();
}
//3. MainActivityComponent 修改如下:
@Component(dependencies = LilyComponent.class)
public interface MainActivityComponent {
    void inject(MainActivity activity);
}
//4. MainActivity 注入代碼修改如下:TODO注意這里寫法有了變化
        //拆開寫法如下:
//        RoseComponent roseComponent = DaggerRoseComponent.create();
//        LilyComponent lilyComponent = DaggerLilyComponent.builder().roseComponent(roseComponent).build();
//
//        DaggerMainActivityComponent.builder().lilyComponent(lilyComponent).build().inject(this);


        //采用級聯(lián)寫法:推薦:
        DaggerMainActivityComponent.builder()
                .lilyComponent(
                        DaggerLilyComponent.builder().roseComponent(
                                DaggerRoseComponent.create()).build()
                        )
                .build().inject(this);
  1. dependence使用的應用,在注入時,需要顯示的提供??瓷厦娴腗ainActivity就能發(fā)現(xiàn)。

3. 再看@Subcomponent的用法(工程中的Module dagger5)

  1. 環(huán)境搭建和dependence中類似,但是寫法順序發(fā)生了變化。
  2. 看代碼:
//1. 先創(chuàng)建MainComponent類,注意,這是個@Subcomponent類。孤零零的一個類,啥也不依賴,最底層的一個小弟。
@Subcomponent
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}
//2. LilyModule不做修改,LilyComponent做如下改造,依然是@Subcomponent類,可以提供MainActivityComponent。算是二弟。
@Subcomponent(modules = {LilyModule.class})
public interface LilyComponent {
    MainActivityComponent plus();
}

//3. RoseModule不做修改,RoseComponent做如下改造。這是@Component,提供了LilyComponent。這是大哥
//看靠上面dependence的對比,RoseComponent從對底層,現(xiàn)在變成了大哥。
@Component(modules = {RoseModule.class})
public interface RoseComponent {
    LilyComponent plus(LilyModule lilyModule);
}

//4. MainActivity注入代碼發(fā)生變化,因為@Component標注的是RoseComponent,因此注入從RoseComponent發(fā)起。
        //級聯(lián)寫法如下:推薦寫法。
        DaggerRoseComponent.create()
                //如下plus返回LilyComponent
                .plus(new LilyModule())
                //如下plus 返回MainActivityComponent
                .plus()
                .inject(this);

4. dependence和@Subcomponent用法區(qū)別:

  1. 兩者功能相同,都是擴展Component類。但是從上面的兩個例子,可以很明顯的發(fā)現(xiàn),dependence和@Subcomponent 觸發(fā)邏輯是相反的。
  2. 另外,從MainActivity來看,用了dependence,就得顯示的提供DaggerXXXComponent的調(diào)用。而@Subcomponent,就不需要提供了。

5. 何時用dependence或者@Subcomponent?

網(wǎng)上有一些人會說 何時用 dependence,何時用@Subcomponent?
我的建議是:先不用管,知道兩者的大概用途就行了。
因為就像文章上面說的,Dagger2 是為了DaggerAndroid 打基礎的。
在DaggerAndroid擴展庫中,再看兩者的具體使用位置。

6. 學習@Scope和@Singleton

Demo例子來自 DaggerTest 工程的 dagger6,演示局部單例。這是Phone Module,可單獨運行。
Demo例子來自 DaggerTest 工程的 dagger7,演示全局單例。這是Phone Module,可單獨運行。

1. @Scope 稱為作用域,也有的叫 "命名符",管理生命周期的。@Scope是用來定義注解的,Singleton是@Scope的一個實現(xiàn)類。有點像@Qualifier 和 @Named的關系

2. 作用和用途:

  1. 其實這就是說明一個對象是全局單例,還是局部單例。文章最上面已經(jīng)說過了。類似通過MyApplication 提供的對象是全局對象。類似FragmentActivity 通過傳遞參數(shù)提供給Fragment的,可以叫做局部單例。
  2. @Scope和@Singleton 就是標記了這個@Module中的@Provide方法,通過@Component提供的時候,是每次都創(chuàng)建個新的對象,還是把創(chuàng)建過的對象直接提供給MainActivity。---有點拗口。
  3. 還是用代碼來說更加直觀:可以看上面的任意例子中的MainActivity類。
@Inject
Lily lily;
@Inject
Lily otherLily;
//上面的 lily 和otherLily 是兩個不同的對象。因為沒有命名符,每次@Provide都會是新的對象。

3. 使用方法,依然通過代碼演示:

//1. LilyModule中增加@Singleton
@Module
public class LilyModule {
    @Provides
    @Singleton
    public Lily provideLily(@Named("red") Rose rose){
        return new Lily(rose);
    }
}
//2. 同時,LilyComponent也要增加。說明了Component + Module的這對組合,只會提供一份Lily對象。
@Singleton
@Component(modules = {LilyModule.class},dependencies = {RoseComponent.class})
public interface LilyComponent {
    Lily getLily();
}
//3. MainActivityComponent,依賴LilyComponent。
@ActivityScope //自定義的@Scope
@Component(dependencies = {LilyComponent.class})
public interface MainActivityComponent {
    void inject(MainActivity activity);
}
//4. @ActivityScope 代碼
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
//5. MainActivity中,lily 和 otherLily的這兩個對象的地址相同。
    @Inject
    Lily lily;
    @Inject
    Lily otherLily;

4. 額外說明:

  1. @ActivityScope 是定義@Scope。由于LilyComponent已經(jīng)使用了@Singleton,因此MainActivityComponent就不能再使用,需要定義一個。因為產(chǎn)生了依賴關系。
  2. 一個 配套的Component + Module組合,通過@Scope可以提供唯一的某個對象。
  3. 如果此時有個SecondActivity,這個類創(chuàng)建的Lily 對象 還是和MainActivity中不相同。以為SecondActivity重新使用了新的Component + Module組合。所以這是局部單例。

5. 如果MainActivity 和Second 也要使用同一份Lily對象,怎么辦?

最簡單的辦法,引入 AppComponent ,來提供全局單例。詳見 Demo中的Module dagger7代碼。

7. 學習@Binds 用法。

Demo例子來自 DaggerTest 工程的 dagger8,演示@Binds。這是Phone Module,可單獨運行。

1. @Binds和@Provide類似,都是在@Module中使用。@Provide提供具體的實現(xiàn)接口。@Binds 提供的接口,沒有實現(xiàn)。

2. 具體的寫法:

//1. 提供Peach對象的構(gòu)造
public class Peach implements IFruit {
    @Inject
    public Peach(){
    }
    @Override
    public String name() {
        return "Peach";
    }
}
//2. 提供一個Module
@Module
public abstract class FruitModule2 {
    @Binds
    abstract IFruit provideFruit(Peach peach);
}
//3. Component引入該Module。
@Component(modules = {FruitModule2.class})
public interface SecondActivityComponent {
    void inject(SecondActivity activity);
}
//4. Activity中使用。這樣,可以通過IFruit直接獲取到Peach對象。
    @Inject
    IFruit fruit;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerSecondActivityComponent.create().inject(this);
        Log.i("Loner","secondActivity find fruit:"+fruit+",name:"+fruit.name());
    }

3. 說明:

  1. @Binds提供如果提供對象。參數(shù)需要外部提供,或者Dagger可以查找到具體的構(gòu)造器。
  2. 本例子是演示最基本的用法,具體的用法講到DaggerAndroid時,會再次提到。

8. Component.Builder 和 @BindsInstance 的用法。

Demo例子來自 DaggerTest 工程的 dagger9。這是Phone Module,可單獨運行。

按照官網(wǎng)的說法,Component.Builder是用來簡化在某個Activity中Dagger注入代碼。
上面的例子中XXXModule中提供的方法都是帶參數(shù)的(有個例子中帶參數(shù)了,但是參數(shù)是可以直接構(gòu)造的)。
如果XXXModule中帶了參數(shù),在XXXActivity注入時,就需要顯示的申明。
Component.Builder就可以簡化這個過程。

1. 不使用Component.Builder時,情況大致如下:

//1. 提供MainActivityModule類
@Module
public class MainActivityModule {
    Person person;
    public MainActivityModule(String name){
        this.person = new Person(name);
    }
    @Provides
    Person providePerson(){
        return person;
    }
    /**
     * Dagger僅提供了對象的注入方式,String也是對象,所以可以提供。
     */
    @Provides
    String providePersonName(){
        return person.name();
    }
}
//2. MainActivityComponent
@Component(modules = MainActivityModule.class)
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}
//3. MainActivity中如需要這樣寫。這樣就可以通過@Inject使用providePersonName。這是個字符串。
        //由于MainActivityModule需要帶參構(gòu)造,因此Dagger不能自動生成,需要顯示的生成該對象。
        DaggerMainActivityComponent.builder()
                .mainActivityModule(new MainActivityModule("Lily"))
                .build()
                .inject(this);

2. 使用Component.Builder 時,這樣寫:

//1. SecondActivityModule如下:
@Module
public class SecondActivityModule {
    //上面提供Person的代碼全部移除,生成Name的方式,替換為參數(shù)傳入。
    // 該參數(shù)在Component.buidler中保持一致。
    @Provides
    String providePersonName(Person person){
        return person.name();
    }
}
//2. SecondActivityComponent如下:
@Component(modules = SecondActivityModule.class)
public interface SecondActivityComponent {
    void inject(SecondActivity mainActivity);

    //如下是關鍵代碼
    @Component.Builder //固定不變
    interface Builder{ //固定不變
        SecondActivityComponent build(); //固定不變。
        //該接口的參數(shù)和@Module中@Provide的參數(shù)保持一致,目的就是為其提供實例。
        @BindsInstance Builder person(Person person);
    }
}
//3. SecondActivity 寫法。
        DaggerSecondActivityComponent.builder().person(new Person("Lucy")).build().inject(this);

3. 說明:

  1. 上面兩個對比,就可以發(fā)現(xiàn)Component.Builder方式簡化一些代碼。
  2. 其他文檔上面寫法不太一樣,有些寫法已經(jīng)被淘汰了。
  3. Component.Builder 用法基本為固定套路,記住就行了。

9. 其他

  1. Lazy:如果某個對象的構(gòu)造器會存在復雜邏輯,可以使用Lazy延遲獲取。

//1. 寫法。
@Inject
Lazy<Lily> lily
//2. 用法:
lily.get() //此時才會真實的創(chuàng)建 lily對象。
  1. @IntoMap,ClassKey等等,在詳解DaggerAndroid擴展庫時,會有說明。

11. 本文檔對應的Demo地址,請 下載 提取碼:pjyk

10. Dagger-Android擴展庫的簡單說明:

  1. 如果覺得學完上面的Dagger2 就用在Android研發(fā)上,那就打錯特錯了,Dagger2只是基礎,只是學習DaggerAndroid擴展庫的基礎。
  2. Dagger2 的學習網(wǎng)上的資料還是挺多,但是到了DaggerAndroid擴展庫時,就會發(fā)現(xiàn)少了很多,或者寫法已經(jīng)用了淘汰的方法。
  3. 接下來去看這篇文章吧:DaggerAndroid
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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