系列文章:
Dagger2學習筆記(一)
Dagger2學習筆記(二)
依賴注入是一種十分好的技巧,它能解偶高層次模塊和低層次模塊当叭,使得高層模塊不用將底層模塊硬編碼到內(nèi)部盖灸。
所有依賴的底層模塊都由外部注入,實際是一種面向接口編程醉箕。高層模塊不依賴底層模塊的實現(xiàn)細節(jié)徙垫,可以方便的做到替換底層模塊。
這種技術(shù)在編寫跨平臺程序的時候可以很容易的替換調(diào)依賴系統(tǒng)的底層模塊己英,并且在做單元測試的時候也可以很容易的使用stub對象注入宿主類中從而方便宿主類的測試代碼的編寫逗抑。
使用Dagger2實現(xiàn)依賴注入
如果不使用DI框架,我們也可以在構(gòu)造方法里傳入依賴類或著用setter方法來將依賴類注入宿主類荧关。但是這樣的話就會需要我們在業(yè)務邏輯中處理依賴類的生成和注入褂傀,其實這些依賴的注入代碼和業(yè)務都沒有什么關(guān)系,僅僅是一些初始化的操作而已同波,如果可以將這些與業(yè)務邏輯無關(guān)的代碼都獨立出去,這樣的話我們的代碼邏輯就會更加的簡潔和清晰未檩。Dagger2就是一個十分強大的DI框架冤狡,它可以幫助我們輕松的在業(yè)務邏輯之外實現(xiàn)依賴注入。
下面我將用一個小Demo來介紹一下Dagger2的用法悲雳。這個小Demo的功能是通過github帳號搜索用戶頭像和用戶名合瓢,同時列出該用戶的follower
Dagger2的引入
Dagger2沒有使用反射,它是通過編譯時生成代碼來實現(xiàn)依賴注入的晴楔。所以需要引入apt:
//build.gradle(project)
...
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
...
//build.gradle(app)
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
...
之后再引入javax.annotation和dagger2:
//build.gradle(app)
...
dependencies {
...
compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'
compile 'org.glassfish:javax.annotation:10.0-b28'
...
}
Dagger2的兩個重要組件
Dagger2有兩個十分重要的組件:Module和Component滥崩。
-
Module
Module是依賴的提供者,Dagger2框架通過Module的Provides方法獲取被依賴類的實例。
-
Component
Component是一個注入接口短条,Dagger2框架通過Component將依賴注入到高層類中才菠。
用一個形象的比喻來說明就是Module是裝有被依賴類的針筒,Component是針頭可都。Dagger2通過選擇針筒和針頭的不同組合可以將不同的被依賴實例注入到高層模塊中蚓耽。
實現(xiàn)搜索頁面
@Inject注解
我們的搜索頁面很簡單,只有一個輸入框和一個搜索安按鈕签杈,它的作用是輸入要搜索的用戶的賬號鼎兽。我們使用MVP模式去實現(xiàn)它,因為它不需要model層铣除,所以只有View和Presenter:
public interface SearchView {
...
}
public class SearchPresenter{
...
@Inject
SearchView mSearchView;
@Inject
Context mContext;
@Inject
public SearchPresenter() {
Log.d(TAG, "SearchPresenter()");
}
...
}
public class SearchActivity extends Activity implements SearchView {
...
@Inject
SearchPresenter mSearchPresenter;
...
}
我們通過@Inject注解告訴Dagger2哪些成員變量是需要被注入的尚粘,這里需要注意的是被@Inject標注的成員變量不可以是private的长已,因為Dagger2沒有用到反射,而是通過生成代碼去完成注入的潘明,所以一旦你將成員變量聲明成private的,那Dagger2就不能訪問到它厚宰,從而無法無法完成注入了遂填。@Inject還有另外一個作用就是告訴Dagger2用哪個構(gòu)造函數(shù)去創(chuàng)建實例,如這里Dagger2就會用SearchPresenter()去創(chuàng)建SearchPresenter的實例撵幽,這個構(gòu)造函數(shù)的作用在接下來就會被講到礁击。
Module
然后再讓我們來看看SearchPresenterModule:
@Module
public class SearchPresenterModule {
private SearchActivity mSearchActivity;
public SearchPresenterModule(SearchActivity view) {
mSearchActivity = view;
}
@Provides
SearchView provideSearchView() {
return mSearchActivity;
}
@Provides
Context provideContext() {
return mSearchActivity;
}
}
注入SearchPresenter所需要的SearchView和Context就是從這里提供的
Module類首先需要使用@Module注解標注哆窿,讓Dagger2知道這是一個Module,然后內(nèi)部的使用@Provides注解標注的方法就是用來獲取被依賴類的實例的方法,例如provideSearchView就可以用來提供SearchView
一般我習慣@Provide方法加上provide前綴强衡,但是這個也不是必須码荔,可以沒有這個前綴。
Component
接著看看Component:
@Component(modules = {SearchPresenterModule.class})
public interface SearchComponent {
void inject(SearchActivity activity);
void inject(SearchPresenter presenter);
}
Component是一個被@Component注解標注的接口越败,Dagger2會自動生成實現(xiàn)這個接口的類誉己,去完成注入的功能。我們需要用modules去告訴Component從哪個Module中獲取被依賴類的實例噪猾。這里Dagger2就會自動生成實現(xiàn)了SearchComponent接口的DaggerSearchComponent類,它有兩個方法袱蜡,分別用來向SearchActivity和SearchPresenter注入依賴坪蚁。
向SearchPresenter注入的SearchView和Context都是SearchPresenterModule提供的這個很容易理解,但是向SearchActivity注入的SearchPresenter又是從哪里來的呢?還記得我們用@Inject標注了SearchPresenter的一個構(gòu)造函數(shù)了嗎贱田?Dagger2會使用我們標注的構(gòu)造函數(shù)創(chuàng)建出一個SearchPresenter來給SearchActivity注入使用嘴脾。
調(diào)用注入方法實現(xiàn)注入
在SearchActivity的onCreate方法中將依賴注入到SearchActivity和SearchPresenter中:
SearchComponent component = DaggerSearchComponent.builder()
.searchPresenterModule(new SearchPresenterModule(this))
.build();
component.inject(this);
component.inject(mSearchPresenter);
它實際是通過查找SearchActivity和SearchPresenter中帶有@Inject注解的成員變量知道哪個變量需要被注入,然后通過SearchPresenterModule的provide方法和SearchPresenter被標注的構(gòu)造方法獲取到被依賴類的實例去實現(xiàn)注入的耗拓。
這里有一點需要注意的是調(diào)用順序奏司,inject(SearchActivity activity)要在inject(SearchPresenter presenter)前面調(diào)用,因為需要先將SearchActivity.this的mSearchPresenter注入竿刁,才能向mSearchPresenter中再注入SearchActivity
指定構(gòu)造函數(shù)
我們在前面講到過@Inject可以指定構(gòu)造函數(shù)搪缨,其實它還有另一重意義,就是存在多個構(gòu)造函數(shù)的時候選擇其中一種。
我們現(xiàn)在添加另外一種SearchPresenter構(gòu)造函數(shù),然后中添加打印:
public class SearchPresenter{
...
@Inject
SearchView mSearchView;
@Inject
Context mContext;
@Inject
public SearchPresenter() {
Log.d(TAG, "SearchPresenter()");
}
public SearchPresenter(Context context) {
Log.d(TAG, "SearchPresenter(Context context)");
mContext = context;
}
...
}
讓我們看看運行的時候到底調(diào)的是哪個構(gòu)造函數(shù)吧:
D/SearchPresenter(27333): SearchPresenter()
如果我們把SearchPresenter類修改一下呢?
public class SearchPresenter{
...
@Inject
SearchView mSearchView;
// @Inject 注釋掉
Context mContext;
// @Inject 注釋掉
public SearchPresenter() {
Log.d(TAG, "SearchPresenter()");
}
@Inject // 添加@Inject
public SearchPresenter(Context context) {
Log.d(TAG, "SearchPresenter(Context context)");
mContext = context;
}
...
}
現(xiàn)在可以看到打印:
D/SearchPresenter(27693): SearchPresenter(Context context)
從打印來看,@Inject的確是可以選擇構(gòu)造函數(shù)的煮盼。但還有個細節(jié)不知道大家有沒有注意到,我們?nèi)サ袅薽Context的@Inject,改由構(gòu)造函數(shù)傳入。這個傳入構(gòu)造函數(shù)的Context又是怎么來的呢香到?
答案在SearchPresenterModule里:
@Module
public class SearchPresenterModule {
private SearchActivity mSearchActivity;
public SearchPresenterModule(SearchActivity view) {
mSearchActivity = view;
}
@Provides
SearchView provideSearchView() {
return mSearchActivity;
}
// 是它,是它,就是它
@Provides
Context provideContext() {
return mSearchActivity;
}
}
沒錯SearchPresenterModule.provideContext()這個方法還能創(chuàng)建Context出來給SearchPresenter的構(gòu)造函數(shù)使用悠就!
Demo地址
可以在這里查看完整代碼,剩余部分的代碼會在下一篇文章里介紹梗脾。