1. 程式人生 > >Dagger2 入門詳解(一)

Dagger2 入門詳解(一)

Dagger2 系列:

一、Dagger2介紹

1. Dagger2是什麼?

Dagger2在Github主頁上的自我介紹是:“A fast dependency injector for Android and Java“(一個提供給Android和Java使用的快速依賴注射器。)
Dagger2是由谷歌接手開發,最早的版本Dagger1 是由Square公司開發的。

2. Dagger2相較於Dagger1的優勢是什麼?

更好的效能:相較於Dagger1,它使用的預編譯期間生成程式碼來完成依賴注入,而不是用的反射。大家知道反射對手機應用開發影響是比較大的,因為反射是在程式執行時載入類來進行處理所以會比較耗時,而手機硬體資源有限,所以相對來說會對效能產生一定的影響。
容易跟蹤除錯:因為dagger2是使用生成程式碼來實現完整依賴注入,所以完全可以在相關程式碼處下斷點進行執行除錯。

3. 使用依賴注入的最大好處是什麼?

沒錯,就是模組間解耦! 就拿當前Android非常流行的開發模式MVP來說,使用Dagger2可以將MVP中的V 層與P層進一步解耦,這樣便可以提高程式碼的健壯性和可維護性。

如果在 Class A 中,有 Class B 的例項,則稱 Class A 對 Class B 有一個依賴。下面所說的依賴都是指類之間的依賴關係
如果不用Dagger2的情況下我們應該這麼寫:

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

上面例子面臨著一個問題,一旦某一天B的建立方式(如構造引數)發生改變,那麼你不但需要修改A中建立B的程式碼,還要修改其他所有地方建立B的程式碼。
如果我們使用了Dagger2的話,就不需要管這些了,只需要在需要B物件的地方寫下:

public class A {
   @Inject
   B b;
}

當然在使用@Inject註解的時候,需要其它一些程式碼共同配合完成這個任務,下面將一一介紹Dagger2的功能。

二、Dagger2工作原理

我們先簡單的從整體上看一下Dagger2工作原理,在針對每個功能逐一講解。

這裡寫圖片描述

上圖中,

1、提供依賴部分,就是new物件。有兩種方式,使用@Inject註解建構函式或者提供一個Module類

2、@Component 它本質是一個使用@Component註解的介面,這個介面與Module類建立聯絡,並提供inject()函式。

3、在原來需要使用new建立物件的地方,現在實現Component,然後呼叫它的inject()函式。

4、使用@Inject註解變數,就能生成它的物件。

三、Dagger2 中的註解

理解 Dagger2 中的幾個註解的含義和用法,是掌握 Dagger2 的關鍵,下面一個一個的分析。

@Inject

使用@Inject註解一個建構函式。 當請求一個新的例項時,Dagger會獲取所需的引數,並呼叫此建構函式建立例項。

class Thermosiphon implements Pump {
  private final Heater heater;

  @Inject
  Thermosiphon(Heater heater) {
    this.heater = heater;
  }

  ...
}

Dagger可以直接注入欄位。 在這個例子中,它建立Heater例項和Pump例項。

class CoffeeMaker {
  @Inject Heater heater;
  @Inject Pump pump;

  ...
}

如果你的類中有@Inject 註解欄位,但沒有@Inject註解建構函式,Dagger將根據請求注入這些欄位,但不會建立新的例項。 可以使用@Inject註解新增一個無引數建構函式,以指示Dagger也可以建立例項。

@Provides

預設情況下,Dagger通過建立所請求型別的例項來滿足每個依賴關係,如上所述。 當你請求CoffeeMaker物件時,將通過呼叫new CoffeeMaker()建立例項,並將例項賦給CoffeeMaker變數。

但是@Inject不能在下面的地方工作:

  • 介面無法構建。
  • 第三方類不能註釋。
  • 必須配置可配置物件!

對於@Inject不能滿足要求的情況下,使用@ Provide 註解一個函式來滿足依賴。 函式返回的例項,就是依賴關係中需要的例項。

當需要Heater物件時,provideHeater()就會被呼叫

@Provides static Heater provideHeater() {
  return new ElectricHeater();
}

所有@Provides方法必須屬於一個模組。 這裡說的模組是有@Module註解的類。如下所示:

@Module
class DripCoffeeModule {
  @Provides static Heater provideHeater() {
    return new ElectricHeater();
  }

  @Provides static Pump providePump(Thermosiphon pump) {
    return pump;
  }
}

@Module

使用@Module註解的類,用來包含@Provides註解的一些方法

@Module
public class Module{
    //A是第三方類庫中的一個類
    @Provides
    A provideA(){
          return A();
    }
}

@Component

@Module 提供依賴, @Inject註解函式表示提供依賴,@Inject註解變量表示請求依賴,而@Component 就是聯絡提供和請求這兩者的紐帶。

使用起來很簡單,只需要建立Component物件,然後呼叫Component的inject(A a)方法。就可以在類A中使用@Inject註解變數來建立例項。

建立Component物件時,需要建立他依賴的module和Component例項。見下面第2、3行程式碼

mMainComponent = DaggerMainComponent.builder()
                .appComponent(getAppComponent())
                .mainModule(new MainModule())
                .activityModule(new ActivityModule(this)).build();

Component的功能有哪些?或者說為什麼要使用Component

1、可以表示誰來提供依賴
2、可以表示依賴哪些其他的 Component
3、可以表示為誰提供依賴
4、可以表示提供哪些依賴

Component會首先從Module中查詢可以建立例項的函式,若找到就用Module建立類例項,並停止查詢Inject維度。否則才查詢使用@Inject註解的建構函式。所以建立類例項級別Module維度要高於Inject維度。

下面通過幾行程式碼,來對@Component主要的功能進行介紹:

@Component 註解的是一個介面

比如下面這個介面,表示它可以提供一個 MyApplication 型別的依賴

@Singleton  //這行先不去理會,下面會講到
@Component(modules = {AppModule.class},dependencies = {xxxComponent.class})
public interface AppComponent{
  void inject(MyActivity myActivity);
  public MyApplication getApplication();
}

對應上面說的四點:

1、modules = {AppModule.class}, 表示AppModule提供依賴,具體的函式實現是在AppModule裡面,可以有多個。(用 @Module 註解的類)

2、dependencies = {xxxComponent.class},AppComponent中部分物件,需要xxxComponent.class來提供依賴。

3、有引數無返回值的函式,表示這個類提供依賴。比如上面程式碼中呼叫 inject(MyActivity myActivity),把AppComponent注入到MyActivity,那麼在 MyActivity就可以通過@Inject 註解變數來生成例項了。

4、有返回值無引數的函式表示AppComponent 向子Component(繼承AppComponent 的Component)暴露哪些依賴。這裡可能會有疑問,Component 可提供的依賴不就是 Module 裡的那些嗎?確實沒錯,這裡的函式是父Component 對子Component暴露函式,並不一定會暴露 Module 中所有的方法,只有暴露的方法才能在子Component中使用

四、一個很簡單的例子

1、關於Module和Component的使用

建立AppModule

@Module
public class AppModule {

    Context context;

    public AppModule(Context context){
        this.context = context;
    }

    @Provides @Singleton
    public Context provideContext(){
        return context;
    }

    @Provides @Singleton
    public ToastUtil provideToastUtil(){
        return new ToastUtil(context);
    }
}

建立AppComponent

@Singleton
@Component (modules={AppModule.class})// 由AppModule提供依賴
public interface AppComponent {
// 必須定義為介面,Dagger2框架將自動生成Component的實現類,對應的類名是Dagger×××××,這裡對應的實現類是DaggerAppComponent 
    Context getContext();
    ToastUtil getToastUtil();
}

通常約定AppComponent是一個全域性的Component,負責管理整個App的全域性類例項。當然你想怎麼用都行,這只是一個主觀層面的規定。

Context getContext();和ToastUtils getToastUtils();是將Context和ToastUtils暴露給子Component。不明白這句話什麼意思??等會下面會進行解釋

在Application中建立AppComponent例項

注意下面的只是生成mAppComponent例項,並沒有呼叫inject();


public class App extends Application {

    AppComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        mAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();

    }

    public AppComponent getAppComponent(){
        return mAppComponent;
    }
}

2、關於父Component中的方法暴露給子Component部分

現在來解釋一下Context getContext();和ToastUtils getToastUtils();是將Context和ToastUtils暴露給子Component。先看下面這段程式碼

@PerActivity
//
@Component(dependencies = AppComponent.class,modules = {MainModule.class, ActivityModule.class})

public interface MainComponent extends ActivityComponent{
    //對MainActivity進行依賴注入
    void inject(MainActivity mainActivity);
}

下面是一個MainComponent的簡單使用

public class MainActivity extends BaseActivity implements MainFragment.OnFragmentInteractionListener{

    private MainComponent mMainComponent;

    @Inject
    ToastUtil toastUtil;//注意這裡的註解使用的是MainComponent依賴的AppComponent中暴露的ToastUtil getToastUtil();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Dagger2框架將自動生成MainComponent的實現類DaggerMainComponent
        mMainComponent = DaggerMainComponent.builder().appComponent(getAppComponent())
                .mainModule(new MainModule())
                .activityModule(new ActivityModule(this)).build();
        mMainComponent.inject(this);//一定不要忘記呼叫inject();
    }
}

注意一定要呼叫inject()這個方法,我大概解釋一下,如果沒有呼叫inject(),那麼toastUtil就是一個空值。

上面程式碼中的第14行

    mMainComponent = DaggerMainComponent.builder().appComponent(getAppComponent())
                .mainModule(new MainModule())
                .activityModule(new ActivityModule(this)).build();

這句程式碼會生成了 使用@Inject 註解的變數的例項(也就是ToastUtil例項),會在mMainComponent例項中生成一個ToastUtil例項。

這裡使用@Inject 註解的變數有什麼要求呢??是不是隨便一個變數都能這樣去建立??

當然不是,需要MainComponent中的module 有提供這個依賴,或者依賴的AppComponent提供這個依賴(這裡具體指的是 在AppComponent 中暴露出ToastUtil getToastUtil())

這個ToastUtil例項是有了 ,但是要和MainActivity例項中的toastUtil例項建立聯絡就需要下面這行程式碼

mMainComponent.inject(this);

這句程式碼的意思是,將this例項與mMainComponent例項中的物件關聯起來,這樣this例項中的toastUtil就不是空值了。

五、Dagger2在android studio中的配置

在module下的build.gradle中新增以下依賴

apply plugin: 'com.neenbedankt.android-apt'

...

dependencies {
    apt "com.google.dagger:dagger-compiler:2.2"
    provided 'org.glassfish:javax.annotation:10.0-b28'
    compile "com.google.dagger:dagger:2.2"
}

在專案的根 build.gradle 裡新增:
apt是一個Gradle外掛,協助Android Studio 處理Annotation Processors,所以在Gradle中還必須新增以下內容

dependencies {
     classpath 'com.android.tools.build:gradle:2.2.3'
     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

一個簡單的例子

如果看了上面的講解還是不知所云,結合這個demo再去看看文章,上面用到的程式碼大多都在這個例子中。也可以去看原作者demo,不過需要修改一些配置。

關注我的公眾號,輕鬆瞭解和學習更多技術
這裡寫圖片描述