一、資料推薦
最近這幾天一直在看有關Dagger2
有關的文章,感覺就是這東西真難用、真難懂,數次想要放棄,還好有網上大神的教程幫助,模模糊糊總算能把基本的幾個概念跑通了。
這裡首先推薦 牛曉偉 的下面一系列文章,其將Dagger2
的基本思想講的通俗易懂:
接下來是結合具體例子進行分析的兩篇文章,適合在理解基本思想的前提下對Dagger2
有個更直觀的認識:
還有前同事 Johnny Shieh 的Dagger2
系列,裡面提到了後來新增的一些用法:
- Dagger 2 完全解析(一),Dagger 2 的基本使用與原理
- Dagger 2 完全解析(二),進階使用 Lazy、Qualifier、Scope 等
- Dagger 2 完全解析(三),Component 的組織關係與 SubComponent
- Dagger 2 完全解析(四),Android 中使用 Dagger 2
- Dagger 2 完全解析(五),Kotlin 中使用 Dagger 2
瞭解完以上這些基礎文章,就可以嘗試去啃一下官方的英文文件了,裡面介紹瞭如何在Android
中使用Dagger2
,但是不得不說,寫的真是晦澀難懂,網上找了很久,也沒有把AndroidInjector
說明白的:
雖然網上的例子很多,但是我們花了那麼多時間去看這些文章,本質上還是要用到專案中,這裡肯定要優先推薦Google
的官方架構Demo
:todo-mvp-dagger2,這裡面涉及到了很多新的註解用法。
二、匯入依賴
在匯入依賴的時候,需要考慮當前工程中使用的Gradle
外掛的版本。如果當前的外掛版本小於2.2
,那麼需要引入 android-apt 外掛,Dagger2 入門,以初學者角度 中就是用的這種方式。
在示例程式碼中,根目錄下的build.gradle
檔案配置的Gradle
外掛版本為2.3.3
:
2.2
,所以我只需要在app
模組中的build.gradle
檔案中,引入以下兩個依賴就好了:
三、依賴注入的兩種方式
下面,我們用一個簡單的例子來演示使用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();
}
}
複製程式碼
流程如下圖所示:
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);
}
複製程式碼
第四步
而在目標類中,RemoteSource
和LocalSource
一樣,都需要加上@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();
}
}
複製程式碼
流程如下圖所示:
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();
}
});
}
}
複製程式碼
執行結果為:
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
是在構造時傳入的:
當我們呼叫DaggerSourceComponent
的inject
方法時,就會去呼叫DataRepository_MembersInjector
類的injectMembers
方法,它通過上面這兩個Provider
所提供的get()
方法對目標類中被@Inject
註解的成員變數進行賦值:
3.5 依賴查詢的過程
對於以上兩種依賴注入方法,其先後順序為:3.2
> 3.1
,即Component
先會在和它關聯的Module
中尋找,這些關聯的Module
包括:
- 自己宣告的
Module
- 依賴的
Component
所關聯的Module
- 通過
@SubComponent
繼承的Component
關聯的Module
它會在以上三個維度中尋找Module
是否提供了這個類的建立方法(也就是方法的返回值型別為這個類)
- 如果有,那麼就通過該方法建立,例如例子中的
RemoteModule
。 - 如果沒有,那麼再通過該類帶有
@Inject
註解標註的構造方法來建立,例如例子中的LocalSource
。
對於每個目標依賴類,如果在它的建立過程中依賴於某個引數,那麼就需要先例項化這個引數。這就類似於二叉樹遍歷的過程,在這一遍歷過程中,如果出現了某個型別不能按照以上兩種方式例項化的時候,那麼會在編譯時丟擲異常。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/