依賴注入(DI, Dependency Injection),目的是為了解決類之間的耦合,方便測(cè)試。
減少耦合的一個(gè)原則是,一個(gè)類A需要的類B的對(duì)象的實(shí)例化不應(yīng)該在A中實(shí)現(xiàn),而是通過(guò)一定的方式從外部傳入B的實(shí)例,這樣就解決了類A和B之間的耦合。
(PS:寫這篇文章的目的不是說(shuō)自己已經(jīng)掌握了Dagger2,其實(shí)還有很多沒(méi)有深刻理解的地方。把自己研究這幾天的成果寫出來(lái),給想了解Dagger2的同學(xué)做個(gè)參考,同時(shí)也作為自己的一個(gè)備忘。有錯(cuò)誤的地方,請(qǐng)大家斧正。)
舉個(gè)例子:
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í)例化。這樣看上去似乎沒(méi)有什么問(wèn)題,但是當(dāng)CLassA的構(gòu)造方法改變,則必須要修改ClassB的代碼。這時(shí)ClassA和ClassB之間有耦合,牽一發(fā)動(dòng)全身。
依賴注入可以很好的解決這個(gè)問(wèn)題。有很多種注入依賴的方法:
1.通過(guò)接口注入依賴。
重構(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.通過(guò)setter方法注入依賴。
public class ClassB {
private ClassA mInstanceA;
public void setInstanceA(ClassA a) {
mInstanceA = a;
}
}
3.通過(guò)構(gòu)造方法注入依賴。
public class ClassB {
private ClassA mInstanceA;
public ClassB(ClassA a) {
mInstanceA = a;
}
}
4.通過(guò)依賴注入框架注入依賴。
開(kāi)始講這篇文章的主角--Dagger2。
Dagger2是目前Android上比較流行的一個(gè)依賴注入框架,Google出品。之前在項(xiàng)目中@劉青也用到了這個(gè)框架,非常適合與MVP架構(gòu)配合使用。
大家知道在MVP架構(gòu)中,View持有Presenter的引用,Presenter持有Model的引用,其實(shí)說(shuō)白了就是依賴。使用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中常見(jiàn)的幾個(gè)注解:
1.@Inject
在字段或構(gòu)造方法前加上這個(gè)注解,表明這個(gè)字段需要被注入依賴或者標(biāo)注依賴對(duì)應(yīng)的構(gòu)造方法。
2.@Module
被@Module標(biāo)注的類作為依賴的提供者。
3.@Provide
在方法前加上這個(gè)注解,表明這個(gè)方法提供依賴,通常用在被@Module注解的類的方法上。舉個(gè)例子:
@Provides
InjectEntity provideInjectEntity() {
return new InjectEntity();
}
表明這個(gè)方法提供InjectEntity的依賴。
@Component
Component是一個(gè)注入器。@Inject需要注入,@Module提供注入,Component就是這兩者之間的橋梁。被@Component注解的一定是接口。
@Scope
@Scope可以自定義注解,通過(guò)自定義注解可以限定注解的作用域。
@Qualifier
當(dāng)類的類型不足以鑒別一個(gè)依賴的時(shí)候,我們就可以使用這個(gè)注解標(biāo)示。
代碼
拿Google官方的demo來(lái)講:
@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);
}
}
這是一個(gè)Module,用來(lái)提供依賴的???code>provideApplicationContext方法,@Provides注解表明它提供Context的依賴。當(dāng)某個(gè)Context類型的字段被@Inject注解時(shí),Dagger2會(huì)到Module中尋找返回值為Context,并且被@Provides標(biāo)注的方法,通過(guò)這個(gè)方法將依賴注入到需要的類中。
看到這里你可能會(huì)有疑問(wèn),如果一個(gè)Module中有多個(gè)方法被@Provides標(biāo)注,并且返回值是Context類型時(shí),該如何選擇?這時(shí)就需要用到@Qualifier標(biāo)注了,先看下@ForApplication的具體寫法:
@Qualifier @Retention(RUNTIME)
public @interface ForApplication {
}
在提供依賴的方法以及需要依賴注入的字段前都用@ForApplication標(biāo)注,這兩個(gè)就構(gòu)成了一一對(duì)應(yīng)關(guān)系,如果Module中還有一個(gè)返回值為Context的方法,但是沒(méi)有用@ForApplication標(biāo)注,就可以將其排除。
@ForApplication只是一個(gè)標(biāo)記,你可以改成別的名字,只要能區(qū)別開(kāi)其他有相同返回值的方法即可。
如下是被依賴字段的@Qualifier寫法:
@Named("ForApplication")
@Provides
Person provideContext(Context context) {
return new Person(context);
}
另一種寫法
其實(shí)還有一種簡(jiǎn)單的,不用定義新注解的寫法:
@Named("Context")
@Provides
Person provideApplicationContext() {
return new application;
}
@Named("Context")
@Inject
Context context;
在提供依賴的方法以及需要依賴注入的字段前都加上@Named標(biāo)注,只要@Name里面的字符串相同,則構(gòu)成一一對(duì)應(yīng)的關(guān)系。這種寫法會(huì)比較簡(jiǎn)便,但是字符串的標(biāo)記容易導(dǎo)致不匹配,字符串寫錯(cuò)了是不會(huì)報(bào)錯(cuò)的。
再看@Singleton注解。這是Dagger2自帶的一個(gè)注解,用于保持在同一個(gè)Component中的對(duì)象是單例的。其實(shí)你去看@Singleton的源碼,就簡(jiǎn)單的幾行:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
事實(shí)上,如果你自己定義一個(gè)注解,把Singleton名字換掉,效果和@Singleton是一樣的。
可以下這樣一個(gè)結(jié)論:
只要Component上添加的@Scope注解和Module中提供依賴的方法上添加的@Scope注解一樣,就會(huì)保持這個(gè)方法返回的對(duì)象在該Component實(shí)例中是單例的。
這也就是很多使用Dagger2的例子中使用的@PerApp、@PerActivity的來(lái)歷。
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中有一個(gè)接口ApplicationComponent,用@Component注解表示這是一個(gè)Component,后面的(modules = AndroidModule.class)指明了與其相關(guān)聯(lián)的Module。
需要注意的是,Component必須是一個(gè)接口,而且需要引用到目標(biāo)類的實(shí)例,上面代碼中的inject()方法就是為Component提供目標(biāo)類的,之所以這樣是因?yàn)?code>Component需要目標(biāo)類的實(shí)例來(lái)尋找目標(biāo)類中被@Inject標(biāo)記的字段,以便為這些字段到Module找相應(yīng)的提供依賴的方法。舉個(gè)例子:
上述Component中有這個(gè)方法
void inject(DemoApplication application);
Component拿到DemoApplication的實(shí)例后會(huì)在其中找到下面這個(gè)字段:
@Inject LocationManager locationManager;
然后,Component會(huì)到其對(duì)應(yīng)的Module中去尋找用@Provides標(biāo)記的,并且返回值是LocationManager的方法,通過(guò)找到的這個(gè)方法得到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不用管,對(duì)理解Dagger沒(méi)什么意義。
可以看到有一個(gè)被@Inject注解的字段locationManager,表示這個(gè)字段需要依賴注入。在onCreate中調(diào)用了Component的inject方法,傳入目標(biāo)類的引用。流程就像我們上面分析的那樣,到Component到Module中尋找@Provides方法,不再贅述。
注意到之前AndroidModule中提供LocationManager依賴的方法是加入了Singletion依賴的,說(shuō)明所提供的LocationManager在Component中是單例的。我們可以再加一個(gè)LocationManager驗(yàn)證一下是否真的是單例(提示一下,這里有個(gè)坑,不要被我?guī)нM(jìn)去了)。
把HomeActivity代碼改成如下所示:
public class HomeActivity extends DemoActivity {
@Inject LocationManager locationManager;
//新加一個(gè)相同類型的字段,用來(lái)驗(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)行之后看下打?。?/p>
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
還是一樣的?
額,只能說(shuō)Google這個(gè)例子選的不好,為什么一定要用LocationManager這個(gè)類來(lái)舉例子呢?翻上去看看provideLocationManager()這個(gè)方法咋寫的:
getSystemService(LOCATION_SERVICE);
Android中的系統(tǒng)級(jí)別服務(wù)采用集合形式的單例模式,所以不管你調(diào)多少次,返回的都是同一個(gè)實(shí)例。其實(shí)可以在provideLocationManager()方法中加一個(gè)打印,你會(huì)發(fā)現(xiàn)加上@Singleton注解,provideLocationManager()只調(diào)用一次,去掉會(huì)調(diào)用兩次,這樣就符合我們預(yù)期了。
組件之間的依賴
看一個(gè)例子:
@Module
public class SubModule {
@Provides
public InjectEntity provideEntity(Context context) {
return new InjectEntity();
}
}
很顯然,SubModule提供一個(gè)InjectEntity的依賴,但是提供依賴的同時(shí)provideEntity方法還依賴一個(gè)Context對(duì)象,如果在SubModule里有@Provides方法返回Context對(duì)象,就像下面這樣:
@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方法會(huì)從provideContext方法中獲得Context對(duì)象的依賴。但是問(wèn)題是如果SubModule中就是沒(méi)有提供Context依賴的方法怎么辦呢?這時(shí)會(huì)到其所依賴的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中找不到的依賴會(huì)通過(guò)MainComponent到MainModule中去尋找。
這里有一個(gè)需要注意的點(diǎn):
一個(gè)沒(méi)有作用域(unscoped )的組件不可以依賴有作用域的組件.
組件之間的依賴在Android中一般體現(xiàn)為Activity或Fragment作用域的Component依賴于App作用域的Component,后者為前兩者提供Application實(shí)例或一些需要在App全局使用的單例對(duì)象。
以上
參考:
google dagger2 官方參考文檔:
https://google.github.io/dagger/
google官方demo地址:
https://github.com/google/dagger/tree/master/examples/android-simple
其他文章: