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 的继承关系,FragemntBindModule 定义六个 Fragment 对应的 SubComponent 的继承关系。

1.1 继承关系的代码实现

继承关系的实现需要:(1)在 parent Component 依赖的 Module 中的subcomponents加上 SubComponent 的 class;(2)在 parent Component 中提供返回 SubComponent.Builder 的接口用以创建 SubComponent。

ActivityBindModule 和 FragemntBindModule 对应上面的第一步,下面看看 AppComponent 和 MainActivityComponent 两个关键 Component 的实现:

@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 依赖注入的实现

先看 AppComponent 的创建过程:

class GankApp : Application() {

    lateinit var appComponent: AppComponent

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

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

再看 SearchActivity 中的注入实现:

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)
    }
}

上面的实现有下面几个问题:

  • 每个需要注入依赖的页面 Activity 或 Fragment 都需要创建一个 Component 类。

  • 继承关系中第二步实现,每个 SubComponent 都需要在 parent componenet 声明对应的返回对应的 SubComponent.Builder 的接口

  • 在 Activity 或 Fragment 中注入依赖时,都必须知道其对应的注入器(Component)的类型,这有悖于依赖注入的原则:被注入的类不应该知道依赖注入的任何细节。

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 实现 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 类实现 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 注解可以解决之前的两个问题,不需要手动创建每一个 SubComponent,也不用在 parent componenet 声明对应的返回对应的 SubComponent.Builder 的接口,以后添加 SubComponent 只需要添加一个 ContributesAndroidInjector 抽象方法。

然后使用AndroidInjection.inject(this) 来简化注入依赖的过程,隐藏注入依赖的细节。

注入 Fragment 和其他三大组件也是类似,只是 ContributesAndroidInjector 抽象方法的返回值变了,HasActivityInjector 接口换做 HasFragmentInjector 等接口。

3. dagger.android 扩展库的原理

3.1 @ContributesAndroidInjector 原理

在上面使用 dagger.android 扩展库注入 Activity 中依赖时,其中第二步定义 SubComponent 和第三步添加一个 SubComponent.Builder 的绑定到 Map 中,这两步可以用 ContributesAndroidInjector 抽象方法简化。其实只是 dagger.android 的注解处理器根据 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)完成依赖注入的过程,这背后是如何实现的呢?

先看该方法的关键源码:

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) {
    // 根据 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 {
      // 根据 AndroidInjector.Factory 创建 AndroidInjector,即创建 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) {
      ...
    }
  }
  ...
}

上面的逻辑简单的来说就是根据 activity 的类型获取相应的 SubComponent.Builder,然后创建其 SubComponent,最后使用 SubComponent 完成依赖注入工作。

现在再回过头来看第三步添加一个 SubComponent.Builder 的绑定到 Map 中,是添加到 AndroidInjectionModule 的 multibinging 中。

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

然后才能实现 DispatchingAndroidInjector<Activity> 的注入过程,Application 需要实现 HasActivityInjector 接口,也是为了方便获取 dispatchingActivityInjector 对象,这样就隐藏了注入器的类型,通过 Activity 的类型获取对应的 SubComponent 完成注入。

4. 总结

dagger.android 扩展库可以极大地简化在 Android 项目中使用 Dagger 2 的过程,但是还是有些限制,SubComponent.Builder 不能自定义 @BindsInstance 方法,SubCompoennt 的 Module 不能有含参数的构造函数,否则AndroidInjection.inject(this)在创建 SubComponent 时无法成功。

在使用过 dagger.android 扩展库一段时间后,个人认为其设计非常优雅,简化了 SubComponent 的定义过程和依赖注入的过程,使得开发者可以专注于 Module 管理依赖对象,所以建议大家在 Android 项目中使用。

最后 Dagger 2 系列文章也到此结束了(Dagger 2 关于单元测试的部分放到 Kotlin 下单元测试的系列中),希望对大家有所帮助。

参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容