Android常用開源工具(1)-Dagger2入門(個人認爲新人入門Dagger2最好的文章)

介紹

Dagger 2是一種依賴注入的框架,能夠在編譯時自動生成出一些代碼,這些代碼可以幫助對應的實例初始化。 
舉個具體的例子,一個容器裏面裝的是蘋果,不用Dagger2的情況下我們應該這麼寫:

public class Container{
    Fruit f=new Apple(color,size);
    ...
}

上面例子面臨着一個問題,Container依賴了Apple實現,如果某一天需要修改Apple爲Banana,那麼你一定得改Container的代碼。有沒有一種方法可以不改Container呢? 
可以使用Dagger2,我們可以把代碼改成

public class Container{
    @Inject
    Fruit f;
    ...
}

這樣,Container的成員變量就自動初始化成Apple實例了,Container不用關心具體用哪個Fruit的實現,也不用關心到底用什麼顏色多大的蘋果。假如某一天要把蘋果替換成香蕉,Container的代碼是完全不需要改動的。從某種意義上說,Dagger2就是一個幫你寫工廠代碼的工具。當然Dagger2的功能比工廠模式更加強大。

結構

Dagger2要實現一個完整的依賴注入,必不可少的元素有三種,Module,Component,Container。 

ä¸èå³ç³»
1、Container就是可以被注入的容器,具體對應上文例子中的Container,Container擁有需要被初始化的元素。需要被初始化的元素必須標上@Inject,只有被標上@Inject的元素纔會被自動初始化。@Inject在Dagger2中一般標記構造方法與成員變量。
2、Module 可以說就是依賴的原材料的製造工廠,所有需要被注入的元素的實現都是從Module生產的。
3、有了可以被注入的容器Container,也有了提供依賴對象的Module。我們必須將依賴對象注入到容器中,這個過程由Component來執行。Component將Module中產生的依賴對象自動注入到Container中。


簡單的例子

配置

project的build.gradle添加

 dependencies {
     ... // 其他classpath
     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //添加apt命令
 }

module的build.gradle添加

// 添加其他插件
apply plugin: 'com.neenbedankt.android-apt'//添加apt命令

dependencies {
    apt 'com.google.dagger:dagger-compiler:2.0.2' //指定註解處理器
    compile 'com.google.dagger:dagger:2.0.2'  //dagger公用api
    provided 'org.glassfish:javax.annotation:10.0-b28'  //添加android缺失的部分javax註解
}

實現


實現Module

 Module其實就是一個依賴的製造工廠。我們只需要爲其添加製造依賴的方法即可,繼續上文實現蘋果容器的例子。

@Module //1  註明本類屬於Module
public class FruitModule{
    @Provides  //2 註明該方法是用來提供依賴對象的特殊方法
    public Fruit provideFruit(){
        return new Apple(Color.RED,Size.BIG);
    }
}

(1)中添加註解@Module註明本類屬於Module 
(2)中添加註解@Provides註明該方法是用來提供依賴對象的特殊方法 
一個完整的Module必須擁有@Module與@Provides註解 


實現Component 


Component就是一個將Module生成的實例注入Container中的注入器。我們來寫一個水果注入器:

@Component(modules={FruitModule.class}) //3 指明Component在哪些Module中查找依賴
public interface FruitComponent{    //4 接口,自動生成實現
    void inject(Container container);   //5  注入方法,在Container中調用
}

(3)中@Component使用modules指向使用的Module的集合。 
(4)所有的Component都必須以接口形式定義。Dagger2框架將自動生成Component的實現類,對應的類名是Dagger×××××,這個例子中對應的實現類是DaggerFruitComponent 
(5)中添加註入方法,一般使用inject做爲方法名,方法參數爲對應的Container

實現Container 

Container就是可以被注入依賴關係的容器。具體實現如下

public Container{
     @Inject   //6 添加@Inject,標記f可以被注入
     Fruit f;
     public void init(){
         DaggerFruitComponent.create().inject(this); //7 使用FruitComponent的實現類注入
     }
 }

Container除了代碼中(6)標記f需要被注入外,還需要代碼中(7)調用Component的實現類將Module的生成的對象注入到f中。

到此,當調用Container的init()方法時,Contianer中的f將會自動初始化成實現類Apple的對象。 
以後如果想更改Fruit的實現類,只需要在@Component中的modules指向不同的Module即可。而Container的代碼完全不需要改動。因爲Container已經不再依賴Apple實現了。

拓展

儘管Dagger2看起來很容易,但其實裏面各種細節很值得注意。

爲@Provides方法添加輸入參數

Module中@Provides方法可以帶輸入參數,其參數由Module集合中的其他@Provides方法提供,或者自動調用構造方法 
下面是其他@Provides方法提供的例子

@Module
public class FruitModule{
    //8輸入參數自動使用到provideFruit()的返回值Color.RED
    @Provides
    public Fruit provideFruit(Color color){         
        return new Apple(color,Size.BIG);
    }
    @Provides
    pulic Color provideFruit(){
        return Color.RED;
    }
}

如果找不到@Provides方法提供對應參數的對象,自動調用帶@Inject參數的構造方法生成相應對象

@Module
public class FruitModule{
    @Provides
    public Fruit provideFruit(FruitInfo info){//自動查找到FruitInfo中帶@Inject的無參構造器並生成實例傳入參數info
        return new Apple(info);
    }
}
public class FruitInfo{
    Color mColor;
    Size mSize;
    @Inject
    FruitInfo(){
        mColor=Color.RED;
        mSize=Size.BIG;
    }
}

添加多個Module


一個Component可以包含多個Module,這樣Component獲取依賴時候會自動從多個Module中查找獲取,Module間不能有重複方法。添加多個Module有兩種方法,一種是在Component的註解@Component(modules={××××,×××}) 添加多個modules,如下

@Component(modules={ModuleA.class,ModuleB.class,ModuleC.class}) //添加多個Module
public interface FruitComponent{
    ...
}

另外一種添加多個Module的方法可以被使用Module中的@Module(includes={××××,×××}),如下

@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class})
public class FruitModule{
    ...
}
@Component(modules={FruitModule.class}) //添加多個Module
public interface FruitComponent{
    ...
}

這種使用Module的includes的方法一般用於構建更高層的Module時候使用。

Module實例的創建

上面簡單例子中,當調用DaggerFruitComponent.create()實際上等價於DaggerFruitComponent.builder().build()。可以看出,DaggerFruitComponent使用了構造者模式。在構建的過程中,默認使用Module無參構造器產生實例。如果需要傳入特定的Module實例,可以使用

DaggerFruitComponent.builder()
.moduleA(new ModuleA()) //指定Module實例
.moduleB(new ModuleB())
.build()

如果Module只有有參構造器,則必須顯式傳入Module實例。

區分返回類型相同的@Provides 方法

當有Fruit需要注入時,Dagger2就會在Module中查找返回類型爲Fruit的方法,也就是說,Dagger2是按照Provide方法返回類型查找對應的依賴。但是,當Container需要依賴兩種不同的Fruit時,你就需要寫兩個@Provides方法,而且這兩個@Provides方法都是返回Fruit類型,靠判別返回值的做法就行不通了。這就需要使用@Named來區分,如下:

//定義Module
@Module
public class FruitModule{
    @Named("typeA")
    @Provides
    public Fruit provideApple(){  //提供Apple給對應的mFruitA
        return new Apple();
    }
    @Named("typeB")
    @Provides
    public Fruit provdeBanana(){ //提供Banana給對應的mFruitB
        return new Banana()
    }
}
//定義Component
@Component(modules={FruitModule.class}) 
interface FruitComponent{    //Dagger根據接口自動生成FruitComponent
    void inject(Container container);   
}
//定義Container
class Container{
    @Named("typeA") //添加標記@Name("typeA"),只獲取對應的@Name("typeA")的元依賴   @Inject
    Fruit mFruitA; 
    @Named("typeB") //添加標記@Name("typeA"),只獲取對應的@Name("typeA")的依賴    @Inject
    Fruit mFruitB;
    ...
    public void init(){
         DaggerFruitComponent.creaete().inject(this); //使用FruitComponent的實現類注入
     }

}

這樣,只有相同的@Named的@Inject成員變量與@Provides方法纔可以被對應起來。 
如果覺得@Named只能用字符串區分不滿足需求,你也可以自定義類似@Named的註解,使用元註解@Qualifier可以實現這種註解,比如實現一個用int類型區分的@IntNamed

@Qualifier   //必須,表示IntNamed是用來做區分用途
@Documented           //規範要求是Documented,當然不寫也問題不大,但是建議寫,做提示作用
@Retention(RetentionPolicy.RUNTIME)  //規範要求是Runtime級別
public @interface IntNamed{
    int value();
}

接下來使用我們定義的@IntNamed來修改上面FruitA,FruitB的例子如下

//定義Module
@Module
class FruitModule{
    @IntName(1)
    @Provides
    public Fruit provideApple(){  //提供Apple給對應的mFruitA
        return new Apple();
    }
    @IntName(2)
    @Provides
    public Fruit provdeBanana(){ //提供Banana給對應的mFruitB
        return new Banana()
    }
}
//定義Component
@Component(modules={FruitModule.class}) 
interface FruitComponent{    //Dagger根據接口自動生成FruitComponent
    void inject(Container container);   
}
//定義Container
class Container{
    @IntName(1) //添加標記@IntName(1),只獲取對應的@IntName(1)的元依賴 
    @Inject
    Fruit mFruitA; 
    @IntName(2) //添加標記@IntName(2),只獲取對應的@IntName(2)的依賴
    @Inject
    Fruit mFruitB;
    ...
    public void init(){
         DaggerFruitComponent.creaete().inject(this); //使用FruitComponent的實現類注入
     }

}

Component定義方法的規則

1)對應上面蘋果容器的例子,Component的方法輸入參數一般只有一個,對應了需要注入的Container。有輸入參數返回值類型就是void 
2)Component的方法可以沒有輸入參數,但是就必須有返回值: 
Step1:返回的實例會先從事先定義的Module中查找,如果找不到跳到Step2 
Step2 : 使用該類帶@Inject的構造器來生成返回的實例,並同時也會遞歸注入構造器參數以及帶@Inject的成員變量。比如

//定義ComponentB
@Component(modules={××××××××})//1.假設Module中沒有provideApp()方法,但有provideInfo()
interface ComponentB{
    Apple apple(); //2.實現類自動返回由Apple(info)構建的實現類
}
public class Apple{
    @Inject
    Apple(Info info){//被@Inject標記,使用這個構造器生成實例
        ...
    }
    Apple(){   //不會使用這個構造器,沒有被@Inject標記
    }
}

上述代碼會生成ComponentB的實現類DaggerComponetB,調用其apple()方法會自動使用Apple(info)構造器生成實例返回。 
3 ) 假設ComponentA依賴ComponentB,B必須定義帶返回值的方法來提供A缺少的依賴 
ComponentA依賴ComponentB的代碼如下

//定義ComponentB
@Component(modules={××××××××})
interface ComponentB{
    ...
}
//定義ComponentA
@Component(dependencies={ComponentB.class},modules={××××××××})//使用dependencies
interface ComponentA{
    ...
}


這樣,當使用ComponentA注入Container時,如果找不到對應的依賴,就會到ComponentB中查找。但是,ComponentB必須顯式把這些A找不到的依賴提供給A。怎麼提供呢,只需要在ComponentB中添加方法即可,如下

@Component(modules={××××××××})
interface ComponentB{
    // 假設A中module中找不到apple,banana,oranges,但是B的module有,B必須提供帶返回值的方法如下
    Apple apple();
    Banana banana();
    Oranges oranges();
}


Container中的@Inject的規則

1)@Inject可以標記Container中的成員變量,但是這些成員變量要求是包級可見,也就是說@Inject不可以標記private類型的成員變量。 
2)當@Inject標記成員變量時,查找對應依賴按照以下規則

1.該成員變量的依賴會從Module的@Provides方法集合中查找;
2.如果查找不到,則查找成員變量類型是否有@Inject構造方法,並注入構造方法且遞歸注入該類型的成員變量

強大的功能

如果說因爲Dagger2能生成工廠代碼就使用Dagger2,那麼有些牽強。Dagger2除了能產生工廠代碼,還有其他強大的功能,這些也是使用Dagger2的理由。 
Scope,Multibinding,Subcomponent,Provider與Lazy,這些將在下一篇文章介紹。
--------------------- 
作者:遠古大鐘 
來源:CSDN 
原文:https://blog.csdn.net/duo2005duo/article/details/50618171 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章