Dagger2 知識梳理(1) Dagger2 依賴注入的兩種方式

澤毛發表於2017-12-21

一、資料推薦

最近這幾天一直在看有關Dagger2有關的文章,感覺就是這東西真難用、真難懂,數次想要放棄,還好有網上大神的教程幫助,模模糊糊總算能把基本的幾個概念跑通了。

這裡首先推薦 牛曉偉 的下面一系列文章,其將Dagger2的基本思想講的通俗易懂:

接下來是結合具體例子進行分析的兩篇文章,適合在理解基本思想的前提下對Dagger2有個更直觀的認識:

還有前同事 Johnny ShiehDagger2系列,裡面提到了後來新增的一些用法:

瞭解完以上這些基礎文章,就可以嘗試去啃一下官方的英文文件了,裡面介紹瞭如何在Android中使用Dagger2,但是不得不說,寫的真是晦澀難懂,網上找了很久,也沒有把AndroidInjector說明白的:

雖然網上的例子很多,但是我們花了那麼多時間去看這些文章,本質上還是要用到專案中,這裡肯定要優先推薦Google的官方架構Demotodo-mvp-dagger2,這裡面涉及到了很多新的註解用法。

二、匯入依賴

在匯入依賴的時候,需要考慮當前工程中使用的Gradle外掛的版本。如果當前的外掛版本小於2.2,那麼需要引入 android-apt 外掛,Dagger2 入門,以初學者角度 中就是用的這種方式。

在示例程式碼中,根目錄下的build.gradle檔案配置的Gradle外掛版本為2.3.3

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式
因為大於2.2,所以我只需要在app模組中的build.gradle檔案中,引入以下兩個依賴就好了:

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式

三、依賴注入的兩種方式

下面,我們用一個簡單的例子來演示使用Dagger2完成依賴注入的兩種方式:在進行資料的讀寫時,我們可以通過資料管理者DataRepository來管理資料來源,外部通過呼叫這個資料倉儲提供的方法來讀寫資料,完整的原始碼可以檢視 Dagger2Sample 中第一章。

對於這些資料來源的賦值,就可以採用依賴注入的方式來實現。

  • DataRepository:目標類。
  • DataRepository內部的資料來源:目標依賴類。

3.1 在目標依賴類建構函式上使用 @Inject

我們先看第一種方法,通過“在目標依賴類建構函式上使用@Inject,來完成依賴注入”。

第一步

先建立一個LocalSource表示本地的資料來源,並在它建構函式上加上@Inject註解。這樣Dagger2在嘗試建立一個LocalSource物件賦值給DataRepository中的mLocalSource變數時,就會呼叫這個建構函式。

public class LocalSource {

    @Inject
    public LocalSource() {}

    public String getData() {
        return "使用在建構函式上使用 @Inject 的方式,獲取到了本地資料";
    }
}
複製程式碼

第二步

接下來需要宣告一個用@Component註解的介面或者抽象類,用於注入依賴,這個介面的方法名可以為任意值,但是其形參必須是目標類的具體型別,而返回值只能為void或者目標類的具體型別。

@Component
public interface SourceComponent {
    public void inject(DataRepository dataRepository);
}
複製程式碼

第三步

做好前期這些準備,接下來需要按順序進行以下幾步操作:

  • 在目標類中需要被注入的變數上加上@Inject註解。
  • make一下工程,讓Dagger2根據SourceComponent中宣告的介面建立一個DaggerSourceComponent實現類。
  • 在需要注入的時候呼叫DaggerSourceComponent.create().inject(this)方法,完成依賴注入。

DataRepository檔案如下所示:

public class DataRepository {

    @Inject
    LocalSource mLocalSource;

    public DataRepository() {
        DaggerSourceComponent.create().inject(this);
    }

    public String getData() {
        return mLocalSource.getData();
    }

}
複製程式碼

流程如下圖所示:

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式

3.2 使用 @Module

3.1的實現方式有一個缺點,就是需要修改建構函式,但是這對於一些第三方的物件來說是不可能做到的,此時就需要通過另一種方法來建立物件。

我們通過下面這個例子來演示:在DataRepository中例項化一個遠端資料來源RemoteSource

第一步

RemoteSource的建構函式上不再需要新增@Inject註解:

public class RemoteSource {

    public String getData() {
        return "使用 @Module 的方式,獲取到了網路資料";
    }
}
複製程式碼

第二步

接下來建立一個RemoteSourceModule類,用於提供RemoteSource物件。Dagger2會根據它宣告的方法的返回值型別去識別提供的是哪種型別的物件,這裡有兩點需要注意:

  • Module類需要加上@Module註解
  • 提供RemoteSource的方法需要加上@Provides註解
@Module
public class RemoteSourceModule {

    @Provides
    public RemoteSource provideRemoteSource() {
        return new RemoteSource();
    }
}
複製程式碼

第三步

SourceComponent中,我們需要告訴它哪些Module可以用來建立目標類所依賴的例項,這裡和第一種方式的區別就是需要在@Component後面加上用來建立依賴例項的Module類名:

@Component(modules = {RemoteSourceModule.class})
public interface SourceComponent {
    public void inject(DataRepository dataRepository);
}
複製程式碼

第四步

而在目標類中,RemoteSourceLocalSource一樣,都需要加上@Inject註解:

public class DataRepository {

    @Inject
    LocalSource mLocalSource;

    @Inject
    RemoteSource mRemoteSource;

    public DataRepository() {
        DaggerSourceComponent.create().inject(this);
    }

    public String getData() {
        return mLocalSource.getData();
    }

    public String getNetData() {
        return mRemoteSource.getData();
    }
}
複製程式碼

流程如下圖所示:

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式

3.3 Demo 演示

下面,我們用一個簡單的程式來驗證DataRepository中的mLocalSource/mRemoteSource是否注入成功:

public class RepositoryActivity extends AppCompatActivity {

    private static final String TAG = RepositoryActivity.class.getSimpleName();
    private Button mBtnGetData;
    private Button mBtnGetNetData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_repository);
        mBtnGetData = (Button) findViewById(R.id.bt_get_data);
        mBtnGetData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DataRepository repository = new DataRepository();
                String data = repository.getData();
                Toast.makeText(RepositoryActivity.this, data, Toast.LENGTH_SHORT).show();
            }
        });
        mBtnGetNetData = (Button) findViewById(R.id.bt_get_net_data);
        mBtnGetNetData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DataRepository repository = new DataRepository();
                String data = repository.getNetData();
                Toast.makeText(RepositoryActivity.this, data, Toast.LENGTH_SHORT).show();
            }
        });
    }
}
複製程式碼

執行結果為:

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式

3.4 依賴注入的過程

下面,我們來看一下依賴注入的內部實現,整個依賴注入的入口函式為DaggerSourceComponent,在上面的例子中,它的原始碼為:

public final class DaggerSourceComponent implements SourceComponent {

  private Provider<RemoteSource> provideRemoteSourceProvider;
  private MembersInjector<DataRepository> dataRepositoryMembersInjector;

  private DaggerSourceComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static SourceComponent create() {
    return new Builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.provideRemoteSourceProvider =
        RemoteSourceModule_ProvideRemoteSourceFactory.create(builder.remoteSourceModule);

    this.dataRepositoryMembersInjector =
        DataRepository_MembersInjector.create(
            LocalSource_Factory.create(), provideRemoteSourceProvider);
  }

  @Override
  public void inject(DataRepository dataRepository) {
    dataRepositoryMembersInjector.injectMembers(dataRepository);
  }

  public static final class Builder {
    private RemoteSourceModule remoteSourceModule;

    private Builder() {}

    public SourceComponent build() {
      if (remoteSourceModule == null) {
        this.remoteSourceModule = new RemoteSourceModule();
      }
      return new DaggerSourceComponent(this);
    }

    public Builder remoteSourceModule(RemoteSourceModule remoteSourceModule) {
      this.remoteSourceModule = Preconditions.checkNotNull(remoteSourceModule);
      return this;
    }
  }
}
複製程式碼

當我們呼叫靜態的create()方法後會返回一個DaggerSourceComponent例項,它是前面宣告的SourceComponent的實現類。其內部最關鍵的成員變數是DataRepository_MembersInjector類,它是依賴注入的實際執行者,其內部包含了所有需要注入的例項的Provider,這些Provider是在構造時傳入的:

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式

當我們呼叫DaggerSourceComponentinject方法時,就會去呼叫DataRepository_MembersInjector類的injectMembers方法,它通過上面這兩個Provider所提供的get()方法對目標類中被@Inject註解的成員變數進行賦值:

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式

3.5 依賴查詢的過程

對於以上兩種依賴注入方法,其先後順序為:3.2 > 3.1,即Component先會在和它關聯的Module中尋找,這些關聯的Module包括:

  • 自己宣告的Module
  • 依賴的Component所關聯的Module
  • 通過@SubComponent繼承的Component關聯的Module

它會在以上三個維度中尋找Module是否提供了這個類的建立方法(也就是方法的返回值型別為這個類)

  • 如果有,那麼就通過該方法建立,例如例子中的RemoteModule
  • 如果沒有,那麼再通過該類帶有@Inject註解標註的構造方法來建立,例如例子中的LocalSource

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式

對於每個目標依賴類,如果在它的建立過程中依賴於某個引數,那麼就需要先例項化這個引數。這就類似於二叉樹遍歷的過程,在這一遍歷過程中,如果出現了某個型別不能按照以上兩種方式例項化的時候,那麼會在編譯時丟擲異常。

Dagger2 知識梳理(1)   Dagger2 依賴注入的兩種方式


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章