Dagger2使用簡(jiǎn)介

最近在項(xiàng)目中使用了 Dagger2 這個(gè)依賴注入的框架,在這里記錄一下。第一次寫技術(shù)文章,不足之處請(qǐng)多指教。不過真的是寫出來才發(fā)現(xiàn)還是有很多不懂的地方。

介紹

  • 什么是 Dagger2
    下面這段是官網(wǎng)的簡(jiǎn)介:

Dagger is a fully static, compile-time dependency injection framework for both Java and Android.

翻譯過來就是Dagger2是Java 和 Android 的一個(gè)編譯時(shí)生成代碼的依賴注入框架,如果你沒有了解過依賴注入,可能會(huì)感到不理解,什么是依賴注入,為什么要使用依賴注入,使用依賴注入有什么優(yōu)點(diǎn),下面來簡(jiǎn)單的說一下。

前置知識(shí)

  • 什么是依賴注入 Dependency Injection

我們?cè)趯?shí)現(xiàn)一個(gè)功能的時(shí)候,往往需要?jiǎng)?chuàng)建很多對(duì)象,舉個(gè)例子,如果要實(shí)現(xiàn)一個(gè)Computer,需要幾個(gè)類:MotherBoard, Cpu, Computer。

在沒有依賴注入的時(shí)候,首先按順序構(gòu)建每個(gè)對(duì)象,先 new 一個(gè) Cpu,然后 new 一個(gè) MotherBoard,再用這兩個(gè)對(duì)象 new 一個(gè) Computer,當(dāng)邏輯簡(jiǎn)單的時(shí)候,這不會(huì)有什么問題,但是實(shí)際開發(fā)中要構(gòu)建的對(duì)象要遠(yuǎn)遠(yuǎn)比這要復(fù)雜的多,如果某個(gè)人修改了其中一個(gè)類的 Constructor,就要修改每個(gè)使用 Constructor 的地方,如果你的創(chuàng)建所有對(duì)象的代碼有成百上千行,那就很麻煩了。

使用依賴注入后,通過依賴注入的框架創(chuàng)建這些對(duì)象,不需要自己去維護(hù)依賴關(guān)系,你只需要去維護(hù)你一個(gè)依賴的配置,這就要簡(jiǎn)單的多了。依賴注入的另一個(gè)好處,是對(duì)單元測(cè)試很友好,因?yàn)槊總€(gè)對(duì)象的創(chuàng)建并不依賴于特定的邏輯,在寫單元測(cè)試的時(shí)候,某些類的功能并不需要,或者無法在測(cè)試場(chǎng)景使用,那么只需要替換掉就可以了。在開發(fā)過程中,使用依賴注入也對(duì)多人并行開發(fā)有一定的好處,如上面的例子,可以一個(gè)人開發(fā) MotherBoard,一個(gè)人開發(fā) Cpu,一個(gè)人開發(fā) Computer,每個(gè)人都不需要關(guān)心其他人的對(duì)象要如何創(chuàng)建,因?yàn)槊總€(gè)對(duì)象的創(chuàng)建都是由依賴注入框架去維護(hù)的。

說到依賴注入的同時(shí),一定會(huì)提起控制反轉(zhuǎn)(IOC),控制反轉(zhuǎn)是一種設(shè)計(jì)思路,意思就是說,原本我要用什么,是我自己創(chuàng)建,自己用,現(xiàn)在是我要用什么,我就管別人要,別人給什么用什么。這就又不得不提到一個(gè)軟件設(shè)計(jì)中的重要思想,依賴倒置原則。

那么什么是依賴倒置原則?

舉個(gè)例子,我們?cè)O(shè)計(jì)一臺(tái)電腦,先設(shè)計(jì) cpu,再設(shè)計(jì)主板,最后設(shè)計(jì)機(jī)箱,這就存在一個(gè)依賴關(guān)系,主板依賴于 cpu的尺寸,機(jī)箱依賴于主板的尺寸,看起來是沒什么問題,現(xiàn)在我們要改一下設(shè)計(jì),把 cpu 的尺寸做小一圈,這就蛋疼了,因?yàn)橹靼逡蕾囉?cpu 的尺寸,主板需要重新設(shè)計(jì),機(jī)箱依賴主板的尺寸,機(jī)箱也要重新設(shè)計(jì),整個(gè)的設(shè)計(jì)都因此而發(fā)生了變化。

現(xiàn)在我們換一種思路,首先設(shè)計(jì)電腦整體的模型,據(jù)此設(shè)計(jì)機(jī)箱的結(jié)構(gòu),再根據(jù)機(jī)箱的結(jié)構(gòu)設(shè)計(jì)主板,根據(jù)主板的結(jié)構(gòu)去設(shè)計(jì) cpu,現(xiàn)在要對(duì) cpu 的進(jìn)行修改,就只需要修改 cpu 自己就行了,并不會(huì)出現(xiàn)上面那種全部推倒重來的現(xiàn)象。

這就是依賴倒置原則,將原本由下至上的依賴關(guān)系翻轉(zhuǎn)過來,變?yōu)橛猩现料拢蠈記Q定下層功能,下層的修改對(duì)上層無任何影響,避免出現(xiàn)牽一發(fā)而動(dòng)全身的現(xiàn)象。此時(shí),上層與下層的依賴是依賴抽象,而非依賴實(shí)現(xiàn),這種方式能對(duì)上下層之間進(jìn)行解耦,也能達(dá)到最大程度的復(fù)用??刂品崔D(zhuǎn)即是依賴倒置原則的一種設(shè)計(jì)思路,而依賴注入則是這種思路的實(shí)現(xiàn)方式。

關(guān)于如何設(shè)計(jì)抽象關(guān)系,通常應(yīng)當(dāng)遵循面向軟件設(shè)計(jì)的5大原則(SOLID),依賴倒置原則也是其中之一,這里就不多說了,貼一下 wiki 上的描述把,有興趣的話可以多了解一些。

S

Single responsibility principle
a class should have only a single responsibility (i.e. changes to only one part of the software's specification should be able to affect the specification of the class)

O

Open/closed principle
“software entities … should be open for extension, but closed for modification.”

L

Liskov substitution principle
“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.” See also design by contract.

I

Interface segregation principle
“many client-specific interfaces are better than one general-purpose interface.”

D

Dependency inversion principle
one should “depend upon abstractions, [not] concretions.”

這里簡(jiǎn)單的總結(jié)一下,個(gè)人理解,軟件設(shè)計(jì)中的各種設(shè)計(jì)模式,原則,歸根結(jié)底,目的都是為了讓你的代碼更容易閱讀,擴(kuò)展和維護(hù),人不是機(jī)器,無法從數(shù)不清的代碼中快速找到自己需要的內(nèi)容,也無法在快速滾動(dòng)的代碼中定位問題,而且一切都依賴于抽象,各種設(shè)計(jì)模式,原則,本質(zhì)上都是各種形式抽象,unix 中有一句話經(jīng)常提到,keep it simple,stupid,也是同樣的道理。依賴注入便是其中的一種實(shí)現(xiàn)方式。

  • 常見注入方式

Java有很多依賴注入的框架,常見的有 Spring, Guice, Dagger1, Dagger2等。早期的注入方式是通過 xml 進(jìn)行配置(Spring),xml注入的方式有很多缺點(diǎn),比如說無法進(jìn)行代碼檢查。在 Java5 出了之后,注解提供了另一個(gè)更便捷的方式,Spring 和 Guice 等框架支持了注解的方式,在JSR 330標(biāo)準(zhǔn)出現(xiàn)之后,更是有了統(tǒng)一的官方標(biāo)準(zhǔn),使用注解進(jìn)行注入的好處是配置不容易出錯(cuò),因?yàn)榇a能編譯過,就說明寫的沒問題,但是此類注入都是通過修改編譯后生成的字節(jié)碼,或通過 JDK 提供的動(dòng)態(tài)代理(Proxy),反射等方式實(shí)現(xiàn),修改后的字節(jié)碼無法進(jìn)行 debug,運(yùn)行時(shí)出錯(cuò)很難排查。

Dagger2提供了另一種選擇,與其他依賴注入框架不同的是,Dagger2的注入方式是在編譯時(shí)生成代碼,通過 JDK 提供的 Annotation Processor Tool功能,在移動(dòng)終端這種性能有限的環(huán)境下,生成代碼的方式更合適,性能也更好。但是也存在一些局限性,例如不支持任何動(dòng)態(tài)注入,因?yàn)榇a在編譯后就已經(jīng)固定了,會(huì)增加編譯時(shí)間等。

以上內(nèi)容參考了這個(gè)視頻,有興趣的話可以看一下)

  • Annotation Processor

上面提到了注解處理器,這里簡(jiǎn)單的說一下,注解處理器是 Java5之后提供的一種在編譯時(shí)動(dòng)態(tài)生成代碼的功能,通過讀取源碼中使用注解標(biāo)記的位置,使用自己的邏輯動(dòng)態(tài)生成代碼。需要注意的是,生成代碼的時(shí)機(jī)是在編譯之前,在 Android的基于 Gradle 的編譯環(huán)境下,每個(gè) Module 都需要指定自己的注解處理器。

Dagger2基本使用

現(xiàn)在開始說具體的使用,Dagger2使用JSR 330標(biāo)準(zhǔn)的API,和自己定義的一些注解,通過幾個(gè)簡(jiǎn)單的注解和接口,就能進(jìn)行注入操作。

首先要說明的是整體的一個(gè)思路,要記住的就是 No Magic,依賴注入從根本上來說,就是創(chuàng)建一個(gè)對(duì)象并給某個(gè)變量賦值,Dagger2的本質(zhì)就注解處理器在編譯時(shí)生成代碼,實(shí)際上就是對(duì)你所需要的變量進(jìn)行賦值,而想要正確的給這個(gè)變量進(jìn)行賦值,需要兩點(diǎn):

  1. 要?jiǎng)?chuàng)建哪個(gè)對(duì)象
  2. 如何創(chuàng)建這個(gè)對(duì)象

第一點(diǎn),首先要找到要?jiǎng)?chuàng)建哪個(gè)對(duì)象,Dagger2在匹配的時(shí)候通過類型和 Qualifier 注解來定位一個(gè)對(duì)象,所以不要在寫了兩個(gè)相同類型的注入并且編譯不通過的時(shí)候感到奇怪。

第二點(diǎn),所有的注入對(duì)象,框架都要知道如何去創(chuàng)建它,以及它所依賴的其他所有對(duì)象,編譯不通過的時(shí)候,第一件事就是看報(bào)錯(cuò)信息, Dagger2的報(bào)錯(cuò)信息在大部分情況下都很詳細(xì),明確的指出了到底是缺少配置,還是配置錯(cuò)誤,或者其他原因,框架也不能把一個(gè)對(duì)象變出來,如果你都不知道這個(gè)對(duì)象是怎么創(chuàng)建的,框架又怎么能知道呢?

看到這里可能比較疑惑,不著急,先往下看。

這里有幾個(gè)關(guān)鍵性的注解:

  • @Inject
  • @Provides
  • @Module
  • @Component

先來看一個(gè)例子,最簡(jiǎn)單的注入:

還記得上面的兩點(diǎn)么,第一點(diǎn),告訴框架如何創(chuàng)建對(duì)象

public class MotherBoard {
    private Cpu mCpu;

    @Inject
    public MotherBoard(Cpu cpu) {
        mCpu = cpu;
    }

    ...
}

第二點(diǎn),告訴框架我要什么對(duì)象

public class Computer {
    @Inject
    MotherBoard motherBoard;

    ...
}

這樣在 Computer 對(duì)象中,就能獲取到 MotherBoard 了。

如果你仔細(xì)看懂了上面的內(nèi)容,就會(huì)發(fā)現(xiàn)這段代碼并不能正確編譯,因?yàn)榭蚣懿恢廊绾蝿?chuàng)建 Cpu 對(duì)象,這里來說明另一種告訴框架如何創(chuàng)建某個(gè)對(duì)象的方式:

@Provides
public static Cpu provideCpu() {
    return new Cpu();
}

那么這段代碼要在哪里執(zhí)行呢?下面來介紹另一個(gè)重要注解@Module

在Dagger2中,所有用@Provides注解的方法,都屬于一個(gè) Module,其實(shí)就是一個(gè)類上面寫@Module

@Module
class ComputerModule {
    @Provides
    static Cpu provideCpu() {
        return new Cpu();
    }

    ...
}

OK,現(xiàn)在完事齊全,只差注入了,沒有這最后一步,前面的一切都是沒用的。那么到底要怎么注入呢?現(xiàn)在需要另一個(gè)注解@Component
在 Dagger2 中,有一個(gè) graph 的概念,其實(shí)每個(gè)依賴注入框架都有類似的概念,要注入的對(duì)象,及其所有依賴的對(duì)象之間,必然存在一個(gè)完整的依賴關(guān)系圖,在注入之前,必須要能完整的構(gòu)建出這個(gè)依賴關(guān)系圖。Component 注解標(biāo)記的類代表了一個(gè)完整的依賴關(guān)系的終點(diǎn)。當(dāng)然這一切都是框架去做的,你所需要的就是正確的配置出這個(gè)依賴關(guān)系。

@Component(modules = {
        ComputerModule.class
})
public interface ComputerComponent {
    void inject(Computer computer);
}

編譯后,dagger2會(huì)生成一個(gè)ComputerComponent 接口的實(shí)現(xiàn)類,類名為 Dagger + Component 名字,調(diào)用其中的方法就可以對(duì) Computer 進(jìn)行注入了。

Computer computer = new Computer();
DaggerComputerComponent.builder().build()
                .inject(computer);

如果你連 Computer 都不想自己創(chuàng)造,也可以這么寫:

// Computer
public class Computer {
    MotherBoard motherBoard;
    @Inject
    Computer(MotherBoard motherBoard) {
        this.motherBoard = motherBoard;
    }

    ...
}

// Component
public interface ComputerComponent {
    Computer make();
}

看到這里可能會(huì)有疑問,這寫 Component,Module,Provide 的類和方法,為什么都叫這樣的名字呢?在上面注入相關(guān)的方法,類名,及后面會(huì)說到的相關(guān)注入功能,方法名類名均與注入過程無關(guān),起什么名字只是為了方便閱讀,方便閱讀也是很重要的。

最簡(jiǎn)單的說完了,下面來說一下各種不同場(chǎng)景的使用。

你可能已經(jīng)注意到了上面的DaggerComputerComponent是帶 Builder 的,那這個(gè) Builder 是做什么的呢?

Provides 方法可以是static的,也可以不是,如果不是,那么就需要一個(gè) Module 對(duì)象來調(diào)用這個(gè)方法,這時(shí)候Component 中的 Builder 就有用了。
修改一下就會(huì)變成這樣:

@Module
class ComputerModule {
    @Provides
    Cpu provideCpu() {
        return new Cpu();
    }
}

// Inject
DaggerComputerComponent.builder()
                .computerModule(new ComputerModule())
                .build()
                .inject(computer);

有的時(shí)候你可能想用接口來注入

public interface Computer {

}

public class MyComputer implements Computer {

    private MotherBoard mMotherBoard;

    @Inject
    MyComputer(MotherBoard motherBoard) {
        mMotherBoard = motherBoard;
    }
}

// Module
@Provides
Computer provideComputer(MyComputer myComputer) {
    return myComputer;
}

這種情況下,如果你的 Module 里都是上面那樣,也可以寫成這樣

@Module
abstract class ComputerModule {
    @Provides
    static Cpu provideCpu() {
        return new Cpu();
    }

    @Binds
    abstract Computer provideComputer(MyComputer myComputer);
}

這里使用一個(gè)新的注解@Binds,這個(gè)注解和@Provides的功能是相同的,區(qū)別在于,如果你的 Provide 方法入?yún)⑴c返回值相同,僅僅是做了類型轉(zhuǎn)換,那么可以省略這個(gè)方法調(diào)用,以一個(gè) abstract 方法代替,在注入時(shí)會(huì)直接使用傳入的參數(shù),如果你去看生成的代碼,就會(huì)發(fā)現(xiàn)實(shí)際并沒有調(diào)用你寫的這個(gè) abstract 方法,而是直接使用了入?yún)⒌淖兞?,減少了一個(gè)方法調(diào)用也是可以節(jié)約一些性能的,在Android這種性能有限的平臺(tái)上,還是很有意義的。

Scope

只要是依賴注入,一定會(huì)有一個(gè) Scope 的概念,通常代表了注入對(duì)象的作用域。在 Dagger2,也就代表了這個(gè)注入的對(duì)象的生命周期。

一個(gè)常見的 Scope 是 @Singleton,在可注入的類的類名上,或者 @Provides 標(biāo)記的方法上使用這個(gè)注解,表示改對(duì)象是單例的。使用的時(shí)候需要注意,如果使用Scope,對(duì)應(yīng)的 Component 也要使用此注解進(jìn)行標(biāo)記。僅僅一個(gè)@Singleton 注解實(shí)際上并不能實(shí)現(xiàn)單例,單例的實(shí)現(xiàn)是依賴于 Component 的,從同一個(gè) Component 對(duì)象進(jìn)行注入,才是單例,如果你創(chuàng)建了兩個(gè) Component用來注入一個(gè)單例,仍然會(huì)產(chǎn)生兩個(gè)不同的對(duì)象,如果打開生成的代碼進(jìn)行查看,會(huì)有更深刻的印象。

同樣也可以自己定義 Scope

@Documented
@Retention(RUNTIME)
@CanReleaseReferences
@Scope
public @interface MyScope {}

事實(shí)上,自己定義的 scope,與@Singleton 并沒有什么區(qū)別,因?yàn)镾cope 的實(shí)現(xiàn)都是基于Component,在同一個(gè) Component 對(duì)象中,標(biāo)記了 Scope 的注入對(duì)象,都只會(huì)注入同一個(gè)對(duì)象,相當(dāng)于局部的單例,主要的目的是實(shí)現(xiàn)清晰的結(jié)構(gòu)。

實(shí)際使用中可能還會(huì)有這樣的場(chǎng)景,某些情況下不需要這個(gè)Component 中再保存單例對(duì)象,這時(shí)候可以將其釋放掉,仔細(xì)看上面的自定義 Scope,有一個(gè)@CanReleaseReferences注解,使用這個(gè)注解就可以額外注入一個(gè)ReleasableReferenceManager對(duì)象來對(duì)此進(jìn)行操作。h'n

// Module
@MyScope
@Provides
static Computer provideMyComputer(MotherBoard motherBoard) {
    return new MyComputer(motherBoard);
}

// Component
@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferenceManager releasableReferenceManager;

ReleasableReferenceManager有兩個(gè)方法,releaseStrongReferences的作用是把保存的對(duì)象轉(zhuǎn)移到一個(gè) WeakReference 中,眾所周知WeakReference中的對(duì)象可以被回收,另一個(gè)方法restoreStrongReferences則可以把前面的 WeakReference 中的還沒有被回收的對(duì)象變回到強(qiáng)引用狀態(tài)。

關(guān)于ForReleasableReferences官方的例子是在內(nèi)存不足時(shí)釋放對(duì)象,但是實(shí)際應(yīng)用中并未發(fā)現(xiàn)實(shí)際的實(shí)用場(chǎng)景,待補(bǔ)充。

關(guān)于 Scope,還有一個(gè)@Reusable 注解,官方的解釋是針對(duì)某些特殊的可以隨便重復(fù)使用的對(duì)象,實(shí)際操作沒有發(fā)現(xiàn)什么區(qū)別,也沒有想到什么使用場(chǎng)景,待補(bǔ)充。

延遲注入

有的時(shí)候注入的對(duì)象并不想在注入的時(shí)候創(chuàng)建,而是在需要的時(shí)候自己去創(chuàng)建

@Inject
Lazy<MotherBoard> motherBoard;

// Inject
motherBoard.get();

多次注入

如果需要多次獲取不同的對(duì)象,如下代碼可以獲取10次主板對(duì)象,每次的對(duì)象都是一個(gè)新的主板。

Provider<MotherBoard> motherBoard;

// Inject
for (int i = 0; i < 10; i++)
    motherBoard.get();

Qualifier

前面說到Dagger2如何定位需要注入的對(duì)象,首先是通過類型,但是如果需要多個(gè)相同類型的要注入怎么辦呢?這時(shí)候就需要Qualifier 注解了。

在@Provides 或@Inject 的地方使用@Named 注解,可以指定一個(gè)名字用來匹配要注入的對(duì)象。

@Provides
@Named("MyComputer")
static Computer provideComputer() {
    return new MyConputer();
}

@Provides
@Named("HisComputer")
static Computer provideComputer() {
    return new HisConputer();
}

// Component
public interface ComputerComponent {
    @Named("MyComputer")
    Computer make();

    @Named("HisComputer")
    Computer make();
}

@Named注解使用一個(gè)字符串來給每個(gè)注入對(duì)象增加一個(gè)名字,如果在很多地方使用,比如說多個(gè) Module 中注入不同的對(duì)象,就不是很好定位,可以自己定義一個(gè)用@Qualifier標(biāo)記的注解,代替上面的@Named注解

@Qualifier
@Documented
@Retention(SOURCE)
public @interface MyComputer {
}

可選綁定

如果某個(gè)依賴對(duì)象允許不存在,可以在 Module 里使用@BindsOptionalOf注解,這樣其他的需要依賴此對(duì)象的地方,都可以寫一個(gè) Optional<MotherBoard> ,支持 guava 和 java8的 Optiona 類,不了解 Optiona 的可以自己查一下,主要是用來解決空指針異常問題的。也同樣支持 Lazy 和 Provider。

例如我現(xiàn)在設(shè)計(jì)了一種神奇的電腦可以沒有主板:

public class MotherBoard {
    private Cpu mCpu;

    MotherBoard(Cpu cpu) {
        mCpu = cpu;
    }
}

public class MyComputer implements Computer {

    private MotherBoard mMotherBoard;

    @Inject
    MyComputer(Optional<MotherBoard> motherBoard) {

        ...
    }
}

// Module
@BindsOptionalOf
abstract MotherBoard optionalMotherBoard();

現(xiàn)在即使不提供 MotherBoard 對(duì)象的注入,也不會(huì)報(bào)錯(cuò)。
在一個(gè) Module 中使用@BindsOptionalOf后,同樣的 Module 不允許使用@Provides 提供相同的注入對(duì)象,并且不能有使用@Inject 的 Constructor。當(dāng)有多個(gè) Module 提供不同的依賴關(guān)系時(shí),@BindsOptionalOf才有意義。

現(xiàn)在我修改了設(shè)計(jì),又想要主板了,于是單獨(dú)寫一個(gè)MotherBoardModule用來提供主板:

@Module
class MotherBoardModule {
    @Provides
    static MotherBoard provideMotherBoard(Cpu cpu) {
        return new MotherBoard(cpu);
    }
}

又可以把主板注入到 Computer 中了。

BindsInstance

有時(shí),Component 中需要注入的對(duì)象可能運(yùn)行時(shí)才創(chuàng)建,這是可以使用@BindsInstance注解,給 Component 傳入一個(gè)可注入的對(duì)象。比如說現(xiàn)在我的電腦里的 Cpu 改為使用其他廠家的,不自己生產(chǎn)了,那么我只需要在構(gòu)建 Component 的時(shí)候作為一個(gè)參數(shù)傳進(jìn)去就行了。

public class Cpu {
    public Cpu() {
    }
}

// Component
@Component.Builder
    interface Builder {
        @BindsInstance
        Builder useCpu(Cpu cpu);

        ComputerComponent build();
    }

// Inject
ComputerComponent computerComponent = DaggerComputerComponent.builder()
            .useCpu(new Cpu())
            .build();

需要注意這里傳入的參數(shù)不能為null,如果可能為 null,需要使用@Nullable 注解。

Dagger2進(jìn)階功能

  • @Subcomponent
  • Set, Map

前面的注入例子都是單個(gè)的對(duì)象,Dagger2還支持把多個(gè)單獨(dú)的對(duì)象注入到集合(Set,Map)中,這塊很簡(jiǎn)單,大家自己去看文檔把。

這里主要說一下另一個(gè)比較有用的功能,@Subcomponent

從名字就看出來,Subcomponent 有著和 Component 類似的功能,事實(shí)上,他們的功能基本相同。Subcomponent 的主要用法是對(duì)某些子模塊的注入功能進(jìn)行封裝,隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。

Subcomponent 目前還沒怎么用過,具體例子待補(bǔ)充。

Dagger2在 Android 中的使用

在網(wǎng)上搜到的大部分關(guān)于 Dagger2的教程,都沒有寫到 Dagger2針對(duì) Android 的特殊功能,所以特別的說明一下。前面舉了很多例子,通過 Component 可以將所需要的對(duì)象注入到某個(gè)對(duì)象中,這就導(dǎo)致了一個(gè)問題,針對(duì)每個(gè)我們被注入的對(duì)象,都需要寫一個(gè) Component 接口來實(shí)現(xiàn)注入,在 Android 中,可能最常見的被注入的對(duì)象就是 Activity 和 Fragment 了,通常的寫法是每個(gè) Activity 和 Fragment都寫一個(gè) Component。這里直接用一下官方文檔的例子:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}

看到上面的一大串代碼了么,是不是覺得好像沒什么問題?

現(xiàn)在想象一下,你有10個(gè) Activity,10個(gè) Fragment,每個(gè) Activity 和 Fragment 里面都要寫這么一長串,是不是很麻煩,最后的結(jié)果就是,每個(gè)人都照著前面一個(gè)人寫的復(fù)制粘貼,久而久之,大部分人都不記得這段代碼是做什么的,只是記得新做一個(gè)界面就要復(fù)制一份過去。顯而易見,這樣復(fù)制粘貼的代碼并不是一個(gè)易于維護(hù)和修改的代碼。

其次,在進(jìn)行如上注入的時(shí)候,你需要首先知道這個(gè) Activity 需要哪些依賴,并進(jìn)行不同的設(shè)置,尤其是在使用接口的時(shí)候,需要知道要用的實(shí)現(xiàn)類,才能進(jìn)行正確的注入,這就打破了依賴注入一個(gè)很重要的原則,使用注入對(duì)象的類不應(yīng)該知道如何創(chuàng)建所需要的對(duì)象。

為了解決上述問題,Dagger2特別提供了一個(gè)針對(duì) Android 使用的簡(jiǎn)化流程。

  1. 給你的Activity 寫一個(gè) Subcomponent,應(yīng)引用所需的 Module
@Subcomponent(modules = ComputerModule.class)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Builder
    public abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
  1. 寫一個(gè) Module 來使用這個(gè) Subcomponent
@Module(subcomponents = MainActivitySubcomponent.class)
abstract class MainActivityModule {
    @Binds
    @IntoMap
    @ActivityKey(MainActivity.class)
    abstract AndroidInjector.Factory<? extends Activity>
    bindMainActivityInjectorFactory(MainActivitySubcomponent.Builder builder);
}
  1. 現(xiàn)在需要一個(gè) Top-Level Component 來進(jìn)行注入,注意,一定要寫AndroidSupportInjectionModule,以及上面寫的 ActivityModule
@Component(modules = {
        AndroidSupportInjectionModule.class,
        MainActivityModule.class
})
@Singleton
interface AppComponent {
    void inject(MyApp app);
  1. 最后,修改你的 Application,實(shí)現(xiàn)HasActivityInjector接口,并在其中調(diào)用 AppComponent 進(jìn)行注入

public class MyApp extends Application implements HasActivityInjector {
    @Inject
    DispatchingAndroidInjector<Activity> mActivityInjector;

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return mActivityInjector;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.create().inject(this);
    }
}
  1. 最后一步,在你的 Activity 生命周期中調(diào)用注入方法
public class MainActivity extends AppCompatActivity {
    @Inject
    Computer mComputer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
    }
}

之后添加新的 Activity 只需要重復(fù)1,2步,并在AppComponent中加入新建的 Module 即可。

是不是覺得很復(fù)雜,沒關(guān)系,還可以寫的更簡(jiǎn)單:

@Module
abstract class MainActivityModule {
    @MyScope
    @ContributesAndroidInjector(modules = {ComputerModule.class})
    abstract MainActivity contributeYourActivityInjector();
}

用上面的代碼替換1,2步的內(nèi)容即可。這種寫法適用于 Subcomponent 中不需要任何其他內(nèi)容的情況。

寫了這么多,是不是覺得很奇怪,為什么這么寫就能注入了?查看AndroidSupportInjectionModule的代碼可以看到,里面定義了Android 中常用的組件的 Map 類型的注入,key 是對(duì)應(yīng)的Class 類,而 value 這是上面第一步定義的MainActivitySubcomponent.Builder類,這個(gè) Builder 類我們?cè)诘诙揭呀?jīng)注入到 這個(gè)Map 中了,然后在 AndroidInjection 的 inject 方法中,根據(jù) Activity 的類型去獲取對(duì)應(yīng)的 Builder 類進(jìn)行注入。

Android中其他組件的注入方式與 Activity 類似,就不多說了,大家可以自己去查看文檔。

這里還存在一些疑問,在 Subcomponent 的 Builder 類中,是可以添加@BindsInstance 注解傳入?yún)?shù)的,但是如果使用 AndroidInjectiion的 inject 方法進(jìn)行注入,是無法傳入?yún)?shù)的,官方也沒有實(shí)際的例子可供參考,可能是我對(duì) Subcomponent的理解還不夠,初步猜測(cè)可能需要自己重寫AndroidInejction,在里面針對(duì)某個(gè)特定的需要參數(shù)的 Builder 進(jìn)行單獨(dú)的初始化操作。如果你有想法,歡迎一起討論。

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

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