Dagger2入門詳解
參考文章
http://www.bozhiyue.com/anroid/boke/2016/0719/273761.html
環(huán)境配置
這里以Gradle配置為例子,實(shí)用得是AndroidStudio:
- 打開project 的 build.gradle 岭埠,添加
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
//dagger2
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
- 打開 app module 的 build.gradle盏混,添加
apply plugin: 'com.android.application'
//dagger2
apply plugin: 'com.neenbedankt.android-apt'
//...
dependencies {
//...
//dagger2
compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'
compile 'org.glassfish:javax.annotation:10.0-b28'
}
- 然后sync gradle一下,環(huán)境就配置好了惜论,在實(shí)用dagger2的時(shí)候许赃,會(huì)自動(dòng)生成一些類,所以最好記一下 build project的快捷鍵 ctrl+F9来涨,寫好dagger代碼图焰,然后build一下,就會(huì)自動(dòng)生成 DaggerXXX 開頭的一些類蹦掐。
入門實(shí)例
好了技羔,下面我們來看一個(gè)入門實(shí)例僵闯,實(shí)用Dagger2到底是怎么依賴注入的。
現(xiàn)在又一個(gè)Person類(這里為了簡(jiǎn)單起見)藤滥,然后MainActivity中又一個(gè)成員變量person鳖粟。
Person.java
public class Person {
public Person() {
System.out.println("a person created");
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
Person person;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
person = new Person();
}
}
如果不適用依賴注入,那么我們只能在MainActivity中自己new一個(gè)Person對(duì)象拙绊,然后使用向图。
使用依賴注入:
public class MainActivity extends AppCompatActivity {
@Inject
Person person;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
在Person對(duì)象上添加一個(gè) @Inject
注解,即可自動(dòng)注入對(duì)象标沪。
那么問題來了榄攀,就一個(gè)@Inject
注解,系統(tǒng)就會(huì)自動(dòng)給我創(chuàng)建一個(gè)對(duì)象? 當(dāng)然不是金句,這個(gè)時(shí)候我們需要一個(gè)Person類的提供者檩赢。估計(jì)叫它: MainModule
MainModule.java
@Module
public class MainModule {
@Provides
Person providesPerson() {
System.out.println("a person created from MainModule");
return new Person();
}
}
里面兩個(gè)注解,@Module
和 @Provides
违寞,Module標(biāo)注的對(duì)象贞瞒,你可以把它想象成一個(gè)工廠,可以向外提供一些類的對(duì)象趁曼。那么到底提供什么對(duì)象呢军浆?
@Provides
標(biāo)注的方法就是提供對(duì)象的,這種方法一般會(huì)返回一個(gè)對(duì)象實(shí)例挡闰,例如上面返回一個(gè) Person對(duì)象
那么好了乒融,現(xiàn)在Perso類的提供者也有了,我們是不是可以運(yùn)行起來了摄悯。ctrol+F9
build一下項(xiàng)目簇抵,然后運(yùn)行。發(fā)現(xiàn)沒有任何輸出(如果創(chuàng)建Person對(duì)象射众,會(huì)打印消息)。為什么了晃财?
這個(gè)時(shí)候需要引入第3個(gè)東東叨橱,component容器《鲜ⅲ可以把它想成一個(gè)容器罗洗, module中產(chǎn)出的東西都放在里面,然后將component與我要注入的MainActivity做關(guān)聯(lián)钢猛,MainActivity中需要的person就可以沖 component中去去取出來伙菜。
MainComponent.java
@Component(modules = {MainModule.class})
public interface MainComponent {
void inject(MainActivity mainActivity);
}
看到一個(gè)新注入 @Component 表示這個(gè)接口是一個(gè)容器,并且與 MainModule.class 關(guān)聯(lián)命迈,它生產(chǎn)的東西都在這里贩绕。
void inject(MainActivity mainActivity);
表示我怎么和要注入的類關(guān)聯(lián)火的。這個(gè)比較抽象!這個(gè)時(shí)候我們可以 build 一下項(xiàng)目淑倾。
然后在MainActivity中將component 關(guān)聯(lián)進(jìn)去:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainComponent component = DaggerMainComponent.builder()
.mainModule(new MainModule()).build();
component.inject(this);
}
下面就是將 MainActivity和Module通過Component關(guān)聯(lián)起來的代碼馏鹤,那么這個(gè)時(shí)候系統(tǒng)看到 有一個(gè) @Inject
修飾的Person,就知道在這個(gè) MainComponent中去找娇哆,并且是有 MainModule 的 Provides修飾的方法提供的湃累。
MainComponent component = DaggerMainComponent.builder()
.mainModule(new MainModule()).build();
component.inject(this);
然后 build 項(xiàng)目,運(yùn)行項(xiàng)目碍讨,發(fā)現(xiàn)打又瘟Α:
person from module
a person created
說明確實(shí)系統(tǒng)創(chuàng)建了對(duì)象,并且注入到MainActivity中勃黍。
細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)宵统,MainComponent, MainModule, MainActivity 都是我們自己創(chuàng)建的,上面還有一個(gè) DaggerMainComponent 是上面鬼溉躲?這就是你 build project 的時(shí)候榜田,dagger自己為你生成的具體的component類(我們自己定義的是MainComponent接口)。感興趣的可以直接跟到代碼里面去看看锻梳。
好了箭券,上面我們把 DI (Dependency Inject) 最基本的流程走了一遍,用到了幾個(gè)注解:
- @Inject
- @Module
- @Component
- @Provides
下面來介紹另外幾個(gè)常用的注解疑枯。
其他注解和情況
如果只有最簡(jiǎn)單的情況辩块,那么上面的幾個(gè)注解已經(jīng)夠了,但是其實(shí)還有很多情形荆永,我們稍微展示幾個(gè)
單例模式 @Singleton(基于Component)
基于Component的單例模式废亭,怎么理解呢?也是是 在這個(gè)Component 對(duì)象中具钥,一個(gè)對(duì)象是單例對(duì)象豆村。如果又新創(chuàng)建了一個(gè)Component,那么兩個(gè)Component中的當(dāng)你對(duì)象是不一樣的骂删。具體的看后面介紹掌动。
上面的MainActivity代碼不變,我們?cè)僭贛ainActivity中添加一個(gè) @Inejct Person person2
宁玫,并打印兩個(gè) person對(duì)象粗恢,結(jié)果如下:
person from module
a person created
person from module
a person created
org.yxm.daggerlearn2.data.Person@64cf4a2
org.yxm.daggerlearn2.data.Person@899b533
發(fā)現(xiàn)person會(huì)被創(chuàng)建兩次,并且兩個(gè)person對(duì)象也不同欧瘪,如果我們希望只有一個(gè) person 和 person2 都指向同一個(gè)Person對(duì)象了眷射? 使用 @Singleton
注解
兩個(gè)地方需要加:
- MainModule.java 的 provides方法上需要添加
@Singleton
注解
@Module
public class MainModule {
private static final String TAG = "MainModule";
@Singleton
@Provides
public Person providesPerson() {
Log.d(TAG, "person from module");
return new Person();
}
}
- MainComponent.java 類上添加
@Singleton
@Component(modules = {MainModule.class})
public interface MainComponent {
void inject(MainActivity mainActivity);
}
再運(yùn)行,發(fā)現(xiàn)只創(chuàng)建了一次,并且兩個(gè)person指向同一個(gè)對(duì)象妖碉。
person from module
a person created
org.yxm.daggerlearn2.data.Person@64cf4a2
org.yxm.daggerlearn2.data.Person@64cf4a2
需要非常注意的是:?jiǎn)卫腔贑omponent的涌庭,所以不僅 Provides 的地方要加 @Singleton
,Component上也需要加嗅绸。并且如果有另外一個(gè)OtherActivity脾猛,并且創(chuàng)建了一個(gè)MainComponent,也注入Person鱼鸠,這個(gè)時(shí)候 MainActivity和OtherActivity中的Person是不構(gòu)成單例的猛拴,因?yàn)樗鼈兊腃omponent是不同的。
帶有參數(shù)的依賴對(duì)象
如果構(gòu)造Person類蚀狰,需要一個(gè)參數(shù)Context愉昆,我們?cè)趺醋⑷肽兀?要知道注入的時(shí)候我們只有一個(gè) @Inject
注解,并不能帶參數(shù)麻蹋。所以我們需要再 MainModule 中提供context跛溉,并且由 providesXXX 函數(shù)自己去構(gòu)造。如:
Person.java
public class Person {
private Context context;
public Person(Context context) {
Log.d(TAG, "a person created with context:"+context);
}
}
修改MainModule.java
@Module
public class MainModule {
private static final String TAG = "MainModule";
private Context context;
public MainModule(Context context) {
this.context = context;
}
@Provides
public Context providesContext() {
return this.context;
}
@Singleton
@Provides
public Person providesPerson(Context context) {
Log.d(TAG, "person from module");
return new Person(context);
}
}
這里需要強(qiáng)調(diào)的是扮授, providesPerson(Context context)
中的 context芳室,不能直接使用 成員變量 this.context
,而是要在本類中提供一個(gè) Context providesContext()
的 @Provides
方法刹勃,這樣在發(fā)現(xiàn)需要 context 的時(shí)候會(huì)調(diào)用 provideContext 來獲取堪侯,這也是為了解耦。
依賴一個(gè)組件
如果組件之間有依賴荔仁,比如 Activity 依賴 Application一樣伍宦,Application中的東西,Activity要直接可以注入乏梁,怎么實(shí)現(xiàn)呢次洼?
例如,現(xiàn)在由 AppModule 提供Context對(duì)象遇骑, ActivityModule 自己無需提供Context對(duì)象卖毁,而只需要依賴于 AppModule,然后獲取Context 對(duì)象即可落萎。
AppModule.java
@Module
public class AppModule {
private Context context;
public AppModule(Context context) {
this.context = context;
}
@Provides
public Context providesContext() {
return context;
}
}
AppComponent.java
@Component(modules = {AppModule.class})
public interface AppComponent {
// 向下層提供Context
Context getContext();
}
ActivityModule.java
@Module
public class ActivityModule {
@Provides
Person providePerson(Context context) {
return new Person(context);
}
}
ActivityComponent.java
@Component(dependencies = {AppComponent.class}, modules = {ActivityModule.class})
public interface ActivityComponent {
void inject(MainActivity mainActivity);
}
通過上面例子势篡,我們需要注意:
- ActivityModule 也需要?jiǎng)?chuàng)建Person時(shí)的Context對(duì)象,但是本類中卻沒有 providesContext() 的方法模暗,因?yàn)樗ㄟ^ ActivityComponent依賴于 AppComponent,所以可以通過 AppComponent中的 providesContext() 方法獲取到Context對(duì)象念祭。
- AppComponent中必須提供
Context getContext();
這樣返回值是 Context 對(duì)象的方法接口兑宇,否則ActivityModule中無法獲取。
使用方法:一定要在 activityComponent中注入 appComponent 這個(gè)它依賴的組件粱坤。我們可以看到隶糕,由于AppComponent沒有直接和 MainActivity發(fā)生關(guān)系瓷产,所以它沒有 void inject(...);
這樣的接口
AppComponent appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
ActivityComponent activityComponent = DaggerActivityComponent.builder()
.appComponent(appComponent)
.activityModule(new ActivityModule())
.build();
activityComponent.inject(this);
自定義標(biāo)記 @Qualifier 和 @Named
如果Person中有兩個(gè)構(gòu)造方法,那么在依賴注入的時(shí)候枚驻,它怎么知道我該調(diào)用哪個(gè)構(gòu)造方法呢濒旦?
修改Person類,兩個(gè)不同的構(gòu)造方法
Person.java
public class Person {
private static final String TAG = "Person";
private Context context;
private String name;
public Person(Context context) {
Log.d(TAG, "a person created with context:" + context);
}
public Person(String name) {
this.name = name;
Log.d(TAG, "a person created with name:" + name);
}
}
有兩種方法可以解決這個(gè)問題:
@Named("...")
@Module
public class MainModule {
private static final String TAG = "MainModule";
private Context context;
public MainModule(Context context) {
this.context = context;
}
@Provides
public Context providesContext() {
return this.context;
}
@Named("context")
@Provides
public Person providesPersonWithContext(Context context) {
return new Person(context);
}
@Named("string")
@Provides
public Person providesPersonWithName() {
return new Person("yxm");
}
}
分別在兩個(gè)提供Person的provides方法上添加 @Named
標(biāo)簽再登,并指定尔邓。
然后在要依賴注入的地方,同樣添加 @Name 標(biāo)注表示要注入時(shí)使用哪一種
@Named("string")
@Inject
Person p1;
@Named("context")
@Inject
Person p2;
@Qualifier自定義標(biāo)簽
使用@Named 會(huì)使用到 字符串 锉矢,如果兩邊都必須寫對(duì)才能成功梯嗽,并且字符串總是不那么優(yōu)雅的,容易出錯(cuò)沽损,所以我們可以自定義標(biāo)簽來解決上面的問題灯节。
PersonWithContext.java
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonWithContext {
}
PersonWithName.java
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonWithName {
}
這樣就自定義了兩個(gè)標(biāo)簽,然后在剛剛所有使用到 @Named 的地方替換成我們自定義的標(biāo)簽即可:
MainModule.java
// ...
@PersonWithContext
@Provides
public Person providesPersonWithContext(Context context) {
return new Person(context);
}
@PersonWithName
@Provides
public Person providesPersonWithName() {
return new Person("yxm");
}
依賴注入的地方:
@PersonWithContext
@Inject
Person p1;
@PersonWithContext
@Inject
Person p2;
輸入:調(diào)用了兩種構(gòu)造方法
a person created with context:org.yxm.daggerlearn2.MainActivity@8018a6e
a person created with name:yxm
懶加載Lazy和強(qiáng)制重新加載Provider
在注入時(shí)分別使用 Lazy 和 Provider 修飾要注入的對(duì)象:
@Inject
Lazy<Person> lazyPerson;
@Inject
Provider<Person> providerPerson;
在使用的地方绵估,用 .get()
方法獲妊捉:
Person p1 = lazyPerson.get();
Person p2 = lazyPerson.get();
Person p3 = providerPerson.get();
Person p4 = providerPerson.get();
打印結(jié)果:
a person created with context:org.yxm.daggerlearn2.MainActivity@8018a6e
a person created with name:yxm
a person created with name:yxm
說明 lazyPerson 多次get 的是同一個(gè)對(duì)象,providerPerson多次get国裳,每次get都會(huì)嘗試創(chuàng)建新的對(duì)象形入。
@Scope 自定義生命周期
通過前面的例子,我們遇到了 @Singleton
這個(gè)標(biāo)簽躏救,它可以保證在同一個(gè)Component中唯笙,一個(gè)對(duì)象是單例對(duì)象。其實(shí)可以跟進(jìn)去看代碼:
Singleton.java
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
就這么多東西盒使。利用單例和組件間依賴的關(guān)系崩掘,是不是我們也可以定義生命周期來滿足我們的需求呢荠瘪?比如 Application戈擒,Activity 這樣的生命周期。下面我們來創(chuàng)建這兩個(gè)生命周期:
Application生命周期
ApplicationScope.java
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}
Activity生命周期
ActivityScope.java
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
除了名字风钻,其他都和 @Singleton 是一樣的英妓。
然后用 ApplicationScope 來修飾 AppModule和AppComponent挽放,ActivityScope 修飾 ActivityModule和ActivityComponent
AppModule.java
@Module
public class AppModule {
private Context context;
public AppModule(Context context) {
this.context = context;
}
@ApplicationScope
@Provides
public Context providesContext() {
return context;
}
}
AppComponent.java
@ApplicationScope
@Component(modules = {AppModule.class})
public interface AppComponent {
// 向下層提供Context
Context getContext();
}
ActivityModule.java
@Module
public class ActivityModule {
@ActivityScope
@PersonWithContext
@Provides
Person providesPersonWithContext(Context context) {
return new Person(context);
}
@ActivityScope
@PersonWithName
@Provides
Person providesPersonWithString() {
return new Person("yxm");
}
}
ActivityComponent.java
@ActivityScope
@Component(dependencies = {AppComponent.class}, modules = {ActivityModule.class})
public interface ActivityComponent {
void inject(MainActivity mainActivity);
}
然后創(chuàng)建自定義Application:
App.java
public class App extends Application {
public static AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
}
在MainActivity中注入:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@PersonWithContext
@Inject
Person p1;
@PersonWithContext
@Inject
Person p2;
@PersonWithName
@Inject
Person p3;
@PersonWithName
@Inject
Person p4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityComponent activityComponent = DaggerActivityComponent.builder()
.appComponent(App.appComponent)
.activityModule(new ActivityModule())
.build();
activityComponent.inject(this);
Log.d(TAG, "" + p1);
Log.d(TAG, "" + p2);
Log.d(TAG, "" + p3);
Log.d(TAG, "" + p4);
}
}
可以看到我們注入了4個(gè)Person對(duì)象,打印結(jié)果:
a person created with context:org.yxm.daggerlearn2.App@103efff
a person created with name:yxm
org.yxm.daggerlearn2.data.Person@711cc
org.yxm.daggerlearn2.data.Person@711cc
org.yxm.daggerlearn2.data.Person@a36ec15
org.yxm.daggerlearn2.data.Person@a36ec15
只創(chuàng)建了兩次蔓纠,說明在ActivityScope生命周期中辑畦,創(chuàng)建了兩種 Person,并且它們保持各自的單例腿倚。
其實(shí)看到這里纯出,再說單例我都覺得不是很準(zhǔn)確了,它其實(shí)就是說的在這個(gè)Component中可以又幾個(gè)這樣的對(duì)象。如果是普通的就可能有很多個(gè)暂筝,如果是Scope修飾的箩言,就只有一個(gè)。
總結(jié)
哎喲我去焕襟,還是寫了很長陨收,這篇還是主要展示了Dagger2中最基本的環(huán)境配置,和常用的標(biāo)簽以及特性,列舉一下:
- @Inject
- @Module
- @Component
- @Singleton
- @Scope
- @Named, @Qualifier
- Lazy, Provider
- modules, dependencys
然后也踩了一些坑鸵赖,很無知的錯(cuò)誤:
- 在Component中我們會(huì)使用:
void inject(實(shí)體類)
务漩,表示這個(gè)Component 可以與實(shí)體類關(guān)聯(lián),然后為它注入卫漫。注意這里的實(shí)體類必須是直接關(guān)聯(lián)的那個(gè)類菲饼,如果你填它的父類,按照多態(tài)你也可以在compoment.inject(...)
的時(shí)候成功列赎,但是卻無法注入 宏悦。糾結(jié)了好一會(huì)兒。 - 一旦使用
compoment.inject(...)
使某個(gè)實(shí)體類和Component發(fā)生了關(guān)系包吝,那么對(duì)應(yīng)Component的 Module 中必須提供@Inject
修飾的所有對(duì)象的 providesXXX 方法饼煞,而且如果有兩種構(gòu)造方法,必須提供兩種 providesXXX 方法哦诗越!又被坑了好一會(huì)兒砖瞧。 - 如果被依賴的 Component 使用了Scope,那么依賴他的 Component 也必須使用Scope才能使用嚷狞。典型例子就是:AppComponent使用了 Scope块促,那么ActivityComponent也必須使用Scope,否則會(huì)編譯出錯(cuò)床未。
下一篇來介紹Android中怎么實(shí)際使用Dagger2來達(dá)到解耦的目的竭翠,敬請(qǐng)期待吧