美文网首页Android开发框架Android知识Kotlin
Dagger 2 完全解析(六),dagger.android

Dagger 2 完全解析(六),dagger.android

作者: JohnnyShieh | 来源:发表于2018-02-26 17:12 被阅读876次

    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 下单元测试的系列中),希望对大家有所帮助。

    参考资料:

    相关文章

      网友评论

      • 在那里呢丿:组件化的时候,ActivityBindModule怎么加载模块的activity呢
      • 敲代码的本愿:module不能通过构造方法传入参数,那么如何传入参数,或者获取到当前依赖的activity实例,求解答
        JohnnyShieh:不是很清楚你的问题,可以具体一点说明
        敲代码的本愿:@敲代码的本愿 是在module里这方法时,作为方法参数传进去?
        JohnnyShieh:@敲代码的本愿 1. 如果想通过构造方法传入参数的话,只能使用原始的 Dagger 2 使用方式,不能使用 dagger.android 扩展库。
        2. 如果只是想获得 activity 实例的话,可以直接使用,因为在 AndroidInjector<T>.Builder 创建 Component 已经绑定了 activity 实例,生成的 ComponentImpl 里的 seedInstance 存放的就是你 AndroidInjection.inject() 的 activity 实例,可以用于之后的依赖注入

      本文标题:Dagger 2 完全解析(六),dagger.android

      本文链接:https://www.haomeiwen.com/subject/xgiyxftx.html