Android 依赖注入可以更简单 —— 新版本 Dagger 2 使用教学
今年 3 月 21 號 Dagger 2 在 2.10 版本之后針對 Android 方面做了很大的優(yōu)化,使用方法也隨之有了不少變化。本次改動除了讓 Dagger 2 的使用更加符合控制反轉(zhuǎn)原則,還針對 Android 端做出了專門的優(yōu)化(即所謂 dagger.android) —— 大大簡化了 Android 端 Dagger 2 的使用?,F(xiàn)在,不妨隨著本文一起探尋新版本 Dagger 2 的基本使用方法。
閱讀前提
要注意閱讀本文內(nèi)容前最好對 2.10 版本前的 Dagger 2 比較熟悉,至少要明白它的依賴注入管理機(jī)制,最好是實際項目中使用過,不然閱讀時會比較迷茫。當(dāng)然,一切的 Dagger 2 的說明文章很多,善用搜索引擎就可以找到不錯的教程,這里就不再贅述了。
另外,本文的 demo 項目中用到了 Google Samples 項目 Android Architecture 使用的 MVP 實現(xiàn)方式,這個也最好有所了解。主要是了解它的 todo-mvp-dagger 分支的結(jié)構(gòu)組織方法即可,至少要明白它的功能層次和組織結(jié)構(gòu)。
以前的做法有問題?
想想我們在 2.10 版本之前是怎么在 Android 端使用 Dagger 2 的?是不是類似下面的代碼:
public class FrombulationActivity extends Activity { Frombulator frombulator;public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// DO THIS FIRST. Otherwise frombulator might be null!((SomeApplicationBaseType) getContext().getApplicationContext()).getApplicationComponent().newActivityComponentBuilder().activity(this).build().inject(this);// ... now you can write the exciting code} }復(fù)制代碼代碼例子取自 Google 官方的 Dagger 2 文檔。
emmmmm,好像也沒什么問題啊?
不,其實這段代碼大有問題!
第一點,代碼重復(fù)度過高:我們要在幾乎每一個需要注入依賴項的 Activity 和 Fragment 里面來一套這樣的代碼。這是因為我們在 Android 編程中要用到的 Activity、Fragment 等類是繼承自系統(tǒng)的,同時生命周期都由系統(tǒng)管理,所以使用 Dagger 2 的時候就不得不進(jìn)行手動的處理,也就只有在純粹自己寫的 Java 類中使用 Dagger 2 才感覺更舒服一些。
第二點,違反了控制反轉(zhuǎn)原則:原有的用法使得被注入的目標(biāo)類必須要了解注入管理工具的詳細(xì)信息,才能讓注入工作順利進(jìn)行。即使可以通過接口使得實際代碼中不必書寫太多的實際類名,但這仍然造成了嚴(yán)重的目標(biāo)類和管理類的緊耦合。
其實,我們以前在使用 Dagger 2 的時候為了解決重復(fù)問題也是使用 Live Template 或者設(shè)計一個通用的工具函數(shù);而為了保證注入目標(biāo)類正常工作和注入管理正常進(jìn)行,就必須在設(shè)計業(yè)務(wù)代碼的時候并行設(shè)計注入管理代碼。
幸好,2.10 版本針對 Android 系統(tǒng)做出了很大的優(yōu)化,甚至單獨做了 Android 方面的依賴注入管理庫,提供了針對 Android 的注解和輔助類大大減少了要寫的代碼。
下面我就用一個簡單的 demo 講解一下新版本的 Dagger 2 的基本使用方法,項目地址 請戳這里。注意項目有兩個分支,master 分支用來演示基本使用方法,simplify 分支演示簡化用法。
基本使用方法
實際上,新版本的 Dagger 2 可以有多種使用方式,需要用戶構(gòu)建不同數(shù)量的接口、抽象類,但我們先不將最簡化的使用方式,因為用基本但復(fù)雜的方法更好理解 Dagger 2 的使用邏輯和結(jié)構(gòu)。
要使用基本的功能,只需要在 app 的 build.gradle 文件中加入下列依賴代碼:
Application 注入管理
Application 的注入基本沒有什么變化,更多的是 Dagger 2 官方建議最好使用 Builder 模式構(gòu)建 Component,方便靈活的向 Component 中添加構(gòu)建屬性,比如:
(modules = AppModule.class) public interface AppComponent {void inject(MyApplication application);.Builderinterface Builder {Builder application(Application application);AppComponent build();} }復(fù)制代碼Module 和舊版相比沒什么太大變化:
public class AppModule {Context provideContext(Application application) {return application;} }復(fù)制代碼而一般在調(diào)用數(shù)據(jù)庫或存取 SharedPreferences 文件時,常常用到 Context,一般會提供 Application 而非 Activity 等。
Activity 注入管理
第一,是方便管理多級 Component,要求在最頂層的即 Application 的 Component 中引入 AndroidInjectionModule 或 AndroidSupportInjectionModule,代碼形式如下:
(modules = {AppModule.class, AndroidSupportInjectionModule.class}) public interface AppComponent {// ... }復(fù)制代碼@BindInstance 注解方便在 Builder 中加入設(shè)置項,傳入要求的實例就能設(shè)置 Builder 中的對應(yīng)屬性。
第二,Activity 的 Component(也可以是某個功能模塊的 Component)用 @Subcomponent 注解而非 @Component,同時它還要繼承 AndroidInjector<T>(T 一般為對應(yīng)的 Activity)。當(dāng)然,如果有對應(yīng)的 Module 也不要忘記在注解中用 moduls 添加。比如:
(modules = MainActivityModule.class) public interface MainActivityComponent extends AndroidInjector<MainActivity> {.Builderabstract class Builder extends AndroidInjector.Builder<MainActivity> {} }復(fù)制代碼此處 的 Builder 可繼承 AndroidInjector.Builder<T>,必須對應(yīng) @Subcomponent.Builder 注解的抽象類,方便生成 Builder 代碼。
第三,要在第二步中的 Subcomponent 的父 Module 中用注解標(biāo)記出 Subcomponents 的內(nèi)容。
可以把 AppModule 當(dāng)作父 Module:
也可以另外寫一個 `ActivityBindingModule 當(dāng)作父 Module:
(subcomponents = {MainActivityComponent.class, DummyActivityComponent.class}) public abstract class ActivityBindingModule {// ... }復(fù)制代碼相對來說,subcomponents 寫在哪里其實并不重要,ActivityBindingModule 也不止這個上面一個作用,但個人認(rèn)為寫到這里更方便管理。
第四,綁定 Subcomponent 的 Builder 到 AndroidInjector.Factory<T>,方便 Dagger 2 借助正確的 Builder 類型生成對應(yīng)的 AndroidInjector 代碼,為方便管理最好寫到一個統(tǒng)一的 Module 中:
(subcomponents = {MainActivityComponent.class, DummyActivityComponent.class}) public abstract class ActivityBindingModule {(MainActivity.class)abstract AndroidInjector.Factory<? extends Activity> bindMainActivityInjectorFactory(MainActivityComponent.Builder builder); }復(fù)制代碼第五,把第四不得 Module 信息加入到 Application 的 Component 注解:
(modules = {AppModule.class, ActivityBindingModule.class,AndroidSupportInjectionModule.class} ) public interface AppComponent {// ... }復(fù)制代碼注意:我這里沒有采用官方文檔的寫法,使用 Activity 的 Module 來管理 subcomponents 屬性,以及綁定 Builder。因為本文描述的方案邏輯上更好理解,也能更集中地管理相似的代碼,這也是從 Google Samples 演示項目 Android Architecture 學(xué)到的方法。
Fragment 注入管理
如果需要對 Fragment 設(shè)置單獨的注入管理類,那么可以參考 Activity 的方式,不同的是父類的 Module 信息要放到 Activity 的 Component 注解中,所有管理類信息如下(本例中也用到了簡單的 MVP 結(jié)構(gòu)):
DummyActivity 相關(guān):
(modules = {DummyActivityModule.class, DummyFragmentBindingModule.class}) public interface DummyActivityComponent extends AndroidInjector<DummyActivity> {.Builderabstract class Builder extends AndroidInjector.Builder<DummyActivity> {} } /*************************************/ public class DummyActivityModule {DummyContract.Presenter provideDummyPresenter(DummyPresenter presenter) {return presenter;} }復(fù)制代碼DummyFragment 相關(guān):
(subcomponents = DummyFragmentComponent.class) public abstract class DummyFragmentBindingModule {(DummyFragment.class)abstract AndroidInjector.Factory<? extends Fragment> bindDummyFragment(DummyFragmentComponent.Builder builder); } /*************************************/ (modules = DummyFragmentModule.class) public interface DummyFragmentComponent extends AndroidInjector<DummyFragment> {.Builderabstract class Builder extends AndroidInjector.Builder<DummyFragment> {} } /*************************************/ public class DummyFragmentModule {DummyContract.View provideDummyView(DummyFragment fragment) {return fragment;}// ... }復(fù)制代碼本例子中的 MVP 實現(xiàn)方式,參考了 Google Samples 的 Android Architecture 的 todo-mvp-dagger 分支,即把 Activity 僅僅當(dāng)作 Fragment 的管理容器,Fragment 作為 MVP 中的 View 角色來對待。
完成注入
第一,Applicaiton 要實現(xiàn)接口 HasActivityInjector,用來實現(xiàn)自動管理 Activity 的 Injector,具體實現(xiàn)方法是固定不變的:
public class MyApplication extends Application implements HasActivityInjector {DispatchingAndroidInjector<Activity> mInjector;public void onCreate() {super.onCreate();DaggerAppComponent.builder().application(this).build().inject(this);}public AndroidInjector<Activity> activityInjector() {return mInjector;} }復(fù)制代碼第二,無需管理 Fragment 的 Injector 的 Activity 直接注入自己需要的依賴即可:
public class MainActivity extends BaseActivity {// ...protected void onCreate(Bundle savedInstanceState) {AndroidInjection.inject(this);super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);} }復(fù)制代碼第三,需要管理 Fragment 的 Injector 的 Activity 需要實現(xiàn)接口 HasSupportFragmentInjector,方式類似第一步:
public class DummyActivity extends BaseActivityimplements HasSupportFragmentInjector { DispatchingAndroidInjector<Fragment> mInjector;// ...protected void onCreate(final Bundle savedInstanceState) {AndroidInjection.inject(this);super.onCreate(savedInstanceState);setContentView(R.layout.activity_dummy_layout);// ...}// ...public AndroidInjector<Fragment> supportFragmentInjector() {return mInjector;} }復(fù)制代碼第四,對 Fragment 注入依賴:
public final class DummyFragment extends BaseFragment {// ...public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {AndroidSupportInjection.inject(this);View view = inflater.inflate(R.layout.fragment_dummy_layout, container, false);unbinder = ButterKnife.bind(this, view);return view;}// ... }復(fù)制代碼簡化用法
仔細(xì)觀察上面的例子,我們應(yīng)該能發(fā)現(xiàn)其實新版本的 Dagger 2 并沒有更改原來的設(shè)定:仍然使用 Component 作注入器,使用 Module 作依賴提供者,也仍然使用多層級樹形 Component 來統(tǒng)合管理整個項目的依賴,同時 @Quilifier 和 @Scope 的作用也沒變。僅僅只是把每個 Activity 和 Fragment 的注入代碼精簡了,無需知道注入的詳細(xì)細(xì)節(jié)了。
但是,僅僅如此的化,你一定會很疑惑:這個改法確實是更符合依賴反轉(zhuǎn)原則了,可實在也沒節(jié)省多少代碼啊?別急,基本用法一般只是用來了解原理的,實際使用不會這么干的!
簡化管理類
首先,我們要考慮到底是什么代碼重復(fù)最多,最容易使用工具生成呢?當(dāng)然是 @Subcomponent 標(biāo)注的代碼!因為上面的例子中我們可以看出它們的功能相對較為簡單,只是為了構(gòu)建一個樹形結(jié)構(gòu)方便管理,所以大部分編程情景下這部分的代碼其實沒有什么額外的功能。而 Dagger 2 也貼心的提供了簡化的方案:
只要 @Subcomponent標(biāo)注的 Component 類滿足以下條件,就能簡化:
- 除了對應(yīng)的 Builder 外沒有其他的方法
- Builder 類也不會添加其他的方法去自定義構(gòu)建屬性
在簡化之前,要先在 app 的 build.gradle 文件中添加下述依賴:
annotationProcessor 'com.google.dagger:dagger-android-processor:2.13'復(fù)制代碼然后,就能使用 APT 工具自動生成相應(yīng)的 @Subcomponent 代碼,比如 Activity 的 Component 就無需再寫,而是在 ActivityBindingModule 中寫入如下的代碼:
public abstract class ActivityBindingModule {(modules = MainActivityModule.class)abstract MainActivity bindMainActivity();(modules = {DummyActivityModule.class, DummyFragmentBindingModule.class})abstract DummyActivity bindDummyActivity(); }復(fù)制代碼而 Fragment 的 @Subcomponent 注解也如法炮制:
public abstract class DummyFragmentBindingModule {(modules = DummyFragmentModule.class)abstract DummyFragment bindDummyFragment(); }復(fù)制代碼注意: DummyFragmentBindingModule 的注解信息必須加到同 DummyActivityModule 一樣的地方,即本例中的 bindDummyActivity() 的注解中。
這樣,即使 Activity 大量增加也不用寫大量沒什么變化的 Component 代碼了。當(dāng)然 Module 還是需要的,只不過也可以有部分的簡化。
比如 Applicaiton 的 Module 使用 @Binds 把 Application 實例和 Context 進(jìn)行綁定:
public abstract class AppModule {abstract Context provideContext(Application application); }復(fù)制代碼同理,在 MVP 中如果需要提供 Presenter 接口也可以使用這個辦法,比如:
public abstract class DummyActivityModule {abstract DummyContract.Presenter bindDummyPresenter(DummyPresenter presenter); }復(fù)制代碼只不過這個辦法只能把傳入的實例和返回類型進(jìn)行綁定,其他復(fù)雜的依賴提供方法(比如需要利用傳入?yún)?shù)手動實例化,或進(jìn)行條件判斷)還是不能簡化的。(這不是廢話嗎...)
簡化注入操作
注入操作很明顯可以簡化,畢竟模式完全相同,簡化的方法就是提供了模板父類 DaggerApplication、DaggerAppCompatActivity 和 DaggerFragment,然后讓需要注入操作的類繼承它們即可。
最后,Application 的代碼就如下所示:
而 Activity 和 Fragment 則最好是讓 BaseActivity 和 BaseFragment 去繼承上面提到的兩個模板父類,在一般的代碼中自只需要用 @Inject 標(biāo)出需要注入的元素即可,無需任何額外操作。這樣,在編寫 Activity 和 Fragment 的時候無需考慮依賴注入的細(xì)節(jié),只要按照正常流程編寫代碼,然后不斷檢查、測試代碼,不斷標(biāo)記出需要注入的元素即可。
最后,再次提醒相關(guān)代碼不可能全部演示出來,可以去 Dagger 2 Android Demo 查看具體細(xì)節(jié),尤其簡化部分的代碼重復(fù)內(nèi)容較多文章不作贅述,需要的可以自行查看 simplify 分支。另外,如果項目有 bug 也歡迎直接提出 issue。
參考
- Dagger 2 官方 Android 使用文檔
- New Android Injector with Dagger 2?—?part 1
- New Android Injector with Dagger 2?—?part 2
轉(zhuǎn)載于:https://juejin.im/post/5a20b606f265da430e4ef364
總結(jié)
以上是生活随笔為你收集整理的Android 依赖注入可以更简单 —— 新版本 Dagger 2 使用教学的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。