Dagger2使用攻略-基礎部分
在這篇文章中,我會介紹 什麼是依賴注入,Dagger2是什麼,解決什麼問題以及基礎註解的使用
依賴注入
什麼是 依賴。
舉個例子
有一個 A 類 它裡面定了一個 B 型別的 屬性 b; 這裡 A 就依賴了 B;
public class A{ public A(){ b = new B(); b.print(); } private B b; }
這就意味著 A 離開 B 不能單獨執行,也就是說 A 在哪裡工作,B就會跟到哪裡,A 無法離開 B 被複用。
這種情況下 A 就是 依賴者,B就是依賴。依賴者依賴於它的依賴。
兩個相互使用的類稱為耦合;耦合有強有弱。耦合總是有方向性的。可能 A 依賴 B,但 B 不一定依賴 A。
依賴型別
- 類 / 介面 依賴
- 屬性 / 方法 依賴
- 間接 / 直接 依賴
硬編碼依賴的不好
在依賴者內部構建或者由依賴者尋找依賴這種就稱為 硬編碼依賴
- 降低複用性
- 不好測試
- 強耦合
- 增加維護成本
關於 什麼是依賴,更詳細的硬編碼依賴的缺點這部分,更詳細的可以參考這篇文章,我就是從篇文章學習來的。
ofollow,noindex">https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-introduction-be6580cb3edb
什麼是依賴注入
依賴注入:一個物件提供另一個物件的依賴的技術;
依賴是個能被使用的物件(一個服務);注入是將依賴傳遞給要使用它的物件(客戶端 / 依賴者)。
服務作為客戶端的一部分。將服務傳遞給客戶端而不是客戶端構建或者尋找服務,這是模式(依賴注入)的基本要求。
換句話說:
依賴作為依賴者的一部分。將依賴傳遞給依賴者而不是由依賴者構建或者尋找依賴,這是依賴注入的基本要求。
也就是說 依賴從來原來的由依賴者構建,改為現在由外部注入,也可以稱為 控制反轉。
這樣的好處是很明顯的,提高可測試性,解偶,降低維護成本等等。
更詳細的解釋 可以看一下這篇文章,解釋的超級棒,如果你看過權力的遊戲,就更棒了。
https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-di-part-i-f5cc4e5ad878
Dagger2 就是 Android 平臺的一個依賴注入框架,它現在由 Google 維護開發。
Dagger2 是編譯時框架,會在編譯時根據你的註解配置生成需要的程式碼。
下面是我對 Dagger2 中的常用註解的理解。理解了這些註解的意思和作用,基本就學會了 Dagger2 的基本用法了。
常用註解
@Inject
這個註解有兩個作用:
- 修飾需要注入的屬性,Dagger2 會自動注入
- 修飾被注入的類的構造方法上;Dagger2 會在需要的時候通過這個註解找到建構函式自動構造物件注入
public class MainActivity extends AppCompatActivity { @Inject DBManager dbManager; } public class DBManager { @Inject public DBManager(){} }
@Component
這個註解的作用 是連線提供依賴和注入依賴的。相當與一個注射器的角色,將依賴注入到需要的地方。
剛剛通過上面的@Inject
註解 了提供依賴的構造方法 和 需要注入的屬性,而這樣還是不夠的,需要使用@Comnponent
連線起來。
建立一個介面,並定義一個方法,定義要往哪裡注入;在編譯時期 Dagger2 就會自動生成這個介面的實現類 並以 Dagger 開頭。
還可以定義 向外提供例項的方法;Dagger2都會在編譯時期生成相應的程式碼。
下面是 示例
@Component() public interface MainComponent { void inject(MainActivity mainActivity); DBManager getDBManager(); } // 在需要被注入的類中注入 例如: public class MainActivity extends AppCompatActivity { @Inject DBManager dbManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 注入 DaggerMainComponent.create().inject(this); } }
@Component
有兩個屬性modules
anddependencies
;
-
modules
的作用是引用Module
的,下面@Module
會繼續說 -
dependencies
的作用是 引用其他Component
使用的,相當於 把其他的Component
當作元件一樣引用過來;
@SubComponent
顧名思義 就是 Comnponent 的兒子,它也表示一個注射器的角色,不過它可以繼承 Component的全部 屬性。
Dagger2 不會生成 Dagger開頭的 DaggerSubComponent 這種類,所以,SubComponent 需要在 Component 註冊和維護。這樣的也好統一管理維護,Dagger2 會在生成 Component的時候自動實現生成在內定義的方法。
舉個例子 我的 ApplicationComponent 是個全域性單例的,有 NetModule, APPModule,等等很多全域性性依賴,如果我的 Activity 的注射器 使用@SubComnponent
,那麼就可以使用Application的全部依賴。
@ActivityScoped @Subcomponent(modules = MainModule.class) public interface MainComponent { void inject(MainActivity mainActivity); } @APPScoped @Component(modules = {APPModule.class, APIModule.class} ) public interface APPComponent { MainComponent plus(MainModule module); SecondComponent plus(SecondModule module); } //注入 DaggerAPPComponent.builder() .aPPModule(new APPModule(getApplication())) .build() .plus(new SecondModule()) .inject(this);
當然還有另外一種方法不用@SubComponent
,使用Component
並使用denpendencies
引用上ApplicationComponent
這樣就相當於將ApplicationComponent
組合進來。
@Module && @Provides
@Module
這個註解用來標註提供依賴的工廠。對的,工廠,我是這麼理解的。
@Provides
這個註解用在提供定義提供依賴的方法上,表示向外提供依賴。方法的返回型別就是提供的依賴型別。
前面提到的@Inject
可以在註解在建構函式以用來提供依賴;而在@Inject
不能滿足需要的時候這個就派上用場了。
例如 我注入一個 字串,數字或一個 第三方依賴的物件 例如 Retrofit ,@Inject
已經滿足不了啦。
這個時候可以建立一個類 專門用來提供這些依賴,並使用@Module
註解,然後在Component
的屬性modules
引用上就可以使用了。
// 需要注入的 Activity public class ThirdActivity extends AppCompatActivity { @Inject String name; @Inject int age; @Inject OkHttpClient client; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); DaggerThirdComponent.create().inject(this); Log.e(ThirdActivity.class.getSimpleName(), "onCreate: name-"+name+";age-"+age+";client-"+client); } } // 提供依賴的 工廠 @Module public class ThirdModule { @Provides public String provideName(){ return "skymxc"; } @Provides public int provideAge(){ return 24; } @Provides public OkHttpClient provideOkHttpClient(){ OkHttpClient client = new OkHttpClient.Builder().build(); return client; } } // 連線 依賴和注入方 ,在這裡引用 依賴提供方。 @Component(modules = ThirdModule.class) public interface ThirdComponent { void inject(ThirdActivity activity); }
@Named
在依賴迷失時給出方向。
解釋一下依賴迷失 :
依舊是上面那個例子,現在 都是根據返回值型別來注入的,現在都是不同的型別所以還沒有出現迷失的情況;
現在我如果要加上 地址 屬性;如下
// activity內 @Inject String name; @Inject int age; @Inject OkHttpClient client; @Inject String address; // module 中 @Provides public String provideName(){ return "skymxc"; } @Provides public int provideAge(){ return 24; } public String provideAddress(){ return "北京"; }
這個時候 在 module 中 有兩個返回 String 型別的 方法,Dagger2 這個時候就不知道注入哪一個了,所以就會出現依賴迷失 的情況;
錯誤: [Dagger/DuplicateBindings] java.lang.String is bound multiple times: @Provides String com.skymxc.example.dagger2.di.module.ThirdModule.provideAddress() @Provides String com.skymxc.example.dagger2.di.module.ThirdModule.provideName()
簡單的解決方法就是在 屬性和提供依賴上 加上@Named
註解
@Named("name") @Provides public String provideName(){ return "skymxc"; } @Provides @Named("address") public String provideAddress(){ return "北京"; } // 在 屬性上也加上 @Named("name") @Inject String name; @Named("address") @Inject String address;
這樣就可以解決了 依賴迷失。
@Qualifier
@Named
的元註解,解決依賴迷失的大 Boss;看一下@Named
的原始碼,@Named
就是被@Qualifier
註解的。
@Qualifier @Documented @Retention(RUNTIME) public @interface Named { /** The name. */ String value() default ""; }
如果怕通過@Named
寫字串的方式容易出錯就可以通過@Qualifier
自定義註解來實現。
下面舉個例子,再加一個 身高屬性。定義兩個註解來區分@Age
and@Height
.
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface Height { } @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface Age { } //在 module 和 屬性上使用 @Age @Provides public int provideAge(){ return 24; } @Provides @Height public int provideHeight(){ return 175; } @Age @Inject int age; @Height @Inject int height;
@Singleton
配合@Component
實現範圍內單例
@Singleton
必須和@Component
配合才能實現單例,而且只能保證在@Component
範圍內單例,如果要實現全域性單例,就必須要保證@Component
的例項在全域性範圍內只有一個,類似 Application 。
舉個例子,我要DBManager
在全域性單例,需要以下幾個步驟
@Singleton
// 1.DBManager 標註 @Singleton @Singleton public class DBManager { @Inject public DBManager(){} } // 2. @Singleton @Component(modules = {APPModule.class, APIModule.class}) public interface APPComponent { MainComponent plus(MainModule module); SecondComponent plus(SecondModule module); //可有可無 為了測試 DBManager getDBManager(); } //3. 在 Application 中獲取 例項,並保證唯一例項 public class MApplication extends Application { private APPComponent appComponent; @Override public void onCreate() { super.onCreate(); appComponent = DaggerAPPComponent.builder() .aPPModule(new APPModule(this)) .build(); } public APPComponent getAppComponent() { return appComponent; } } // 測試,在 MainActivity 注入兩個。 public class MainActivity extends AppCompatActivity { @Inject DBManager dbManager; @Inject DBManager dbManager1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //使用 Application 獲取 AppComponent ((MApplication)getApplication()).getAppComponent() .plus(new MainModule()) .inject(this); Log.e(MainActivity.class.getSimpleName(), "onCreate: appdb-->"+((MApplication)getApplication()).getAppComponent().getDBManager().hashCode()); //是否是全域性範圍內單例 if (dbManager==dbManager1) { Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode()); }else{ Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode()); Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode()); } } } // 在 SecondActivity 注入兩個看看是否和 Main 中的是一個例項 public class SecondActivity extends AppCompatActivity { @Inject DBManager dbManager; @Inject DBManager dbManager1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); ((MApplication)getApplication()).getAppComponent() .plus(new SecondModule()) .inject(this); if (dbManager==dbManager1) { Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode()); }else{ Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode()); Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode()); } } }
測試結果必須是全域性唯一單例,看一下 log
E/MainActivity: onCreate: appdb-->192114699 onCreate: dbmanager-sintleton->192114699 E/SecondActivity: onCreate: dbmanager-singleton->192114699
@Singleton
的作用域 始終是跟隨所在的Component
的例項的,如果超出它的範圍就無法保證單例。
就拿上個例子舉例,如果每次 在 Activity 注入的時候 不從 Application 獲取例項而是每次都是使用 DaggerAppComponent 建立一個新的 例項 ,那麼就無法保證兩個 Activity 內的 DBManager 都是一個例項了,因為每個 Activity 都是獲取新的 AppComponent 的例項,它的作用範圍只能在單個例項內。
下面我實現一個 只在 Activity 範圍實現單例的 例子,就是把上面的程式碼改改,在Activity注入的時候 建立新的 Component 例項。
public class SecondActivity extends AppCompatActivity { @Inject DBManager dbManager; @Inject DBManager dbManager1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); //((MApplication)getApplication()).getAppComponent() //.plus(new SecondModule()) //.inject(this); // 獲取新例項 DaggerAPPComponent.builder().aPPModule(new APPModule(getApplication())).build().plus(new MainModule()).inject(this); if (dbManager==dbManager1) { Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode()); }else{ Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode()); Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode()); } } } public class MainActivity extends AppCompatActivity { @Inject DBManager dbManager; @Inject DBManager dbManager1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 獲取新例項 DaggerAPPComponent.builder().aPPModule(new APPModule(getApplication())).build().plus(new MainModule()).inject(this); if (dbManager==dbManager1) { Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode()); }else{ Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode()); Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode()); } } } // log 09-23 00:02:52.937 E/DBHelper: DBHelper: 09-23 00:02:52.937 E/MainActivity: onCreate: dbmanager-sintleton->115289709 09-23 00:02:57.097 E/DBHelper: DBHelper: 09-23 00:02:57.097 E/SecondActivity: onCreate: dbmanager-singleton->64826129
總結 : Dagger2 實現單例要@Singleton
和@Component
||@SubComponent
配合使用,只能實現範圍內(例項內)單例,所以範圍要控制好。只要範圍控制好,隨意 Activity 或者 Application 範圍。
@Scope
作用域 上面說到的@Singleton
就是它的預設實現,也是唯一一個預設實現。
看一下@Singleton
的原始碼
/** * Identifies a type that the injector only instantiates once. Not inherited. * * @see javax.inject.Scope @Scope */ @Scope @Documented @Retention(RUNTIME) public @interface Singleton {}
@Singleton
能夠實現範圍內單例 主要是@Scope
在起作用。預設實現叫Singleton
也是為了更好的理解。
我們可以根據自己的情況,自定義我們自己的依賴作用域,就像我們上面說的 跟隨 Application 生命週期的,跟隨 Activity 生命週期的,或者 User 生命週期的等等。
舉個例子 我們定義倆個AppScoped, ActivityScoped. 分別讓我們的依賴實現 全域性單例和Activity內單例
/** * APP全域性單例 * 此註解使用的 Component 要全域性範圍內唯一 ,不然無法實現全域性單例 */ @Scope @Retention(RetentionPolicy.RUNTIME) public @interface APPScoped { } /** * activity 內單例 * 使用 此註解的Component 生命週期要跟隨 Activity 的生命週期。 */ @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScoped { }
- 建立一個類 SingletonObj 讓其在 Activity範圍內 單例,讓 DBManager 全域性單例
@ActivityScoped public class SingletonObj { @Inject public SingletonObj(){} } /** * */ @APPScoped public class DBManager { @Inject DBHelper helper; @Inject public DBManager(){} }
- 定義 Component ,注意 AppScoped , ActivityScoped 的位置
@APPScoped @Component(modules = { APIModule.class,APPModule.class}) public interface APPComponent { MainComponent plus(MainModule module); SecondComponent plus(SecondModule module); DBManager getDBManager(); }
@ActivityScoped @Subcomponent(modules = MainModule.class) public interface MainComponent { void inject(MainActivity mainActivity); }
@ActivityScoped @Subcomponent(modules = SecondModule.class) public interface SecondComponent { void inject(SecondActivity activity); }
- 獲取 Component 並開始注入
在 Application 獲取 AppComponent 的例項 ,並保持唯一。
public class MApplication extends Application { private APPComponent appComponent; @Override public void onCreate() { super.onCreate(); appComponent = DaggerAPPComponent.builder() .aPPModule(new APPModule(this)) .build(); } public APPComponent getAppComponent() { return appComponent; } }
在 MainActivity 獲取到 MainComponent 的例項 並注入
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ @Inject DBManager dbManager; @Inject DBManager dbManager1; @Inject SingletonObj mainSingleton; @Inject SingletonObj mainSingleton1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.bt_to_second).setOnClickListener(this); findViewById(R.id.bt_to_third).setOnClickListener(this); ((MApplication)getApplication()).getAppComponent() .plus(new MainModule()) .inject(this); Log.e(MainActivity.class.getSimpleName(), "onCreate: appdb-->"+((MApplication)getApplication()).getAppComponent().getDBManager().hashCode()); //檢視 是否和 second的一致,是否是全域性範圍內單例 if (dbManager==dbManager1) { Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode()); }else{ Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode()); Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode()); } //主要看 這個 和 second的是否一致,是否是activity範圍內單例。 if (mainSingleton==mainSingleton1){ Log.e(MainActivity.class.getSimpleName(), "onCreate: main-singleton->"+mainSingleton.hashCode()); }else{ Log.e(MainActivity.class.getSimpleName(), "onCreate: main:"+mainSingleton.hashCode()); Log.e(MainActivity.class.getSimpleName(), "onCreate: main1:"+mainSingleton1.hashCode()); } } }
在 SecondActivity 獲取到 SecondComponent 的例項 並注入 ,這裡就可以看出來 是否是 範圍內單例。
public class SecondActivity extends AppCompatActivity { @Inject DBManager dbManager; @Inject DBManager dbManager1; @Inject SingletonObj mainSingleton; @Inject SingletonObj mainSingleton1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); ((MApplication)getApplication()).getAppComponent() .plus(new SecondModule()) .inject(this); if (dbManager==dbManager1) { Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode()); }else{ Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode()); Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode()); } if (mainSingleton==mainSingleton1){ Log.e(SecondActivity.class.getSimpleName(), "onCreate: main-singleton>"+mainSingleton.hashCode()); }else{ Log.e(SecondActivity.class.getSimpleName(), "onCreate: main:"+mainSingleton.hashCode()); Log.e(SecondActivity.class.getSimpleName(), "onCreate: main1:"+mainSingleton1.hashCode()); } } }
log 可以看出 範圍內單例
E/MainActivity: onCreate: appdb-->229426894 onCreate: dbmanager-sintleton->229426894 onCreate: main-singleton->142055919 E/SecondActivity: onCreate: dbmanager-singleton->229426894 onCreate: main-singleton>241744847
總結 :我們可以通過@Scope
隨意自定義我們自己的作用域,當然不是說我們定義了 ActivityScoped 他就能保證 Activity內單例了,要配合 Component 範圍並用對位置。
這些Demo 的程式碼 我放在了Github
基礎部分就先介紹這些吧,接下來我會繼續 Dagger2-Android 的分享。
參考資料
- https://google.github.io/dagger/
- https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-introduction-be6580cb3edb
- https://proandroiddev.com/how-to-dagger-2-with-android-part-1-18b5b941453f
- https://blog.csdn.net/briblue/article/details/75578459
- https://juejin.im/entry/593cee56ac502e006b3dc9c2
- https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-dagger-2-part-i-f2de5564ab25