1. 程式人生 > >秒懂依賴注入及 Dagger2 的實用技能(如何在Android中使用)

秒懂依賴注入及 Dagger2 的實用技能(如何在Android中使用)

前言

古人曰:紙上得來終覺淺,絕知此事要躬行,這話說的是真他孃的有道理啊,對於程式設計那點事似乎更是真理。2016年的時候就首次瞭解過dagger,但是沒有去編碼實現,當時哥們真覺得懂了,但是最近有個專案要使用Dagger2,突然發現還是不知道如何下手,日了個狗,於是又研究了一番,躬行了一下,總結於此,讓那些哭著喊著要求進步的程式設計師儘快淘汰沒有追求的程式設計師,淨化我們的生態環境。

概述

Dagger2 是什麼?解決什麼問題?具體如何使用?什麼原理?如何學習及可否改進?回答的了這些問題說明少年你已經頓悟了,不需要往下看了。

概念

Dagger2 是什麼

Dagger is a fully static, compile-time

dependency injection >framework for both Java and Android.

Dagger 是一個可以用於Java及Android平臺的依賴注入框架,其注入過程完全是靜態的在編譯時期完成的(通過編譯時產生程式碼的方式,區別於Spring等框架的依賴反射的方式,反射是基於執行時的)。

Dagger2 主要是通過Java註解(Annotation)來工作的,這一點首先要清楚。如果對註解不是很瞭解,請移步到 秒懂Java註解

什麼是依賴注入

維基百科上如是說:

In software engineering, dependency injection is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
依賴注入

是一種給一個物件提供其依賴的物件的技術。例如A物件需要依賴B物件,那麼此處關鍵點就是不能在A裡面去new B物件,必須通過某種方式將B物件提供給(注入)A中,例如最簡單的方式是通過一個 set(B b)方法

當首次接觸一些學術名詞的時候總是把人虎的一愣一愣的,當了解皮毛後又覺得不過如此,名字叫的到是嚇人,再往後通過實踐積累了大量相關經驗後又感覺這名字叫的真是精妙。例如依賴注入,其實相信有點面向物件高階語言程式設計經歷的人在首次接觸這個名稱以前都使用過依賴注入,只是自己渾然不知。

例如:下面這種方式中AB的依賴就不是以依賴注入的方式獲得而是A主動構建的B

public class
A { private B b=new B(); }

而下面這種方式就是將依賴注入到了A類中。

public class A {
    private B b;
    public void setB(B b) {
        this.b = b;
    }

那有的同學要問了,這使用依賴注入和不使用依賴注入有什麼區別呢?其最主要的目的就是為了解耦,隨即而來的就是可測試性,可維護,可擴充套件性就大大提高了。以上面的情況為例:

第一種方式中A,B緊緊的耦合在了一起。假設由於業務需要 B 的建構函式中多了一個引數,即B的構造方式需要變化,那麼我們就必須到A裡面去改相關的程式碼。那只有A一個類使用了B還好,那要是有100個類依賴了B呢?不管你哭不哭,反正王二狗已經哭暈在廁所了。
第二種方式就好很多,因為不需要去修改A類,只需要修改外部生成B物件的地方即可。

使用依賴注入的方式還可以寫出便於測試的程式碼。下面我們通過一個例子看一下:我們可以將AB的依賴改成依賴B實現了的介面I,如程式碼所示

public interface I{
    void test();
}
public class B implements I{
    @Override
    public void test() {
        System.out.println("總有刁民想害朕");
    }
}
public class A {
    private I b;
    public void setB(I b) {
        this.b = b;
    }

那樣,我們就可以在測試的時候從外面傳入一個同樣實現了I介面的Mock類物件,完成測試。

public class Mock implements I{
    @Override
    public void test() {
        System.out.println("這裡面可以寫測試邏輯");
    }
}

dagger2 解決什麼問題

第一:dagger 是一個依賴注入框架,首要任務當然是解決依賴注入的問題。
第二:dagger主要想通過編譯時產生程式碼的方式來解決那些基於反射的依賴注入框架所存在的缺點,例如效能問題,開發過程中存在的問題。

如何使用dagger2

在開始使用dagger2之前,首先需要理解它的一些基本概念及工作原理,不然就無從下手。
dagger2中最重要的就是InjectComponentModule,搞清楚這三個概念基本上可以上手dagger2了.

假設我們有兩個類,A類和B類,A類要依賴B類。假設A類代表王二狗,B類代表牛翠花,王二狗想讓牛翠花給他生猴子,那麼就是說王二狗要依賴牛翠花。

public class A
{
    @Inject
    B b;
    @Inject
    C c;

    @Inject
    public A(){ }

    //此處應該將結果回撥,為了簡單不寫了
    public void xxoo(){
        System.out.println("翠花,關燈。。。");
        b.birthSon();
    }

    public void appointment(){
        System.out.println("上官無雪,晚上一起去看看月亮聊聊理想啊?");
        c.turnDown();
    }
}
public class B{
     @Inject
     public B(){}

    public void birthSon(){
      System.out.println("我王二狗的兒子王不二降臨人世了!");
    }   
}

@Inject

  • A類中標記B類的欄位:告訴dagger 我們要注入B類的例項到A類中,你幫我搞定(我王二狗要牛翠花,dagger2你幫我搞定)。
  • B類中標記B類的建構函式:告訴dagger B類是一個可以被注入的類,如果你想把B例項物件注入到其他類中,例如注入到A類中,是沒有任何問題的。

@Component

雖然王二狗和牛翠花20多年前就定了娃娃親,被配成了一對,產生了聯絡(通過 @Inject標記),但是王二狗在天津,牛翠花在上海,這兩個人也沒見過面,沒法真的在一起生猴子。那就需要一個兩人都認識的中間人,將他們二人撮合到一起,這個中間人就是王婆Component。在一個月黑風高的夜晚王二狗看了一部日本愛情動作片後打開了王婆的微信,“王姐啊,你能不能幫我聯絡一下我那從未見過面的未婚妻,我想和她見個面,事成之後請你吃飯”。王婆於是開啟微信,找到牛翠花,讓她週末來天津和王二狗見面。對映到程式設計領域如下:

Component 一般是一個使用 @Component標記的介面,其持有A類的例項,其在A類中發現有使用@Inject標記的屬性b,就會去查詢這個屬性的型別B,然後呼叫其建構函式產生例項物件賦值給b。這樣就完成了對A類中b屬性的賦值,即完成了依賴注入。

@Module

按理說有了上面兩步就可以完成依賴注入了,那為什麼還要一個Module,這是為“第三者”準備的。例如王二狗這廝突然看上了新來的測試妹子上官無雪 C類,找王婆搭線。王婆說不行啊,人家設定了不接受陌生人撩撥,二狗賊心不死,央求王婆,王婆於是找到了上官無雪的閨蜜二丫,二丫把上官無雪的聯絡方式告訴了王婆,於是王婆。。。二丫就是一個Module。對映到程式設計領域如下:

C類是一個沒有使用@Inject註解其建構函式的類,而且我們也無法加上,可能由於它是一個第三方類,或者其他原因。那我們想要使用dagger2將其注入到A類中就需要使用到ModuleModule負責提供C類的例項物件,當Component發現了使用 @Inject註解的C類屬性時,先去Module中查詢,沒有的話再去看其建構函式是不是使用@Inject標記了,如果都沒有,注入失敗。

具體例項

先看下專案結構,儘量簡單了。
這裡寫圖片描述
其中A類和B類已經在上面列出了。

具體步驟如下

1: 先定位自己要使用的類,決定是以在此類建構函式上新增@Inject呢,還是在Module中提供@providers方法的方式來使用注入。例如我們的C類,我們使用第二種方式。

public class C
{
    public void turnDown(){
        System.out.println("癩蛤蟆想吃天鵝肉,滾!");
    }
}

2 : 定義Module,如果使用第一種方式,可以沒有Module,此步驟可以省略.

@Module
public class DaggerModule
{
    //此處為了儘量簡單,其實這邊可以有很多種寫法
    @Provides
    public C providerC(){
        return new C();
    }
}

3:定義Component,就是一個使用@Component標記的一個介面,把這個Component 要用到的Modulemodules = {Module1.class,Module2.class}的形式提供出來,例如下面的程式碼中,MatchComponent 依賴了一個DaggerModule

@Component(modules = {DaggerModule.class})
public interface MatchComponent
{
    void mainActivityInject(MainActivity activity);
}

上面的程式碼中之所以注入的是MainActivity ,是因為我們講的是如何在Android中使用,所以我要在MainActivity中注入對A類的依賴。那樣我只要在MainActivity中申明 @Inject A a;,dagger就會幫我注入A的例項物件.

4:客戶端中使用,例如我們的MainActivity 。將要注入的物件使用@Inject標記,使用dagger為我們生成的Component的例項呼叫裡面的注入方法void mainActivityInject(MainActivity activity);。大功告成,瞬間秒懂。

public class MainActivity extends AppCompatActivity
{
    @Inject
    A a;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MatchComponent component= DaggerMatchComponent
                .builder()
                .build();
        component.mainActivityInject(this);

        findViewById(R.id.btn_niu).setOnClickListener(v->{a.xxoo();});
        findViewById(R.id.btn_xue).setOnClickListener(v->{a.appointment();});
    }
}

不知道大家會不會有疑問,我們要注入的是C類物件,而在客戶端注入的是A類物件,那C類物件是怎麼注入的呢?仔細看一下A類的程式碼就明白了,這個是一個遞迴注入的過程。

這裡寫圖片描述
如果我們點選第一個按鈕,系統就會輸出:

 翠花,關燈。。。
 我王二狗的兒子王不二降臨人世了!

如果我們點選第二個按鈕,系統就會輸出:

上官無雪,晚上一起去看看月亮聊聊理想啊?
癩蛤蟆想吃天鵝肉,滾!

什麼原理

dagger2之所以這麼神奇,還是因為註解的力量,強烈推薦去看這篇文章秒懂Java註解,就是因為dagger2在背後默默的為我們生成了很多程式碼,才使得我們使用如此輕鬆。看一下dagger2為我們生產的程式碼。
這裡寫圖片描述

那麼我們如何閱讀這些生成的程式碼呢?如果沒有一個好的方法,就會感覺無從下手,一團亂麻。
首先到我們的MainActivity 中,找到注入的這句話,這是在Component中申明的方法component.mainActivityInject(this);,點選Ctr+Alt+B 就會定位到那個具體實現了Component介面的類DaggerMatchComponent中。

  @Override
  public void mainActivityInject(MainActivity activity) {
    injectMainActivity(activity);
  }

接著看injectMainActivity(activity)方法,看到其呼叫了MainActivity_MembersInjector類的injectA()方法

  private MainActivity injectMainActivity(MainActivity instance) {
    MainActivity_MembersInjector.injectA(instance, getA());
    return instance;
  }

MainActivity_MembersInjector類的injectA()方法很簡單,就是講傳入的a例項物件賦值給了MainActivity 中我們宣告的a變數,現在關鍵就是看這個傳入的a例項物件如何產生的了。

  public static void injectA(MainActivity instance, A a) {
    instance.a = a;
  }

我們看到這個a例項物件是由DaggerMatchComponent類中的getA()方法產生的。

  private A getA() {
    return injectA(A_Factory.newA());
  }
  private A injectA(A instance) {
    A_MembersInjector.injectB(instance, new B());
    A_MembersInjector.injectC(instance, DaggerModule_ProviderCFactory.proxyProviderC(daggerModule));
    return instance;
  }

可以從上面的程式碼看到,a例項物件最終是由A_Factory類的newA()方法產生的

  public static A newA() {
    return new A();
  }

至此,我們已經明白了dagger是如何為我們在MainActivity中的A 屬性注入例項物件的了。

而在注入A的過程中,dagger2還注入了B和C的物件,具體程式碼在上面injectA(A instance)方法裡面,有興趣的同學可以檢視其原始碼,也非常簡單。

  A_MembersInjector.injectB(instance, new B());
  A_MembersInjector.injectC(instance, DaggerModule_ProviderCFactory.proxyProviderC(daggerModule));

結語

這裡只是希望可以讓對dagger2一知半解的同學快速入門,dagger2還有許多高階的特性等待你們去挖掘,祝願編碼愉快,生活愉快。

也希望王二狗可以珍惜眼前人,拋棄令人唾棄的骯髒想法,和牛翠花愉快的培養祖國的下一代。