Dagger 2 完全解析(六)拴清,dagger.android 擴展庫的使用

Dagger 2 完全解析系列:

Dagger 2 完全解析(一)靶病,Dagger 2 的基本使用與原理

Dagger 2 完全解析(二),進階使用 Lazy口予、Qualifier娄周、Scope 等

Dagger 2 完全解析(三),Component 的組織關系與 SubComponent

Dagger 2 完全解析(四)沪停,Android 中使用 Dagger 2

Dagger 2 完全解析(五)煤辨,Kotlin 中使用 Dagger 2

Dagger 2 完全解析(六)裳涛,dagger.android 擴展庫的使用

本系列文章是基于 Google Dagger 2.11-rc2 版本

在項目中使用了 dagger.android 擴展庫后,體驗到了其可以簡化 Dagger 2 在 Android 項目中的使用众辨,所以該系列的最后一篇文章解析 dagger.android 擴展庫的使用及原理端三。本文以個人寫的 Gank 項目 為例,逐步分析 dagger.android 擴展庫的使用鹃彻。

注:本文代碼語言為 Kotlin郊闯。

1. Gank 項目中原來的 Dagger 2 依賴注入邏輯

Gank 項目中依賴關系比較簡單,主要是提供一個單例的 GankService 依賴用于拉取 API 接口蛛株。依賴關系圖如下:

gank_dependency.jpg

其中 AppComponent 持有單例的 GankService 依賴团赁,三個 Activity 對應三個 SubComponent 繼承自 AppComponent,六個 Fragment 對應六個 SubComponent 繼承自 MainActivityComponent泳挥。

相應的 Dagger 2 類結構如下:

gank_di_toc.png

AppModule 提供 GankService 依賴然痊,ActivityBindModule 定義三個 Activity 對應的 SubComponent 的繼承關系,F(xiàn)ragemntBindModule 定義六個 Fragment 對應的 SubComponent 的繼承關系屉符。

1.1 繼承關系的代碼實現(xiàn)

繼承關系的實現(xiàn)需要:(1)在 parent Component 依賴的 Module 中的subcomponents加上 SubComponent 的 class剧浸;(2)在 parent Component 中提供返回 SubComponent.Builder 的接口用以創(chuàng)建 SubComponent。

ActivityBindModule 和 FragemntBindModule 對應上面的第一步矗钟,下面看看 AppComponent 和 MainActivityComponent 兩個關鍵 Component 的實現(xiàn):

@Singleton
@Component(modules = [AppModule::class, ActivityBindModule::class])
interface AppComponent {

    val appContext: Context

    fun mainActivityComponent(): MainActivityComponent.Builder

    fun pictureActivityComponent(): PictureActivityComponent.Builder

    fun searchActivityComponent(): SearchActivityComponent.Builder
}

@ActivityScope
@Subcomponent(modules = [FragmentBindModule::class])
interface MainActivityComponent {

    @Subcomponent.Builder
    interface Builder {

        @BindsInstance
        fun activity(activity: Activity): Builder

        fun build(): MainActivityComponent
    }

    fun welfareFragmentComponent(): WelfareFragmentComponent.Builder

    fun todayGankFragmentComponent(): TodayGankFragmentComponent.Builder

    fun androidFragmentComponent(): AndroidFragmentComponent.Builder

    fun iosFragmentComponent(): IOSFragmentComponent.Builder

    fun frontEndFragmentComponent(): FrontEndFragmentComponent.Builder

    fun videoFragmentComponent(): VideoFramentComponent.Builder
}

1.2 Component 依賴注入的實現(xiàn)

先看 AppComponent 的創(chuàng)建過程:

class GankApp : Application() {

    lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()
        ...
        initInjector()
    }

    private fun initInjector() {
        appComponent = DaggerAppComponent.builder()
                .appModule(AppModule(this))
                .build()
    }
}

再看 SearchActivity 中的注入實現(xiàn):

class SearchActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        initInjector()
    }

    private fun initInjector() {
        (application as GankApp).appComponent
            .searchActivityComponent()
            .activity(this)
            .build()
            .inject(this)
    }
}

上面的實現(xiàn)有下面幾個問題:

  • 每個需要注入依賴的頁面 Activity 或 Fragment 都需要創(chuàng)建一個 Component 類唆香。

  • 繼承關系中第二步實現(xiàn),每個 SubComponent 都需要在 parent componenet 聲明對應的返回對應的 SubComponent.Builder 的接口

  • 在 Activity 或 Fragment 中注入依賴時吨艇,都必須知道其對應的注入器(Component)的類型躬它,這有悖于依賴注入的原則:被注入的類不應該知道依賴注入的任何細節(jié)。

2. dagger.android 擴展庫的使用

dagger.android 擴展庫就是為了解決上述問題而產生的东涡,簡化 Dagger 2 在 Android 的使用冯吓。

2.1 引入 dagger.android 擴展庫

// dagger 2
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"

// dagger.android
implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

從上面可以看出 dagger.android 擴展庫有單獨的注解處理器 dagger-android-processor。

2.2 注入 Activity 中的依賴

以 SearchActivity 為例疮跑,說明 dagger.android 的使用:

1.在 AppComponent 中安裝 AndroidInjectionModule组贺,確保包含四大組件和 Fragment 的注入器類型。

@Singleton
@Component(modules = [AppModule::class, AndroidInjectionModule::class])
interface AppComponent { ... }

2.Activity 對應的 SubComponent 實現(xiàn) AndroidInjector<XXActivity> 接口祖娘,對應的 @Subcomponent.Builder 繼承 AndroidInjector.Builder<XXActivity>失尖。

@ActivityScope
@Subcomponent
interface SearchActivitySubcomponent : AndroidInjector<SearchActivity> {
    @Subcomponent.Builder
    abstract class Builder : AndroidInjector.Builder<SearchActivity>()
}

3.在定義 SubComponent 后,添加一個 ActivityBindModule 用來綁定 subcomponent builder渐苏,并把該 module 安裝到 AppComponent 中掀潮。

@Module(subcomponents = [SearchActivitySubcomponent::class])
abstract class ActivityBindModule {
    @Binds
    @IntoMap
    @ActivityKey(SearchActivity::class)
    abstract fun bindAndroidInjectorFactory(
            builder: SearchActivitySubcomponent.Builder): AndroidInjector.Factory<out Activity>
}

@Singleton
@Component(modules = [AppModule::class, AndroidInjectionModule::class, ActivityBindModule::class])
interface AppComponent { ... }

如果 SubComponent 和其 Builder 沒有其他方法或沒有繼承其他類型,可以使用 @ContributesAndroidInjector 注解簡化第二步和第三步琼富,在一個抽象 Module 中添加一個使用 @ContributesAndroidInjector 注解標記的返回具體的 Activity 類型的抽象方法仪吧,還可以在 ContributesAndroidInjector 注解中標明 SubComponent 需要安裝的 module。如果 SubComponent 需要作用域鞠眉,只需要標記在該方法上即可邑商。

@Module
abstract class ActivityBindModule {
    @ActivityScope
    @ContributesAndroidInjector
    abstract fun searchActivityInjector(): SearchActivity
}

@Singleton
@Component(modules = [AppModule::class, AndroidInjectionModule::class, ActivityBindModule::class])
interface AppComponent { ... }

4.Application 類實現(xiàn) HasActivityInjector 接口摄咆,并且注入一個 DispatchingAndroidInjector<Activity> 類型的依賴作為 activityInjector() 方法的返回值。

class GankApp : Application(), HasActivityInjector {

    @Inject
    lateinit var dispatchingActivityInjector: DispatchingAndroidInjector<Activity>

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.builder()
                .appModule(AppModule(this))
                .build()
                .inject(this)
    }

    override fun activityInjector() = dispatchingActivityInjector
}

5.最后在 onCreate)() 方法中人断,在 super.onCreate() 之前調用 AndroidInjection.inject(this)

class SearchActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)

        ...
    }
}

使用 dagger.android 后 Activity 對應的 SubComponent 的定義簡化如下:

@Module
abstract class ActivityBindModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = [FragmentBindModule::class])
    abstract fun mainActivityInjector(): MainActivity

    @ActivityScope
    @ContributesAndroidInjector
    abstract fun pictureActivityInjector(): PictureActivity

    @ActivityScope
    @ContributesAndroidInjector
    abstract fun searchActivityInjector(): SearchActivity
}

其中 @ContributesAndroidInjector 注解可以解決之前的兩個問題朝蜘,不需要手動創(chuàng)建每一個 SubComponent恶迈,也不用在 parent componenet 聲明對應的返回對應的 SubComponent.Builder 的接口,以后添加 SubComponent 只需要添加一個 ContributesAndroidInjector 抽象方法谱醇。

然后使用AndroidInjection.inject(this) 來簡化注入依賴的過程暇仲,隱藏注入依賴的細節(jié)。

注入 Fragment 和其他三大組件也是類似副渴,只是 ContributesAndroidInjector 抽象方法的返回值變了奈附,HasActivityInjector 接口換做 HasFragmentInjector 等接口。

3. dagger.android 擴展庫的原理

3.1 @ContributesAndroidInjector 原理

在上面使用 dagger.android 擴展庫注入 Activity 中依賴時煮剧,其中第二步定義 SubComponent 和第三步添加一個 SubComponent.Builder 的綁定到 Map 中斥滤,這兩步可以用 ContributesAndroidInjector 抽象方法簡化。其實只是 dagger.android 的注解處理器根據(jù) ContributesAndroidInjector 抽象方法在編譯時完成了這兩步的代碼勉盅,編譯完后的代碼邏輯其實是一樣佑颇。

看上面 SearchActivity 的例子,ContributesAndroidInjector 抽象方法為:

@Module
abstract class ActivityBindModule {
    @ActivityScope
    @ContributesAndroidInjector
    abstract fun searchActivityInjector(): SearchActivity
}

在編譯后會生成一個 ActivityBindModule_SearchActivityInjector 類:

@Module(subcomponents = ActivityBindModule_SearchActivityInjector.SearchActivitySubcomponent.class)
public abstract class ActivityBindModule_SearchActivityInjector {
  private ActivityBindModule_SearchActivityInjector() {}

  @Binds
  @IntoMap
  @ActivityKey(SearchActivity.class)
  abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
      SearchActivitySubcomponent.Builder builder);
  // 第三步添加一個 SubComponent.Builder 的綁定到 Map 中

  @Subcomponent
  @ActivityScope
  public interface SearchActivitySubcomponent extends AndroidInjector<SearchActivity> {
    // 第二步定義 SubComponent
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<SearchActivity> {}
  }
}

所以 @ContributesAndroidInjector 原理是利用注解處理器減少手動 coding 的代碼量草娜。

3.2 AndroidInjection.inject(this) 的原理

dagger.android 擴展庫可以使用一行代碼AndroidInjection.inject(this)完成依賴注入的過程挑胸,這背后是如何實現(xiàn)的呢?

先看該方法的關鍵源碼:

public static void inject(Activity activity) {
    ...
    // 調用 application 的 activityInjector() 方法獲取到 dispatchingActivityInjector 對象
    AndroidInjector<Activity> activityInjector =
        ((HasActivityInjector) application).activityInjector();
    ...
    // 使用 dispatchingActivityInjector 對象注入 activity 所需的依賴
    activityInjector.inject(activity);
}

接著看 DispatchingAndroidInjector 的 inject() 方法的邏輯:

public final class DispatchingAndroidInjector<T> implements AndroidInjector<T> {

  private final Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>>
    injectorFactories;

  @Inject
  DispatchingAndroidInjector(
      Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>> injectorFactories) {
    this.injectorFactories = injectorFactories;
  }

  @Override
  public void inject(T instance) {
    // 在 maybeInject 中完成注入
    boolean wasInjected = maybeInject(instance);
    if (!wasInjected) {
      throw new IllegalArgumentException(errorMessageSuggestions(instance));
    }
  }

  public boolean maybeInject(T instance) {
    // 根據(jù) activity 的類型獲取到對應的 AndroidInjector.Factory 的 Provider宰闰,
    // 其實就是 SubComponent.Builder茬贵,因為第二步中定義的 SubComponent.Builder 繼承了 AndroidInjector.Factory
    Provider<AndroidInjector.Factory<? extends T>> factoryProvider =
        injectorFactories.get(instance.getClass());
    if (factoryProvider == null) {
      return false;
    }

    @SuppressWarnings("unchecked")
    AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
    // 獲取到 AndroidInjector.Factory
    try {
      // 根據(jù) AndroidInjector.Factory 創(chuàng)建 AndroidInjector,即創(chuàng)建 SubComponent
      AndroidInjector<T> injector =
          checkNotNull(
              factory.create(instance),
              "%s.create(I) should not return null.",
              factory.getClass().getCanonicalName());

      injector.inject(instance);
      return true;
    } catch (ClassCastException e) {
      ...
    }
  }
  ...
}

上面的邏輯簡單的來說就是根據(jù) activity 的類型獲取相應的 SubComponent.Builder移袍,然后創(chuàng)建其 SubComponent解藻,最后使用 SubComponent 完成依賴注入工作。

現(xiàn)在再回過頭來看第三步添加一個 SubComponent.Builder 的綁定到 Map 中咐容,是添加到 AndroidInjectionModule 的 multibinging 中舆逃。

@Module
public abstract class AndroidInjectionModule {
  @Multibinds
  abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
      activityInjectorFactories();
  ...
}

然后才能實現(xiàn) DispatchingAndroidInjector<Activity> 的注入過程,Application 需要實現(xiàn) HasActivityInjector 接口戳粒,也是為了方便獲取 dispatchingActivityInjector 對象路狮,這樣就隱藏了注入器的類型,通過 Activity 的類型獲取對應的 SubComponent 完成注入蔚约。

4. 總結

dagger.android 擴展庫可以極大地簡化在 Android 項目中使用 Dagger 2 的過程奄妨,但是還是有些限制,SubComponent.Builder 不能自定義 @BindsInstance 方法苹祟,SubCompoennt 的 Module 不能有含參數(shù)的構造函數(shù)砸抛,否則AndroidInjection.inject(this)在創(chuàng)建 SubComponent 時無法成功评雌。

在使用過 dagger.android 擴展庫一段時間后,個人認為其設計非常優(yōu)雅直焙,簡化了 SubComponent 的定義過程和依賴注入的過程景东,使得開發(fā)者可以專注于 Module 管理依賴對象,所以建議大家在 Android 項目中使用奔誓。

最后 Dagger 2 系列文章也到此結束了(Dagger 2 關于單元測試的部分放到 Kotlin 下單元測試的系列中)斤吐,希望對大家有所幫助。

參考資料:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末厨喂,一起剝皮案震驚了整個濱河市和措,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜕煌,老刑警劉巖派阱,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異斜纪,居然都是意外死亡贫母,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門傀广,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颁独,“玉大人,你說我怎么就攤上這事伪冰∈木疲” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵贮聂,是天一觀的道長靠柑。 經常有香客問我,道長吓懈,這世上最難降的妖魔是什么歼冰? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮耻警,結果婚禮上隔嫡,老公的妹妹穿的比我還像新娘。我一直安慰自己甘穿,他們只是感情好腮恩,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著温兼,像睡著了一般秸滴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上募判,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天荡含,我揣著相機與錄音咒唆,去河邊找鬼。 笑死释液,一個胖子當著我的面吹牛全释,可吹牛的內容都是我干的。 我是一名探鬼主播均澳,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼恨溜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了找前?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤判族,失蹤者是張志新(化名)和其女友劉穎躺盛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體形帮,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡槽惫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辩撑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片界斜。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖合冀,靈堂內的尸體忽然破棺而出各薇,到底是詐尸還是另有隱情,我是刑警寧澤君躺,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布峭判,位于F島的核電站,受9級特大地震影響棕叫,放射性物質發(fā)生泄漏林螃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一俺泣、第九天 我趴在偏房一處隱蔽的房頂上張望疗认。 院中可真熱鬧,春花似錦伏钠、人聲如沸横漏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绊茧。三九已至,卻和暖如春打掘,著一層夾襖步出監(jiān)牢的瞬間华畏,已是汗流浹背鹏秋。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亡笑,地道東北人侣夷。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像仑乌,于是被迫代替她去往敵國和親百拓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容