今年 3 月 21 號 Dagger 2 在 2.10 版本以後針對 Android 方面作了很大的優化,使用方法也隨之有了很多變化。本次改動除了讓 Dagger 2 的使用更加符合控制反轉原則,還針對 Android 端作出了專門的優化(即所謂 dagger.android) —— 大大簡化了 Android 端 Dagger 2 的使用。如今,不妨隨着本文一塊兒探尋新版本 Dagger 2 的基本使用方法。html
要注意閱讀本文內容前最好對 2.10 版本前的 Dagger 2 比較熟悉,至少要明白它的依賴注入管理機制,最好是實際項目中使用過,否則閱讀時會比較迷茫。固然,一切的 Dagger 2 的說明文章不少,善用搜索引擎就能夠找到不錯的教程,這裏就再也不贅述了。
另外,本文的 demo 項目中用到了 Google Samples 項目 Android Architecture 使用的 MVP 實現方式,這個也最好有所瞭解。主要是瞭解它的 todo-mvp-dagger 分支的結構組織方法便可,至少要明白它的功能層次和組織結構。java
想一想咱們在 2.10 版本以前是怎麼在 Android 端使用 Dagger 2 的?是否是相似下面的代碼:android
public class FrombulationActivity extends Activity {
@Inject Frombulator frombulator;
@Override
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
}
}複製代碼
代碼例子取自 Google 官方的 Dagger 2 文檔。git
emmmmm,好像也沒什麼問題啊?github
不,其實這段代碼大有問題!數據庫
第一點,代碼重複度太高:咱們要在幾乎每個須要注入依賴項的 Activity 和 Fragment 裏面來一套這樣的代碼。這是由於咱們在 Android 編程中要用到的 Activity、Fragment 等類是繼承自系統的,同時生命週期都由系統管理,因此使用 Dagger 2 的時候就不得不進行手動的處理,也就只有在純粹本身寫的 Java 類中使用 Dagger 2 才感受更舒服一些。編程
第二點,違反了控制反轉原則:原有的用法使得被注入的目標類必需要了解注入管理工具的詳細信息,才能讓注入工做順利進行。即便能夠經過接口使得實際代碼中沒必要書寫太多的實際類名,但這仍然形成了嚴重的目標類和管理類的緊耦合。api
其實,咱們之前在使用 Dagger 2 的時候爲了解決重複問題也是使用 Live Template 或者設計一個通用的工具函數;而爲了保證注入目標類正常工做和注入管理正常進行,就必須在設計業務代碼的時候並行設計注入管理代碼。
幸虧,2.10 版本針對 Android 系統作出了很大的優化,甚至單獨作了 Android 方面的依賴注入管理庫,提供了針對 Android 的註解和輔助類大大減小了要寫的代碼。app
下面我就用一個簡單的 demo 講解一下新版本的 Dagger 2 的基本使用方法,項目地址 請戳這裏。注意項目有兩個分支,master 分支用來演示基本使用方法,simplify 分支演示簡化用法。ide
實際上,新版本的 Dagger 2 能夠有多種使用方式,須要用戶構建不一樣數量的接口、抽象類,但咱們先不將最簡化的使用方式,由於用基本但複雜的方法更好理解 Dagger 2 的使用邏輯和結構。
要使用基本的功能,只須要在 app 的 build.gradle 文件中加入下列依賴代碼:
implementation 'com.google.dagger:dagger:2.13'
implementation 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-compiler:2.13'複製代碼
Application 的注入基本沒有什麼變化,更多的是 Dagger 2 官方建議最好使用 Builder 模式構建 Component
,方便靈活的向 Component
中添加構建屬性,好比:
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
void inject(MyApplication application);
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
}複製代碼
Module
和舊版相比沒什麼太大變化:
@Module
public class AppModule {
@Singleton
@Provides
Context provideContext(Application application) {
return application;
}
}複製代碼
而通常在調用數據庫或存取 SharedPreferences 文件時,經常用到
Context
,通常會提供 Application 而非 Activity 等。
第一,是方便管理多級 Component
,要求在最頂層的即 Application 的 Component
中引入 AndroidInjectionModule 或 AndroidSupportInjectionModule,代碼形式以下:
@Singleton
@Component(modules = {AppModule.class, AndroidSupportInjectionModule.class})
public interface AppComponent {
// ...
}複製代碼
@BindInstance
註解方便在 Builder 中加入設置項,傳入要求的實例就能設置 Builder 中的對應屬性。
第二,Activity 的 Component
(也能夠是某個功能模塊的 Component
)用 @Subcomponent
註解而非 @Component
,同時它還要繼承 AndroidInjector<T>
(T 通常爲對應的 Activity)。固然,若是有對應的 Module
也不要忘記在註解中用 moduls
添加。好比:
@ActivityScoped
@Subcomponent(modules = MainActivityModule.class)
public interface MainActivityComponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
}
}複製代碼
此處 的 Builder 可繼承
AndroidInjector.Builder<T>
,必須對應@Subcomponent.Builder
註解的抽象類,方便生成 Builder 代碼。
第三,要在第二步中的 Subcomponent
的父 Module
中用註解標記出 Subcomponents
的內容。
能夠把 AppModule
看成父 Module
:
@Module(subcomponents = {MainActivityComponent.class})
public class AppModule {
@Singleton
@Provides
Context provideContext(Application application) {
return application;
}
}複製代碼
也能夠另外寫一個 `ActivityBindingModule
看成父 Module
:
@Module(subcomponents = {MainActivityComponent.class, DummyActivityComponent.class})
public abstract class ActivityBindingModule {
// ...
}複製代碼
相對來講,
subcomponents
寫在哪裏其實並不重要,ActivityBindingModule
也不止這個上面一個做用,但我的認爲寫到這裏更方便管理。
第四,綁定 Subcomponent
的 Builder 到 AndroidInjector.Factory<T>
,方便 Dagger 2 藉助正確的 Builder 類型生成對應的 AndroidInjector
代碼,爲方便管理最好寫到一個統一的 Module
中:
@Module(subcomponents = {MainActivityComponent.class, DummyActivityComponent.class})
public abstract class ActivityBindingModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindMainActivityInjectorFactory(
MainActivityComponent.Builder builder);
}複製代碼
第五,把第四不得 Module
信息加入到 Application 的 Component
註解:
@Singleton
@Component(
modules = {AppModule.class, ActivityBindingModule.class,
AndroidSupportInjectionModule.class}
)
public interface AppComponent {
// ...
}複製代碼
注意:我這裏沒有采用官方文檔的寫法,使用 Activity 的
Module
來管理subcomponents
屬性,以及綁定 Builder。由於本文描述的方案邏輯上更好理解,也能更集中地管理類似的代碼,這也是從 Google Samples 演示項目 Android Architecture 學到的方法。
若是須要對 Fragment 設置單獨的注入管理類,那麼能夠參考 Activity 的方式,不一樣的是父類的 Module
信息要放到 Activity 的 Component
註解中,全部管理類信息以下(本例中也用到了簡單的 MVP 結構):
DummyActivity 相關:
@ActivityScoped
@Subcomponent(modules = {DummyActivityModule.class, DummyFragmentBindingModule.class})
public interface DummyActivityComponent extends AndroidInjector<DummyActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<DummyActivity> {
}
}
/*************************************/
@Module
public class DummyActivityModule {
@ActivityScoped
@Provides
DummyContract.Presenter provideDummyPresenter(DummyPresenter presenter) {
return presenter;
}
}複製代碼
DummyFragment 相關:
@Module(subcomponents = DummyFragmentComponent.class)
public abstract class DummyFragmentBindingModule {
@Binds
@IntoMap
@FragmentKey(DummyFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> bindDummyFragment(
DummyFragmentComponent.Builder builder);
}
/*************************************/
@FragmentScoped
@Subcomponent(modules = DummyFragmentModule.class)
public interface DummyFragmentComponent extends AndroidInjector<DummyFragment> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<DummyFragment> {
}
}
/*************************************/
@Module
public class DummyFragmentModule {
@FragmentScoped
@Provides
DummyContract.View provideDummyView(DummyFragment fragment) {
return fragment;
}
// ...
}複製代碼
本例子中的 MVP 實現方式,參考了 Google Samples 的 Android Architecture 的 todo-mvp-dagger 分支,即把 Activity 僅僅看成 Fragment 的管理容器,Fragment 做爲 MVP 中的 View 角色來對待。
第一,Applicaiton 要實現接口 HasActivityInjector
,用來實現自動管理 Activity 的 Injector
,具體實現方法是固定不變的:
public class MyApplication extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> mInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().application(this).build().inject(this);
}
@Override
public AndroidInjector<Activity> activityInjector() {
return mInjector;
}
}複製代碼
第二,無需管理 Fragment 的 Injector
的 Activity 直接注入本身須要的依賴便可:
public class MainActivity extends BaseActivity {
// ...
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}複製代碼
第三,須要管理 Fragment 的 Injector
的 Activity 須要實現接口 HasSupportFragmentInjector
,方式相似第一步:
public class DummyActivity extends BaseActivity implements HasSupportFragmentInjector {
@Inject DispatchingAndroidInjector<Fragment> mInjector;
// ...
@Override
protected void onCreate(final Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dummy_layout);
// ...
}
// ...
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return mInjector;
}
}複製代碼
第四,對 Fragment 注入依賴:
public final class DummyFragment extends BaseFragment {
// ...
@Override
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;
}
// ...
}複製代碼
仔細觀察上面的例子,咱們應該能發現其實新版本的 Dagger 2 並無更改原來的設定:仍然使用 Component
做注入器,使用 Module
做依賴提供者,也仍然使用多層級樹形 Component
來統合管理整個項目的依賴,同時 @Quilifier
和 @Scope
的做用也沒變。僅僅只是把每一個 Activity 和 Fragment 的注入代碼精簡了,無需知道注入的詳細細節了。
可是,僅僅如此的化,你必定會很疑惑:這個改法確實是更符合依賴反轉原則了,可實在也沒節省多少代碼啊?別急,基本用法通常只是用來了解原理的,實際使用不會這麼幹的!
首先,咱們要考慮究竟是什麼代碼重複最多,最容易使用工具生成呢?固然是 @Subcomponent
標註的代碼!由於上面的例子中咱們能夠看出它們的功能相對較爲簡單,只是爲了構建一個樹形結構方便管理,因此大部分編程情景下這部分的代碼其實沒有什麼額外的功能。而 Dagger 2 也貼心的提供了簡化的方案:
只要 @Subcomponent
標註的 Component
類知足如下條件,就能簡化:
在簡化以前,要先在 app 的 build.gradle 文件中添加下述依賴:
annotationProcessor 'com.google.dagger:dagger-android-processor:2.13'複製代碼
而後,就能使用 APT 工具自動生成相應的 @Subcomponent
代碼,好比 Activity 的 Component
就無需再寫,而是在 ActivityBindingModule
中寫入以下的代碼:
@Module
public abstract class ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity bindMainActivity();
@ActivityScoped
@ContributesAndroidInjector(
modules = {DummyActivityModule.class, DummyFragmentBindingModule.class}
)
abstract DummyActivity bindDummyActivity();
}複製代碼
而 Fragment 的 @Subcomponent
註解也如法炮製:
@Module
public abstract class DummyFragmentBindingModule {
@FragmentScoped
@ContributesAndroidInjector(modules = DummyFragmentModule.class)
abstract DummyFragment bindDummyFragment();
}複製代碼
注意:
DummyFragmentBindingModule
的註解信息必須加到同DummyActivityModule
同樣的地方,即本例中的bindDummyActivity()
的註解中。
這樣,即便 Activity 大量增長也不用寫大量沒什麼變化的 Component
代碼了。固然 Module
仍是須要的,只不過也能夠有部分的簡化。
好比 Applicaiton 的 Module
使用 @Binds
把 Application 實例和 Context
進行綁定:
@Module
public abstract class AppModule {
@Binds
abstract Context provideContext(Application application);
}複製代碼
同理,在 MVP 中若是須要提供 Presenter
接口也可使用這個辦法,好比:
@Module
public abstract class DummyActivityModule {
@ActivityScoped
@Binds
abstract DummyContract.Presenter bindDummyPresenter(DummyPresenter presenter);
}複製代碼
只不過這個辦法只能把傳入的實例和返回類型進行綁定,其餘複雜的依賴提供方法(好比須要利用傳入參數手動實例化,或進行條件判斷)仍是不能簡化的。(這不是廢話嗎...)
注入操做很明顯能夠簡化,畢竟模式徹底相同,簡化的方法就是提供了模板父類 DaggerApplication
、DaggerAppCompatActivity
和 DaggerFragment
,而後讓須要注入操做的類繼承它們便可。
最後,Application 的代碼就以下所示:
public class MyApplication extends DaggerApplication {
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}
}複製代碼
而 Activity 和 Fragment 則最好是讓 BaseActivity
和 BaseFragment
去繼承上面提到的兩個模板父類,在通常的代碼中自只須要用 @Inject
標出須要注入的元素便可,無需任何額外操做。這樣,在編寫 Activity 和 Fragment 的時候無需考慮依賴注入的細節,只要按照正常流程編寫代碼,而後不斷檢查、測試代碼,不斷標記出須要注入的元素便可。
最後,再次提醒相關代碼不可能所有演示出來,能夠去 Dagger 2 Android Demo 查看具體細節,尤爲簡化部分的代碼重複內容較多文章不做贅述,須要的能夠自行查看 simplify 分支。另外,若是項目有 bug 也歡迎直接提出 issue。