Dagger2 系列(一)Dagger2 入門簡(jiǎn)介

Dagger 是為 Java 和 Android 平臺(tái)提供的一個(gè)完全靜態(tài)的,在編譯時(shí)進(jìn)行依賴注入的框架。Dagger 由 Square 公司出品,Dagger2 是 Google 在 Dagger 基礎(chǔ)上的二次開發(fā)。

Dagger2 官方資料

Github: https://github.com/google/dagger
官方文檔: https://google.github.io/dagger/
API: https://google.github.io/dagger/api/latest/

Dagger2 優(yōu)點(diǎn)

為什么要使用這個(gè)框架呢?

  • 提升開發(fā)效率,省去重復(fù)的簡(jiǎn)單體力勞動(dòng),同時(shí)使代碼更加優(yōu)雅。比如 new 一個(gè)實(shí)例的過(guò)程就是簡(jiǎn)單的重復(fù)的勞動(dòng),Dagger2 完全可以勝任此工作,使開發(fā)人員將精力放在關(guān)鍵業(yè)務(wù)上。
  • 更好的管理類實(shí)例。ApplicationComponent 管理整個(gè) app 的全局類實(shí)例,所有的全局類實(shí)例都統(tǒng)一交給 ApplicationComponent 管理,并且它們的生命周期與 app 的生命周期一樣。每個(gè)頁(yè)面對(duì)應(yīng)自己的 Component,頁(yè)面 Component 管理著自己頁(yè)面所依賴的所有類實(shí)例。因?yàn)?Component,Module,整個(gè) app 的類實(shí)例結(jié)構(gòu)變的很清晰。
  • 解耦。假如某類的構(gòu)造函數(shù)發(fā)生變化,那些設(shè)計(jì)到的類都要進(jìn)行修改,解耦避免了代碼的僵化性。

依賴注入

Dagger2 是一個(gè)依賴注入框架,那么依賴注入又是什么呢?我們先把這個(gè)詞拆開看,一個(gè)是依賴,一個(gè)是注入。

class Player{
    MediaFile media;
    public Player() {
        media = new MediaFile();
    }
}

看上述代碼,在 Player 類中,依賴一個(gè) MediaFile 類的對(duì)象。依賴就是有聯(lián)系,有地方使用到它就是對(duì)它有依賴,一個(gè)系統(tǒng)不可能完全的避免依賴。如果一個(gè)類在任何地方都沒使用到,那么這個(gè)類就可以刪掉了。注入就是將 MediaFile 的一個(gè)實(shí)例對(duì)象賦值給 media 引用。其實(shí)注入的方法有很多種,構(gòu)造函數(shù)只是其中一種,我們還可以通過(guò) setter 方法注入,如

public void setMedia(MediaFile media) {
        this.media = media;
}

通過(guò) 接口方式注入,如

class Player implements IPlayer {
    MediaFile media;
    public void play(IMediaFile file) {
        media = file;
    }
}

現(xiàn)在來(lái)看依賴注入,就是在有依賴需要的地方傳入一個(gè)實(shí)例對(duì)象啊。那么依賴注入這個(gè)詞到底是怎么來(lái)的呢?我們還是先看一下依賴倒置原則吧!

DIP,依賴倒置原則

DIP,英文全稱 Dependence Inversion Principle,由軟件開發(fā)大師 Robert C. Martin 提出,具體描述為:

  • 高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象。
  • 抽象不應(yīng)該依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴于抽象。

直接講原則有點(diǎn)枯燥,假設(shè)我們想用播放器播放一段音頻,看一段普通代碼實(shí)現(xiàn):

public class Test {
    Player player;
    MediaFile mediaFile;
    
    public void test(){
        player = new Player();
        mediaFile = new MediaFile();
        player.play(mediaFile); 
    }
}

上述代碼就違反了 DIP 原則,Test 類 直接依賴低層模塊 Player、MediaFile,而沒有依賴于其抽象。我們可以用 DIP 原則修改,將 Player 抽象成 IPlayer 接口,MediaFile 抽象為 IMediaFile,讓 DI 類去依賴二者的抽象,修改后代碼如下所示,

public class DI {
    IMediaFile mediaFile;
    IPlayer player;
    public void test() {
        mediaFile = new MediaFile();
        player = new Player();
        player.play(mediaFile);
    }

    interface IPlayer {
        void play(IMediaFile file);
    }

    class Player implements IPlayer {
        public void play(IMediaFile file) {
        }
    }
    
    interface IMediaFile {
        String FilePath();

    }
    
    class MediaFile implements IMediaFile {
        public String FilePath() {
            return "";
        }
    }
}

注意:實(shí)際開發(fā)過(guò)程中不要為了實(shí)現(xiàn)某一原則而過(guò)度設(shè)計(jì)。

IOC ,控制反轉(zhuǎn)

控制反轉(zhuǎn),Inversion of Control,是 Martin Flower 在2004 年總結(jié)提煉出來(lái),也是面向?qū)ο笾匾呢?fù)責(zé)之一,旨在減小程序中不同部分的耦合問(wèn)題。
在 DI 類中,我們只能生成固定的播放器和媒體文件,假如我想換一個(gè)播放器播放視頻咋辦,用哪個(gè)播放器播放音頻或者視頻,是否能夠根據(jù)需求動(dòng)態(tài)配置呢? IOC 就解決了這個(gè)問(wèn)題。我們假設(shè)共有兩款播放器:QQ 和 百度,兩種媒體文件:音頻和視頻。我們可以把這種具體需求交給一個(gè)配置文件 config.properties(文件名、后綴名可隨便寫),然后讓程序運(yùn)行的時(shí)候去讀取配置文件的內(nèi)容去生成具體的實(shí)例??创a(僅核心部分代碼),

public static void main(String[] args) {
        // 采用哪種播放器,播放哪個(gè)媒體文件可以轉(zhuǎn)給第三方框架控制,由第三方框架去讀取配置文件(可行方式一),再?zèng)Q定生產(chǎn)哪個(gè)播放器實(shí)例和媒體文件的實(shí)例。
        // 控制權(quán) 由起先的用戶 轉(zhuǎn)給了第三方框架。
        Properties properties = new Properties();
        try {
            /**
             * 根據(jù)配置文件的位置進(jìn)行讀取數(shù)據(jù),由于現(xiàn)在.properties文件在src目錄下,所以直接
             * IoC.class.getClassLoader().getResourceAsStream("config.properties")就可以獲取到配置文件的信息
             */
            properties.load(IoC.class.getClassLoader().getResourceAsStream("ioc/config.properties"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        /**
         *將配置文件中的信息進(jìn)行分割,返回成一個(gè)數(shù)組
         */

        String playerName = properties.getProperty("player");
        String mediaName = properties.getProperty("media");

        IPlayer _player = PlayerFactory.getPlayer(playerName);
        IMediaFile _mtype = MediaFactory.getMediaFile(mediaName);

        _player.play(_mtype);
    }

config.properties 文件內(nèi)容如下,運(yùn)行程序輸出結(jié)果為“QQPlayer+video”。配置文件內(nèi)容以鍵值對(duì)的形式存在,player 后對(duì)應(yīng)是具體的播放器,media 后對(duì)應(yīng)的是具體的媒體類型,這個(gè)可以按需修改。

player:qqq
media:video

IOC 和 DI 的關(guān)系:

所以控制反轉(zhuǎn) IoC(Inversion of Control)是說(shuō)創(chuàng)建對(duì)象的控制權(quán)進(jìn)行轉(zhuǎn)移,以前創(chuàng)建對(duì)象的主動(dòng)權(quán)和創(chuàng)建時(shí)機(jī)是由自己把控的,而現(xiàn)在這種權(quán)力轉(zhuǎn)移到第三方,比如轉(zhuǎn)移交給了 IoC 容器,它就是一個(gè)專門用來(lái)創(chuàng)建對(duì)象的工廠,你要什么對(duì)象,它就給你什么對(duì)象,有了 IoC 容器,依賴關(guān)系就變了,原先的依賴關(guān)系就沒了,它們都依賴 IoC 容器了,通過(guò) IoC 容器來(lái)建立它們之間的關(guān)系。

DI(依賴注入)其實(shí)就是 IOC 的另外一種說(shuō)法,DI是由Martin Fowler 在2004年初的一篇論文中首次提出的。他總結(jié):控制的什么被反轉(zhuǎn)了?就是:獲得依賴對(duì)象的方式反轉(zhuǎn)了。

簡(jiǎn)單使用

若想在工程中使用 Dagger2 框架,需要在 build.gradle 文件中添加配置:

dependencies {
    ...
    compile 'com.google.dagger:dagger:2.11-rc2'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
}

若 Android Gradle plugin 插件版本低于 2.2,還需要引入 android-apt 插件,https://bitbucket.org/hvisser/android-apt

接下來(lái)就可以使用了,下文將會(huì)介紹最簡(jiǎn)單的用法并將Dagger 的整個(gè)框架串一下,高級(jí)用法及原理會(huì)在以后的文章中介紹。

case-1

假設(shè)我們有一輛下車,名字是蘭博基尼,我們?cè)?Activity 中將其名字顯示出來(lái)。此用例只需用到兩個(gè)注解,@Inject 和 @Component,@Inject 用在 Car 的構(gòu)造函數(shù)上和被依賴的地方。@Component 相當(dāng)于一個(gè)中間人的角色,負(fù)責(zé)牽線,將需要依賴的地方和提供依賴的地方連接起來(lái)。

public class Car {
    private String name = "我是一輛小車,Lamborghini";
    @Inject
    public Car() {
    }
    @Override
    public String toString(){
        return name;
    }
}

public interface CarComponent {
    void injects(MainActivity mainActivity);
}

Activity 代碼如下

public class MainActivity extends AppCompatActivity {
    @Inject
    Car mCar;
    TextView mTv;

    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerCarComponent.builder().build().injects(this);
        mTv = findViewById(R.id.tv);
        mTv.setText(mCar.toString());
    }
}

顯示結(jié)果如下圖,證明 mCar 經(jīng)過(guò)了初始化,要不然會(huì)有空指針異常。


顯示結(jié)果
case-2

case-1 有一個(gè)局限性,就是 @Inject 必須要修飾一個(gè)類的構(gòu)造方法,若我們是應(yīng)用的第三方工具包,我們是無(wú)法修改其源碼的,這時(shí)候我們可以用 @Module 、@Provides來(lái)幫忙。

  • 第一步,用 @Module 聲明一個(gè)類,代表這個(gè)類是擁有對(duì)外提供實(shí)例的功能。
  • 第二步,用 @Provides 修飾一個(gè)方法,該方法返回具體的實(shí)例對(duì)象。
  • 第三步,修改用 @Component 修飾的 interface,指定其需要的 Module 模塊。
@Module
public class CarModule {
    @Provides
    public Car getsssssssCar(){
        return new Car();
    }
}

@Component(modules = CarModule.class)
public interface CarComponent {
    void injects(MainActivity mainActivity);
}

其余代碼不變,運(yùn)行程序,仍能看到上圖顯示結(jié)果。

相關(guān)注解

看上述代碼,分析可以發(fā)現(xiàn),要實(shí)現(xiàn)依賴注入至少需要三個(gè)角色:

  • 依賴需求者,類似于 MainActivity 中 用 @Inject 修飾的引用。
  • 依賴提供者,類似用 @Inject 修飾的構(gòu)造函數(shù)和及 Module。
  • 中間人,將二者串聯(lián)起來(lái)的角色,一個(gè)小手牽依賴的需求者,一個(gè)小手牽依賴的提供者。

我們將其中用到的注解解釋下:

  • @Inject,注意下,這個(gè)注解類的全名稱是javax.inject.Inject,是 Java 擴(kuò)展包定義的注解。@Inject 可以修飾一個(gè)引用,代表此處可通過(guò)依賴注入傳入一個(gè)實(shí)例對(duì)象,需要注意的是引用的訪問(wèn)修飾符不能是 private 和 protected,默認(rèn)包訪問(wèn)權(quán)限即可;還可以修飾一個(gè)類的構(gòu)造函數(shù),作為一個(gè)標(biāo)記,代表框架可能會(huì)根據(jù)該標(biāo)調(diào)用此構(gòu)造函數(shù)生成實(shí)例對(duì)象。
  • @Module、@Provides,主要是為了解決第三方類庫(kù)問(wèn)題而生的,Module 中可以定義多個(gè)創(chuàng)建實(shí)例的方法,這些方法用 @Provides 標(biāo)注。
  • @Component,是一個(gè)中間人的角色(也可理解為干活的秘書),也是一個(gè)注入器,負(fù)責(zé)將生成的實(shí)例對(duì)象注入到依賴的需求者中,同時(shí)管理多個(gè) Module。

我們將幾個(gè)核心注解的功能以講故事的方式在串一下,以小明要買玩具為例吧!

首先小明家境很富裕,他老爸直接給配了個(gè)秘書(@Component),小明有啥事都可以讓秘書去做。小明看上了挖掘機(jī)的玩具(用 @Inject 修飾的引用),于是就叫秘書去買把。但是玩具哪有賣的呢?秘書說(shuō)先去商店(@Module)逛一逛吧,假設(shè)該商店的一個(gè)貨架(Provides)上正好有這個(gè)玩具,秘書直接買回去給小明就是了。若商店沒有的話,商店老板告訴秘書我們這沒有,我可以嘗試聯(lián)系一下玩具廠家(用@Inject 修飾的構(gòu)造函數(shù)),看他們那有沒有,他那有的話可以從那去取,沒有的話就真的沒有了。

依賴規(guī)則

再補(bǔ)充一下依賴關(guān)系,假如既用 @Inject 聲明了構(gòu)造函數(shù),也在 Module 提供了響應(yīng)方法,那么到底用哪個(gè)呢?Dagger2 的依賴規(guī)則是這樣的:

  • 步驟1:查找Module中是否存在創(chuàng)建該類的方法。
  • 步驟2:若存在創(chuàng)建類方法,查看該方法是否存在參數(shù)
  • 步驟2.1:若存在參數(shù),則按從步驟1開始依次初始化每個(gè)參數(shù)
  • 步驟2.2:若不存在參數(shù),則直接初始化該類實(shí)例,一次依賴注入到此結(jié)束
  • 步驟3:若不存在創(chuàng)建類方法,則查找Inject注解的構(gòu)造函數(shù),看構(gòu)造函數(shù)是否存在參數(shù)
  • 步驟3.1:若存在參數(shù),則從步驟1開始依次初始化每個(gè)參數(shù)
  • 步驟3.2:若不存在參數(shù),則直接初始化該類實(shí)例,一次依賴注入到此結(jié)束

參考文獻(xiàn)

http://m.itdecent.cn/p/6eee326566b4
http://m.itdecent.cn/p/cd2c1c9f68d4
https://blog.csdn.net/bestone0213/article/details/47424255

最后編輯于
?著作權(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)容