##1.前言 依稀記得兩年之前就偷偷學習過dagger2的使用,當然那個時候也是雲裡霧裡,對照著部落格確實實現了物件的注入,但是後來基本上沒有再深入的學習過,雖然偶爾也會瀏覽到dagger2的文章,但是實際專案中並沒有使用,一直擱置著,最近突然腦子發熱,想學習了,剛好學到了這裡,趁著腦子熱,索性就記錄下,待以後需要使用的翻看便知。
##2.什麼是dagger2 dagger2是一款非常有名的依賴注入框架,那麼問題來了,什麼是依賴注入,很簡單,就是字面意思,當你需要建立物件的時候,依賴於其他的程式進行注入,而不是自己主動去建立。
平時我們用到的set方法其實也是一種依賴注入的方式,再比如有參建構函式傳遞過來的物件同樣屬於注入的一種。
優點:
- 避免無用的體力勞動(重複多次new物件)
- 更好的管理類的例項,避免耦合 #####引入dagger2:
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
implementation 'com.google.dagger:dagger-android:2.11'
implementation 'com.google.dagger:dagger-android-support:2.11'
// if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.11'
複製程式碼
##3.最簡單的物件注入(無參)
dagger2的使用依賴於幾個比較主要的註解,下面跟隨我一切來實現一個簡單的物件注入,我們想要在MainActivity建立一個School物件:
1.首先建立School類
public class School {
@Inject
public School() {
}
}
複製程式碼
可以看到我們在School的建構函式上加上了@Inject註解,此處的作用就是 : #####標識School類可以被注入,相對於給注入的物件指定一個建立的來源,不然程式無法知曉如果建立school物件。
2.school類有了,也標明瞭它可以被注入(使用@Inject註解),下面我們就要找個東西來建立這個例項,@Component註解就是這個作用,它相當於一個注入器,負責生產類的例項,起到一個橋樑的作用,一端是生產,一端是注入。
@Component
public interface MainComponent {
void inject(MainActivity activity);
}
複製程式碼
3.Android Studio中 build---> make project,或者單獨編譯下module也可以,編譯完成後將會自動生成dagger2注入所需的程式碼,程式碼生成位置在:
此時我們開始把school物件注入到MainActivity中,程式碼如下:
public class MainActivity extends AppCompatActivity {
@Inject
School school;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//inject方法負責連線所建立的例項和mainActivity,此時例項被賦值給school變數
DaggerMainComponent.create().inject(this);
Log.e("daggerLog","school :【"+school+"】");
}
}
複製程式碼
####此時執行程式,就會出現下面的列印結果:
我們發現,MainActivity並沒有new School()的操作,僅僅通過@Inject註解,並且進行inject連線之後,物件就“自動”產生了。
有些槓精可能要說了,這TM實在逗我,建立個物件一行程式碼搞定了,你給我弄這麼多花裡胡哨的不嫌麻煩嗎?我只能說你說的沒毛病,單個物件不頻繁的建立或許並不需要這麼麻煩,但是為了降低程式碼的耦合性,或者說從架構方面考慮,可能這真的很有必要。
##4.有參的物件注入
上面我們只是簡單定義了一個無參的School物件,那麼有參的物件應該如何注入呢,下面我們改造下School類。
public class School {
private String name;
private String address;
@Inject
public School(String name, String address) {
this.name = name;
this.address = address;
}
}
複製程式碼
嗯,也沒幹啥,就增加了兩個引數,我們來編譯一下:
完犢子,報錯了,看錯誤提示裡面出現了@Provides註解,那麼對於有參的建構函式,該如何進行注入呢,dagger2給我們提供了兩個註解,@Module和@Provides, @Module標識我是用來提供注入例項的,@Provides用來表示具體提供例項的方法,看下面的這段程式碼:
@Module
public class MainModules {
@Provides
public School provideSchool(){
return new School("南京高階中學","江蘇南京");
}
}
複製程式碼
在class名字上方加上@Module註解,在具體提供例項的地方新增上@Provides註解,此時School就具備了被注入的能力,但前面我們說到連線例項和被注入端使用的是@Component註解,那麼如何為@Component指定使用哪個Module注入呢?
@Component(modules = MainModules.class)
public interface MainComponent {
void inject(MainActivity activity);
}
複製程式碼
####很簡單,通過@Component中的modules關鍵字來指定提供例項的module,如果有多個module可以使用{}括起來,形如下面這樣的形式:
@Component(modules = {MainModules.class,OtherModules.class})
複製程式碼
此時我們再次編譯專案,並且修改MainActivity程式碼如下:
public class MainActivity extends AppCompatActivity {
@Inject
School school;
@Inject
School school2;
private Button btnJump;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.create().inject(this);
Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");
Log.e("daggerLog","school add >【"+school+"】");
Log.e("daggerLog","school2 add >【"+school2+"】");
}
}
複製程式碼
執行後,結果列印如下:
此時School物件已經注入完畢,可以正常使用了,此處大家可能發現了我們注入了兩個School物件,但如果我們想保持School單例的話,該如何操作呢? 當然dagger2不會讓我們為這些所擔憂,它提供了@Singleton註解用於處理單例的情況;
####注意:Singleton是區域性單例,和生命週期繫結,注入activity單例範圍就是當前activity之內,注入application就是全域性單例。 下面我們來驗證一下,修改程式碼,在Module和Component中分別加入@Singleton註解,缺一不可,此時程式碼如下:
@Module
public class MainModules {
@Singleton
@Provides
public School provideSchool(){
return new School("南京高階中學","江蘇南京");
}
}
@Singleton
@Component(modules = MainModules.class)
public interface MainComponent {
void inject(MainActivity activity);
}
複製程式碼
執行後,結果列印如下:
可以看到此時在MainActivity中確實實現了單例,那麼我們再次修改下程式碼,新增SecordActivity,並把School物件注入到SecordActivity中,並在MainActivity中新增跳轉按鈕,點選後跳轉到SecordActivity:
@Singleton
@Component(modules = MainModules.class)
public interface MainComponent {
void inject(MainActivity activity);
void inject(SecordActivity activity);
}
public class SecordActivity extends AppCompatActivity {
@Inject
School school;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_secord);
DaggerMainComponent.create().inject(this);
Log.e("daggerLog","secord school add >【"+school+"】");
}
}
複製程式碼
編譯執行後,點選跳轉後列印結果如下:
####就像我們前面說的那樣,@Singleton是區域性單例,此時的兩個Activity中的School物件已經是不同的物件了。我們看下@Singleton的原始碼:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
複製程式碼
發現裡面有個@Scope註解,@Scope其實才是單例的關鍵,@Scope是用來管理生命週期的,而@Singleton是dagger2通過@Scope提供的一種預設實現。
##5.全域性單例的實現 下面我們看一下全域性單例該如何實現,首先建立MyApplicaiton:
public class MyApplicaiton extends Application {
private AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder().build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
void inject(MainActivity activity);
void inject(SecordActivity activity);
}
@Module
public class AppModule {
@Singleton
@Provides
public School provideSingleSchool(){
return new School("全域性單例的School","中國");
}
}
public class MainActivity extends AppCompatActivity {
@Inject
School school;
@Inject
School school2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MyApplicaiton)getApplication()).getAppComponent().inject(this);
Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");
Log.e("daggerLog","school add >【"+school+"】");
Log.e("daggerLog","school2 add >【"+school2+"】");
}
}
public class SecordActivity extends AppCompatActivity {
@Inject
School school;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_secord);
((MyApplicaiton)getApplication()).getAppComponent().inject(this);
Log.e("daggerLog","secord school add >【"+school+"】");
}
}
複製程式碼
然後執行,跳轉到SecordActivity,日誌列印如下:
#####通過日誌可以發現,所有的School物件都是同一物件,實現了全域性的單例!##6.如何傳遞context.
有些時候我們在通過@Provides提供物件的時候需要context引數,形如下面的形式:
public class ShowUtils {
private Context context;
@Inject
public ShowUtils(Context context) {
this.context = context;
}
}
複製程式碼
這個時候如果我們正常通過@Provides的方式提供的話,dagger是無法確定此處的context是哪種型別的,是Application的?還是Activity的?此時編譯就會報錯,那麼我們該如何指定Context具體是何種型別呢?
#####此時又出現了一個新的註解@Qualifier,@Qualifier的作用是當dagger無法確定型別的時候,或者說當我們@module裡有多個provides提供相同型別的返回值(dagger提供物件是根據返回值匹配的)的時候,對型別進行限定,下面舉個例子來體驗一下,首先自定義一個限定註解:
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ForActivityContext {
}
複製程式碼
然後我們修改下上面例子中的MainModules:
@Module
public class MainModules {
private Context context;
public MainModules(Context context) {
this.context = context;
}
@Singleton
@Provides
public School provideSchool(){
return new School("南京高階中學","江蘇南京");
}
@Singleton
@Provides
public ShowUtils provideShowUtils(){
return new ShowUtils(context);
}
//注意這裡,此處限定我們使用的context是activity的context
@ForActivityContext
@Provides
public Context provideContext(){
return context;
}
}
複製程式碼
當然我們還需要在需要此物件的Activity上新增此註解:
@ForActivityContext
public class MainActivity extends AppCompatActivity {
@Inject
School school;
@Inject
School school2;
@Inject
ShowUtils showUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder().mainModules(new MainModules(this)).build()
.inject(this);
Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");
Log.e("daggerLog","school add >【"+school+"】");
Log.e("daggerLog","school2 add >【"+school2+"】");
Log.e("daggerLog","showUtils add >【"+showUtils+"】");
}
}
複製程式碼
此時執行結果如下,可以看到showUtils物件成功注入到了MainActivity中:
####再看另外一種情況: 當我們需要在一個@module裡提供同一物件的不同例項的時候,該如何指定具體需要由哪個@Provides來提供呢? 先看下面一段程式碼:
@Singleton
@Provides
public School provideSchool(){
return new School("南京高階中學","江蘇南京");
}
@Singleton
@Provides
public School provideSchoolOther(){
return new School("國外學校","外國");
}
複製程式碼
其他程式碼不修改,直接編譯會報下面的錯誤:
不要慌,@Qualifier來幫你解決這個問題,首先使用@Qualifier自定義一個註解:@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface SchoolType {
int type() default 0;
}
複製程式碼
然後在@provides中新增此註解:
@SchoolType
@Singleton
@Provides
public School provideSchool(){
return new School("南京高階中學","江蘇南京");
}
@SchoolType(type = 1)
@Singleton
@Provides
public School provideSchoolOther(){
return new School("國外學校","外國");
}
複製程式碼
在MainActivity中@Inject處指定需要使用那種type的物件來進行注入:
@ForActivityContext
public class MainActivity extends AppCompatActivity {
@SchoolType
@Inject
School school;
@SchoolType(type = 1)
@Inject
School school2;
@Inject
Student student;
@Inject
ShowUtils showUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder().mainModules(new MainModules(this)).build()
.inject(this);
Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");
Log.e("daggerLog","school add >【"+school+"】");
Log.e("daggerLog","school2 add >【"+school2+"】");
Log.e("daggerLog","school2 name:【"+school2.getName()+"】 address:【"+school2.getAddress()+"】");
Log.e("daggerLog","showUtils add >【"+showUtils+"】");
}
}
複製程式碼
執行結果如下:
可以看到school和school2物件都成功的注入到了MainActivity中,由於我們指定了@SchoolType並且使用了不同的type,所以建立了兩個不同的物件。 #####那麼是不是我們需要限定的時候就需要自定義註解呢,當然不是,dagger2為我們提供了一個註解@Named,我們來看下@Named的原始碼:@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
複製程式碼
是不是瞬間豁然開朗,這不就是我們剛剛定義的@SchoolType一樣嘛,只是名字不同罷了,當然把上面的@SchoolType直接替換成@Named也是一樣的,不信大家可以試試!
####說了這麼多,對於dagger基本使用大家是不是有點頭緒了,本篇我們主要講解了基本註解@Inject,@Component,@Module,@Provides,@Qualifier等的使用,當然dagger2還是很多更高階的用法,比如dependencies依賴,又比如subcomponent註解我們還沒有講到,這些內容準備放在下篇博文中進行講解,敬請期待!
本文原始碼地址: github.com/hanxiaofeng… --- > daggeruse目錄