Dagger2使用詳解
Dagger2 使用詳解
前言
Dagger2 是一款使用在Java和Android上的依賴注入的一個類庫。
配置信息
使用Android Studio 創建一個新的項目,在Project的 build.gradle
文件添加以下內容:
1 2 3 4 5 6 7 |
buildscript { dependencies { classpath 'me.tatarka:gradle-retrolambda:3.2.4' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } |
並在Module下的build.gradle
添加以下內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'me.tatarka.retrolambda' android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { apt 'com.google.dagger:dagger-compiler:2.4' compile 'com.google.dagger:dagger:2.4' provided 'org.glassfish:javax.annotation:10.0-b28' } |
這樣就基本完全了Dagger2的配置環境(順便也配置了支持lambda表達式)。
Dagger2基本使用
我們先簡單地創建一個類:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Poetry { private String mPemo; // 用Inject標記構造函數,表示用它來注入到目標對象中去 public Poetry() { mPemo = "生活就像海洋"; } public String getPemo() { return mPemo; } } |
然後我們在MainActivity中使用這個類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class MainActivity extends AppCompatActivity { //添加@Inject註解,表示這個mPoetry是需要注入的 Poetry mPoetry; private TextView mTextView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { mTextView = (TextView) findViewById(R.id.tv_poetry); mTextView.setText(mPoetry.getPoems()); } } |
但是這樣直接運行是會出錯的,此時這樣子在MainActivity中的mPoetry對象是無法被注入的,因爲MainActivity不知道去哪裏找到它的實例去注入生成,這時我們需要一個連接器Component,讓上面這兩個類產生聯繫:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//用@Component表示這個接口是一個連接器,能用@Component註解的只 //能是interface或者抽象類 public interface MainComponent { /** * 需要用到這個連接器的對象,就是這個對象裏面有需要注入的屬性 * (被標記爲@Inject的屬性) * 這裏inject表示注入的意思,這個方法名可以隨意更改,但建議就 * 用inject即可。 */ void inject(MainActivity activity); } |
先運行一遍,AS會生成一些類,再修改一下MainActivity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class MainActivity extends AppCompatActivity { //添加@Inject註解,表示這個mPoetry是需要注入的 Poetry mPoetry; private TextView mTextView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 使用Dagger2生成的類 生成組件進行構造,並注入 DaggerMainComponent.builder() .build() .inject(this); initView(); } private void initView() { mTextView = (TextView) findViewById(R.id.tv_poetry); mTextView.setText(mPoetry.getPoems()); } } |
運行,如下
上面MainActivity
中的Poetry
實例並不直接由MainActivity類創建,而是由MainActivityComponent
類注入生成實例。以上就是一個簡單的Dagger2示例。
@Module
有時候我們並不能直接在構造函數裏面添加@Inject
註解,或者類中存在多個構造函數時,@Inject
也只能註解其中一個構造函數,不能註解多個構造函數,這裏是會產生歧義性,因爲Dagger2無法確認調用哪一個構造函數來生成例的實例對象。另外一種情況是我們在項目中引用第三方類庫時,也是無法直接在類構造函數中添加@Inject
註解的,所以我們需要用到@Module
註解了。@Module
是用來生產實例來注入對象的,它類似一個工廠,集中創建要注入的類的對象實例。下面我們引用一下Gson庫來看看@Module
是怎麼使用的,創建MainModule
類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* @Module註解表示這個類提供生成一些實例用於注入 */ public class MainModule { /** * @Provides 註解表示這個方法是用來創建某個實例對象的,這裏我們創建返回Gson對象 * 方法名隨便,一般用provideXXX結構 * @return 返回注入對象 */ public Gson provideGson(){ return new Gson(); } } |
添加完這個類後,我們要與之前寫的類產生關聯,不然誰知道你這裏提供了生成Gson實例的方法啊。修改MainCompontent
:
1 2 3 4 5 6 7 8 9 10 11 12 |
//這裏表示Component會從MainModule類中拿那些用@Provides註解的方法來生成需要注入的實例 (modules = MainModule.class)public interface MainComponent { /** * 需要用到這個連接器的對象,就是這個對象裏面有需要注入的屬性 * (被標記爲@Inject的屬性) * 這裏inject表示注入的意思,這個方法名可以隨意更改,但建議就 * 用inject即可。 */ void inject(MainActivity activity); } |
這裏多了一個依賴,依賴MainModule
類中的方法生成Gson實例,我們在MainActivity
裏注入Gson實例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class MainActivity extends AppCompatActivity { //添加@Inject註解,表示這個mPoetry是需要注入的 Poetry mPoetry; Gson mGson; private TextView mTextView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 使用Dagger2生成的類 生成組件進行構造,並注入 DaggerMainComponent.builder() .build() .inject(this); initView(); } private void initView() { mTextView = (TextView) findViewById(R.id.tv_poetry); String json = mGson.toJson(mPoetry); mTextView.setText(json); } } |
運行,結果如下:Component
可以依賴多個Module
對象,以上的構造方法與生成方法都是無參生成實例的,如果我們帶參數應該怎麼做了?我們創建多一個PoetryModule
用於提供Poetry
實例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class PoetryModule { // 這個方法需要一個String參數,在Dagger2注入中,這些參數也是注入形式的,也就是 // 要有其他對方提供參數poems的生成,不然會造成編譯出錯 public Poetry providePoetry(String poems){ return new Poetry(poems); } // 這裏提供了一個生成String的方法,在這個Module裏生成Poetry實例時,會查找到這裏 // 可以爲上面提供String類型的參數 public String providePoems(){ return "只有意志堅強的人,才能到達彼岸"; } } |
修改MainComponent
依賴:
1 2 3 4 5 6 7 8 9 10 11 12 |
//這裏表示Component會從MainModule類中拿那些用@Provides註解的方法來生成需要注入的實例 (modules = {MainModule.class,PoetryModule.class})public interface MainComponent { /** * 需要用到這個連接器的對象,就是這個對象裏面有需要注入的屬性 * (被標記爲@Inject的屬性) * 這裏inject表示注入的意思,這個方法名可以隨意更改,但建議就 * 用inject即可。 */ void inject(MainActivity activity); } |
運行,就可以看到不同的詩詞了:
細心的同學就會發現了,我們提供了兩個可以生成Poetry
實例的方法,一個是在Poetry
類的構造函數時候用@Inject
提供的實例創建方法,一個是在PoetryModule
中的@Privodes
註解的providePoetry方法,而在上面的運行結果中我們發現是調用了PoetryModule
提供的方法,這裏就要說明一下優先級的問題,在上面這種既在構造函數中用@Inject
提供注入來源,也在@Module
中用@Privodes
註解提供注入來源的,Dagger2是先從@Privodes
查找類實例,如果找到了就用@Module
提供的方法來創建類實例,如果沒有就從構造函數裏用@Inject
註解的生成類實例,如果二者都沒有,則報錯,簡而言之,就是@Module
的優先級高於@Inject
。
另外這裏還要說明一點,在providePoetry(String)方法中,String這個參數也是要注入提供的,必須也要有在同一個連接器裏面有提供,其中在構建類實例的時候,會按照以下順序執行:
- 從Module中查找類實例創建方法
- Module中存在創建方法,則看此創建方法有沒有參數
- 如果有參數,這些參數也是由Component提供的,返回步驟1逐一生成參數類實例,最後再生成最終類實例
- 如果無參數,則直接由這個方法生成最終類實例
- Module中沒有創建方法,則從構造函數裏面找那個用@Inject註解的構造函數
- 如果該構造函數有參數,則也是返回到步驟1逐一生成參數類實例,最後調用該構造函數生成類實例
- 如果該構造函數無參數,則直接調用該構造函數生成類實例
以上就是一次注入生成類實例的生成步驟。
@Scope
我們創建多一個Activity,這個Activity也注入了Poetry跟Gson對象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class OtherActivity extends AppCompatActivity { //添加@Inject註解,表示這個mPoetry是需要注入的 Poetry mPoetry; Gson mGson; private TextView mTextView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_other); MainComponent.getInstance() .inject(this); initView(); } private void initView() { mTextView = (TextView) findViewById(R.id.tv_poetry); String json = mGson.toJson(mPoetry); String text = json + ",mPoetry:"+mPoetry; mTextView.setText(text); } } |
我們順便也把MainComponent
改成抽象類的形式,並添加返回MainComponent
單例的方法,對應添加MainActivity跳轉到OtherActivity的方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
(modules = {MainModule.class,PoetryModule.class})public abstract class MainComponent { /** * 需要用到這個連接器的對象,就是這個對象裏面有需要注入的屬性 * (被標記爲@Inject的屬性) * 這裏inject表示注入的意思,這個方法名可以隨意更改,但建議就 * 用inject即可。 */ abstract void inject(MainActivity activity); abstract void inject(OtherActivity activity); private static MainComponent sComponent; public static MainComponent getInstance(){ if (sComponent == null){ sComponent = DaggerMainComponent.builder().build(); } return sComponent; } } public class MainActivity extends AppCompatActivity { //添加@Inject註解,表示這個mPoetry是需要注入的 Poetry mPoetry; Gson mGson; private TextView mTextView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 使用Dagger2生成的類 生成組件進行構造,並注入 MainComponent.getInstance() .inject(this); initView(); } private void initView() { mTextView = (TextView) findViewById(R.id.tv_poetry); String json = mGson.toJson(mPoetry); String text = json + ",mPoetry:"+mPoetry; mTextView.setText(text); findViewById(R.id.open).setOnClickListener(view -> startActivity(new Intent(this,OtherActivity.class))); } } |
運行結果如下:
可以看到,調用同一個MainComponent
實例多次注入的時候每次都重新生成Poetry實例,有時候我們需要只希望生成一個共用實例的時候應該怎麼辦呢,這裏我們就需要用到Dagger2的@Scope屬性了,Scope是作用域的意思,我們先自定義一個@Scope註解:
1 2 3 4 |
(RetentionPolicy.RUNTIME)public PoetryScope { } |
同時在Module與Component加上這個自定義Scope:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
(modules = {MainModule.class,PoetryModule.class})public abstract class MainComponent { /** * 需要用到這個連接器的對象,就是這個對象裏面有需要注入的屬性 * (被標記爲@Inject的屬性) * 這裏inject表示注入的意思,這個方法名可以隨意更改,但建議就 * 用inject即可。 */ abstract void inject(MainActivity activity); abstract void inject(OtherActivity activity); private static MainComponent sComponent; public static MainComponent getInstance(){ if (sComponent == null){ sComponent = DaggerMainComponent.builder().build(); } return sComponent; } } public class PoetryModule { // 這個方法需要一個String參數,在Dagger2注入中,這些參數也是注入形式的,也就是 // 要有其他對方提供參數poems的生成,不然會造成編譯出錯 public Poetry providePoetry(String poems){ return new Poetry(poems); } // 這裏提供了一個生成String的方法,在這個Module裏生成Poetry實例時,會查找到這裏 // 可以爲上面提供String類型的參數 public String providePoems(){ return "只有意志堅強的人,才能到達彼岸"; } } |
重新運行:
這時你會發現這兩個Poetry實例是同一個實例來的,通過實現自定義@Scope註解,標記當前生成對象的使用範圍,標識一個類型的注射器只實例化一次,在同一個作用域內,只會生成一個實例,然後在此作用域內共用一個實例。這樣看起來很像單例模式,我們可以查看@Singleton其實就是@Scope的一個默認實現而已。當然,你得是同一個Component對象來生成,這點我們應該可以理解的吧。
我們可以通過自定義Scope來組織Component的作用域,使得每個Component的作用域清晰明瞭,各施其職。
組織Component
我們在一個項目之中不可能只使用一個Component連接器來注入對象完成注入工作,一般除了一個全局的ApplicationComponent之外,還有一些作用域在Activity/Fragment的Component,Component之間存在依賴關係與從屬關係。如果我們已經創建好了一個全局的ApplicationComponent,然後其它的Component剛好需要ApplicationComponent裏面的一個全局屬性,想要與ApplicationComponent共享同一個實例,這時就需要用到依賴關係了。
依賴方式
一個Component可以依賴一個或多個Component,並拿到被依賴Component暴露出來的實例,Component的dependencies屬性就是確定依賴關係的實現。
這裏的有點像數學裏面的交集方式,被依賴的Component主動暴露對象給二者共享,如我們在ApplicationModule提供了一個全局的Gson對象,我們想要提供給其他Component時,要在ApplicationComponent顯式的提供一個接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class ApplicationModule { /** * @Provides 註解表示這個方法是用來創建某個實例對象的,這裏我們創建返回Gson對象 * 方法名隨便,一般用provideXXX結構 * @return 返回注入對象 */ public Gson provideGson(){ return new Gson(); } } (modules = ApplicationModule.class)public interface ApplicationComponent { Gson getGson();// 暴露Gson對象接口 } |
並在自定義的MainApplication中初始化它,更改MainComponent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public class MainApplication extends Application { private ApplicationComponent mApplicationComponent; private static MainApplication sApplication; public static MainApplication getInstance() { return sApplication; } public void onCreate() { super.onCreate(); sApplication = this; mApplicationComponent = DaggerApplicationComponent.builder().build(); } public ApplicationComponent getApplicationComponent() { return mApplicationComponent; } } //這裏表示Component會從MainModule類中拿那些用@Provides註解的方法來生成需要注入的實例 (dependencies = ApplicationComponent.class, modules = {MainModule.class,PoetryModule.class})public abstract class MainComponent { /** * 需要用到這個連接器的對象,就是這個對象裏面有需要注入的屬性 * (被標記爲@Inject的屬性) * 這裏inject表示注入的意思,這個方法名可以隨意更改,但建議就 * 用inject即可。 */ abstract void inject(MainActivity activity); abstract void inject(OtherActivity activity); private static MainComponent sComponent; public static MainComponent getInstance(){ if (sComponent == null){ sComponent = DaggerMainComponent.builder() .applicationComponent(MainApplication.getInstance() .getApplicationComponent()) .build(); } return sComponent; } } |
這樣就達到了MainComponent依賴ApplicationComponent。並且這裏需要注意的是,MainComponent的作用域不能和ApplicationComponent的作用域一樣,否則會報錯,一般來講,我們應該對每個Component都定義不同的作用域。
包含方式(從屬方式)@SubComponent
如果我們需要父組件全部的提供對象,這時我們可以用包含方式而不是用依賴方式,相比於依賴方式,包含方式不需要父組件顯式顯露對象,就可以拿到父組件全部對象。且SubComponent只需要在父Component接口中聲明就可以了。添加多一個AActivity,AComponent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
(RetentionPolicy.RUNTIME)public AScope { } public class AModule { public Poetry getPoetry(){ return new Poetry("萬物美好"); } } (modules = AModule.class)public interface AComponent { void inject(AActivity activity); } (modules = ApplicationModule.class)public interface ApplicationComponent { Gson getGson();// 暴露Gson對象接口 //AComponent plus(); AComponent plus(AModule module);//添加聲明 } public class MainApplication extends Application { private ApplicationComponent mApplicationComponent; private AComponent mAComponent; private static MainApplication sApplication; public static MainApplication getInstance() { return sApplication; } public void onCreate() { super.onCreate(); sApplication = this; mApplicationComponent = DaggerApplicationComponent.builder().build(); } public ApplicationComponent getApplicationComponent() { return mApplicationComponent; } public AComponent getAComponent() { if (mAComponent == null){ mAComponent = mApplicationComponent.plus(new AModule()); } return mAComponent; } } public class AActivity extends AppCompatActivity { TextView mTextView; Gson mGson; Poetry mPoetry; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_a); MainApplication.getInstance() .getAComponent() .inject(this); mTextView = (TextView) findViewById(R.id.text); String text = mPoetry.getPoems()+",mPoetry:"+mPoetry+(mGson == null ? "Gson沒被注入" : "Gson已經被注入"); mTextView.setText(text); } } |
最後我們在OtherActivity中添加一個按鈕跳轉到AActivity,運行結果如下:
@Qualifier
假如在上面的AActivity裏面我們想要注入兩個不同的Poetry(指peoms不一樣)實例,我們可以在AModule下添加多一個生成Poetry的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class AModule { public Poetry getPoetry(){ return new Poetry("萬物美好"); } public Poetry getOtherPoetry(){ return new Poetry("我在中間"); } } |
但是直接這樣做Dagger2是無法區分調用哪個方法生成Poetry實例的,這個時候就需要自定義@Qualifier限定符來匹配注入方法了,添加一個自定義Qualifier並修AMoudule,AActivity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
(RetentionPolicy.RUNTIME)public PoetryQualifier { String value() default ""; } public class AModule { "A") ( public Poetry getPoetry(){ return new Poetry("萬物美好"); } "B") ( public Poetry getOtherPoetry(){ return new Poetry("我在中間"); } } public class AActivity extends AppCompatActivity { TextView mTextView; Gson mGson; // 匹配Module中同樣註解的方法 "A") ( Poetry mPoetry; // 匹配Module中同樣註解的方法 "B") ( Poetry mPoetryB; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_a); MainApplication.getInstance() .getAComponent() .inject(this); mTextView = (TextView) findViewById(R.id.text); String text = mPoetry.getPoems()+",mPoetryA:"+mPoetry+ mPoetryB.getPoems()+",mPoetryB:"+mPoetryB+ (mGson == null ? "Gson沒被注入" : "Gson已經被注入"); mTextView.setText(text); } } |
重新編譯運行:
而Dagger2已經默認幫我們實現了一個@Named:
1 2 3 4 5 6 7 8 |
(RUNTIME)public Named { /** The name. */ String value() default ""; } |
跟我們自定義的PoetryQualifier其實是一樣的。
後記
這篇是我參考了其他文章之後自己又重新總結一遍的,錯誤之處請幫忙指出,大家一起進步。除了以上常用到的註解之外,Dagger還提供了其他一些註解,如Set,Map類的註解,具體可以參考以下文章。
參考
Dagger2圖文完全教程
Google官方MVP+Dagger2架構詳解【從零開始搭建android框架系列(6)】
Android:dagger2讓你愛不釋手-基礎依賴注入框架篇
Android:dagger2讓你愛不釋手-重點概念講解、融合篇
Android:dagger2讓你愛不釋手-終結篇
Android:Dagger2學習之由淺入深