依賴注入(DI, Dependency Injection)份企,目的是為了解決類之間的耦合称近,方便測試块攒。
減少耦合的一個原則是,一個類A
需要的類B
的對象的實(shí)例化不應(yīng)該在A
中實(shí)現(xiàn)南蹂,而是通過一定的方式從外部傳入B
的實(shí)例,這樣就解決了類A
和B
之間的耦合犬金。
(PS:寫這篇文章的目的不是說自己已經(jīng)掌握了Dagger2,其實(shí)還有很多沒有深刻理解的地方。把自己研究這幾天的成果寫出來晚顷,給想了解Dagger2的同學(xué)做個參考峰伙,同時也作為自己的一個備忘。有錯誤的地方该默,請大家斧正瞳氓。)
舉個例子:
public class ClassA {
public ClassA() {
//do some init work
}
}
public class ClassB {
private ClassA mInstanceA;
public ClassB() {
//ClassA的實(shí)例化在ClassB的構(gòu)造方法中執(zhí)行
instanceA = new ClassA();
}
}
如上,ClassB
依賴于ClassA
栓袖,并且在ClassB
的構(gòu)造方法中完成了ClassA
的實(shí)例化匣摘。這樣看上去似乎沒有什么問題,但是當(dāng)CLassA
的構(gòu)造方法改變裹刮,則必須要修改ClassB
的代碼音榜。這時ClassA
和ClassB
之間有耦合,牽一發(fā)動全身捧弃。
依賴注入可以很好的解決這個問題囊咏。有很多種注入依賴的方法:
1.通過接口注入依賴。
重構(gòu)一下ClassB
的寫法:
public interface IClassB {
void setClassA(ClassA a);
}
public class ClassB implements IClassB {
private ClassA mInstanceA;
@Override
void setClassA(ClassA a) {
mInstanceA = a;
}
}
2.通過setter方法注入依賴塔橡。
public class ClassB {
private ClassA mInstanceA;
public void setInstanceA(ClassA a) {
mInstanceA = a;
}
}
3.通過構(gòu)造方法注入依賴梅割。
public class ClassB {
private ClassA mInstanceA;
public ClassB(ClassA a) {
mInstanceA = a;
}
}
4.通過依賴注入框架注入依賴。
開始講這篇文章的主角--Dagger2葛家。
Dagger2是目前Android上比較流行的一個依賴注入框架户辞,Google出品。之前在項(xiàng)目中@劉青也用到了這個框架癞谒,非常適合與MVP架構(gòu)配合使用底燎。
大家知道在MVP架構(gòu)中,View
持有Presenter
的引用弹砚,Presenter
持有Model
的引用双仍,其實(shí)說白了就是依賴。使用Dagger2可以很方便的注入這些依賴桌吃。
貼出部分代碼:
public class LoginFragment extends BaseFragment implements LoginView {
@Inject
LoginPresenter mLoginPresenter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
injectDependencies();
}
private void injectDependencies() {
DaggerLoginComponent.builder()
.appComponent(ComponentHolder.getAppComponent())
.loginModule(ModuleProvider.getInstance().provideLoginModule(this, getActivity()))
.build()
.inject(this);
}
}
可以看出朱沃,LoginFragment
依賴于LoginPresenter
。在mLoginPresenter
上添加@Inject
注解茅诱,然后在onCreate
方法中做了某種操作后逗物,LoginPresenter
就被注入到LoginFragment
中了,具體是哪種操作瑟俭,我們往下看翎卓。
首先看下Dagger2中常見的幾個注解:
1.@Inject
在字段或構(gòu)造方法前加上這個注解,表明這個字段需要被注入依賴或者標(biāo)注依賴對應(yīng)的構(gòu)造方法摆寄。
2.@Module
被@Module
標(biāo)注的類作為依賴的提供者失暴。
3.@Provide
在方法前加上這個注解坯门,表明這個方法提供依賴,通常用在被@Module
注解的類的方法上逗扒。舉個例子:
@Provides
InjectEntity provideInjectEntity() {
return new InjectEntity();
}
表明這個方法提供InjectEntity
的依賴田盈。
@Component
Component
是一個注入器。@Inject
需要注入缴阎,@Module
提供注入允瞧,Component
就是這兩者之間的橋梁。被@Component
注解的一定是接口蛮拔。
@Scope
@Scope
可以自定義注解述暂,通過自定義注解可以限定注解的作用域。
@Qualifier
當(dāng)類的類型不足以鑒別一個依賴的時候建炫,我們就可以使用這個注解標(biāo)示畦韭。
代碼
拿Google官方的demo來講:
@Module
public class AndroidModule {
private final DemoApplication application;
public AndroidModule(DemoApplication application) {
this.application = application;
}
@Provides
@Singleton
@ForApplication
Context provideApplicationContext() {
return application;
}
@Provides
@Singleton
LocationManager provideLocationManager() {
return (LocationManager) application.getSystemService(LOCATION_SERVICE);
}
}
這是一個Module
,用來提供依賴的肛跌∫张洌看provideApplicationContext
方法,@Provides
注解表明它提供Context
的依賴衍慎。當(dāng)某個Context類型的字段被@Inject
注解時转唉,Dagger2會到Module
中尋找返回值為Context
,并且被@Provides
標(biāo)注的方法稳捆,通過這個方法將依賴注入到需要的類中赠法。
看到這里你可能會有疑問,如果一個Module
中有多個方法被@Provides
標(biāo)注乔夯,并且返回值是Context
類型時砖织,該如何選擇?這時就需要用到@Qualifier
標(biāo)注了末荐,先看下@ForApplication的
具體寫法:
@Qualifier @Retention(RUNTIME)
public @interface ForApplication {
}
在提供依賴的方法以及需要依賴注入的字段前都用@ForApplication
標(biāo)注侧纯,這兩個就構(gòu)成了一一對應(yīng)關(guān)系,如果Module
中還有一個返回值為Context
的方法甲脏,但是沒有用@ForApplication
標(biāo)注眶熬,就可以將其排除。
@ForApplication
只是一個標(biāo)記剃幌,你可以改成別的名字聋涨,只要能區(qū)別開其他有相同返回值的方法即可。
如下是被依賴字段的@Qualifier
寫法:
@Named("ForApplication")
@Provides
Person provideContext(Context context) {
return new Person(context);
}
另一種寫法
其實(shí)還有一種簡單的负乡,不用定義新注解的寫法:
@Named("Context")
@Provides
Person provideApplicationContext() {
return new application;
}
@Named("Context")
@Inject
Context context;
在提供依賴的方法以及需要依賴注入的字段前都加上@Named
標(biāo)注,只要@Name
里面的字符串相同脊凰,則構(gòu)成一一對應(yīng)的關(guān)系抖棘。這種寫法會比較簡便茂腥,但是字符串的標(biāo)記容易導(dǎo)致不匹配,字符串寫錯了是不會報錯的切省。
再看@Singleton
注解最岗。這是Dagger2自帶的一個注解,用于保持在同一個Component
中的對象是單例的朝捆。其實(shí)你去看@Singleton
的源碼般渡,就簡單的幾行:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
事實(shí)上,如果你自己定義一個注解芙盘,把Singleton
名字換掉驯用,效果和@Singleton
是一樣的。
可以下這樣一個結(jié)論:
只要Component上添加的@Scope注解和Module中提供依賴的方法上添加的@Scope注解一樣儒老,就會保持這個方法返回的對象在該Component實(shí)例中是單例的蝴乔。
這也就是很多使用Dagger2的例子中使用的@PerApp
、@PerActivity
的來歷驮樊。
public class DemoApplication extends Application {
@Singleton
@Component(modules = AndroidModule.class)
public interface ApplicationComponent {
void inject(DemoApplication application);
void inject(HomeActivity homeActivity);
void inject(DemoActivity demoActivity);
}
@Inject LocationManager locationManager;
private ApplicationComponent component;
@Override public void onCreate() {
super.onCreate();
component = DaggerDemoApplication_ApplicationComponent.builder()
.androidModule(new AndroidModule(this))
.build();
component().inject(this);
}
public ApplicationComponent component() {
return component;
}
}
在DemoApplication
中有一個接口ApplicationComponent
薇正,用@Component
注解表示這是一個Component
,后面的(modules = AndroidModule.class)
指明了與其相關(guān)聯(lián)的Module
囚衔。
需要注意的是挖腰,Component
必須是一個接口,而且需要引用到目標(biāo)類的實(shí)例练湿,上面代碼中的inject()
方法就是為Component
提供目標(biāo)類的曙聂,之所以這樣是因?yàn)?code>Component需要目標(biāo)類的實(shí)例來尋找目標(biāo)類中被@Inject
標(biāo)記的字段,以便為這些字段到Module
找相應(yīng)的提供依賴的方法鞠鲜。舉個例子:
上述Component中有這個方法
void inject(DemoApplication application);
Component
拿到DemoApplication
的實(shí)例后會在其中找到下面這個字段:
@Inject LocationManager locationManager;
然后宁脊,Component
會到其對應(yīng)的Module
中去尋找用@Provides
標(biāo)記的,并且返回值是LocationManager
的方法贤姆,通過找到的這個方法得到LocationManager
的實(shí)例榆苞。到這里也就完成了依賴的注入。
繼續(xù)看HomeActivity
的寫法:
public class HomeActivity extends DemoActivity {
@Inject LocationManager locationManager;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((DemoApplication) getApplication()).component().inject(this);
Log.d("HomeActivity", locationManager.toString());
}
}
繼承自DemoActivity
不用管霞捡,對理解Dagger沒什么意義坐漏。
可以看到有一個被@Inject
注解的字段locationManager
,表示這個字段需要依賴注入碧信。在onCreate
中調(diào)用了Component
的inject
方法赊琳,傳入目標(biāo)類的引用。流程就像我們上面分析的那樣砰碴,到Component到Module中尋找@Provides方法躏筏,不再贅述。
注意到之前AndroidModule
中提供LocationManager
依賴的方法是加入了Singletion
依賴的呈枉,說明所提供的LocationManager
在Component
中是單例的趁尼。我們可以再加一個LocationManager
驗(yàn)證一下是否真的是單例(提示一下埃碱,這里有個坑,不要被我?guī)нM(jìn)去了)酥泞。
把HomeActivity
代碼改成如下所示:
public class HomeActivity extends DemoActivity {
@Inject LocationManager locationManager;
//新加一個相同類型的字段砚殿,用來驗(yàn)證是否真的是單例的
@Inject LocationManager locationManager2;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((DemoApplication) getApplication()).component().inject(this);
Log.d("HomeActivity", locationManager.toString());
Log.d("HomeActivity", locationManager2.toString());
}
}
運(yùn)行之后看下打印:
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
嗯芝囤,的確是一樣的似炎。那如果將Singletion去掉是不是應(yīng)該就不一樣了呢?去掉試下:
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
還是一樣的悯姊?
額羡藐,只能說Google這個例子選的不好,為什么一定要用LocationManager
這個類來舉例子呢挠轴?翻上去看看provideLocationManager()
這個方法咋寫的:
getSystemService(LOCATION_SERVICE);
Android中的系統(tǒng)級別服務(wù)采用集合形式的單例模式,所以不管你調(diào)多少次传睹,返回的都是同一個實(shí)例。其實(shí)可以在provideLocationManager()
方法中加一個打印岸晦,你會發(fā)現(xiàn)加上@Singleton
注解欧啤,provideLocationManager()
只調(diào)用一次,去掉會調(diào)用兩次启上,這樣就符合我們預(yù)期了邢隧。
組件之間的依賴
看一個例子:
@Module
public class SubModule {
@Provides
public InjectEntity provideEntity(Context context) {
return new InjectEntity();
}
}
很顯然,SubModule
提供一個InjectEntity
的依賴冈在,但是提供依賴的同時provideEntity
方法還依賴一個Context
對象倒慧,如果在SubModule
里有@Provides
方法返回Context
對象,就像下面這樣:
@Module
public class SubModule {
private Context mContext;
public SubModule(Context context) {
mContext = context;
}
@Provides
public Context provideContext() {
return mContext;
}
@Provides
public InjectEntity provideEntity(Context context) {
return new InjectEntity();
}
}
這樣provideEntity
方法會從provideContext
方法中獲得Context
對象的依賴包券。但是問題是如果SubModule
中就是沒有提供Context
依賴的方法怎么辦呢纫谅?這時會到其所依賴的Component
中尋找:
@Component(modules = MainModule.class)
public Interface MainComponent {
void inject(DemoApplication application);
}
@Module
public class MainModule {
private Context mContext;
public MainModule(Context context) {
mContext = context;
}
@Provide
public Context provideContext() {
return mContext;
}
}
@Component(dependencies = MainComponent.class, Module = SubModule.class)
public interface SubComponent {
void inject(HomeActivity activity);
}
上面代碼邏輯很清晰,SubComponent
依賴于MainComponent
溅固,在SubModule
中找不到的依賴會通過MainComponent
到MainModule
中去尋找签孔。
這里有一個需要注意的點(diǎn):
一個沒有作用域(unscoped )的組件不可以依賴有作用域的組件.
組件之間的依賴在Android中一般體現(xiàn)為Activity
或Fragment
作用域的Component
依賴于App作用域的Component
然想,后者為前兩者提供Application
實(shí)例或一些需要在App全局使用的單例對象藕溅。
以上
參考:
google dagger2 官方參考文檔:
https://google.github.io/dagger/
google官方demo地址:
https://github.com/google/dagger/tree/master/examples/android-simple
其他文章: