依赖注入(九)—— Android特供版Dagger

508 阅读4分钟

受限于Android结构的特殊性,当我们在Android项目中使用Dagger时,不得不在组件的生命周期中编写一些模板代码:

class FrombulationActivity : Activity() {
    @Inject
    lateinit var frombulator: Frombulator

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

        (contxt.applicationContext as MyApplication)
            .getApplicationComponent()
            .newActivityComponentBuilder()
            .activity(this)
            .build()
            .inject(this)
    }
}

为了实现对Android组件依赖项的注入,我们不得不在每一个需要的组件的生命周期中复制-粘贴这些模板代码,这是一种”坏味道“。另外,更重要的是,发起请求注入的客户端需要知道它的注入器,这破坏了依赖注入的核心原则:一个类不应知道如何实现依赖注入。

为了解决这个问题,google提供了dagger的安卓特供版:dagger-android

依赖引入

apply plugin: 'kotlin-kapt'

dependencies {
    implementation 'com.google.dagger:dagger-android:2.x'
    // 如果需要使用support库或androidx中的组件
    implementation 'com.google.dagger:dagger-android-support:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
    kapt 'com.google.dagger:dagger-android-processor:2.x'
}

为Activity注入

  1. AndroidInjectionModule注册到Application Component中。

  2. 为请求注入的Android组件提供一个SubComponent,该SubComponent继承自AndroidInjector<T>,并且该SubComponent拥有一个继承自AndroidInjector.Factory<T>的Factory接口:

    @Subcomponent
    interface MainComponent : AndroidInjector<MainActivity> {
        @Subcomponent.Factory
        interface Factory : AndroidInjector.Factory<MainActivity>
    }
    
  3. 为SubComponent编写一个用于绑定子组件的Module,并在其中提供一个用于绑定SubComponent的Factory的方法:

    @Module(subcomponents = [MainComponent::class])
    abstract class SubComponentsModule {
        @Binds
        @IntoMap
        @ClassKey(MainActivity::class)
        abstract fun bindMainActivityInjectorFactory(
            factory: MainComponent.Factory
        ): AndroidInjector.Factory<*> 
    }
    

    如果子组件和其工厂中除了步骤2、步骤3中提到的内容以外,没有其他方法和supertype时(其实大部分情况下你都不需要编写额外的内容,因为Factory限制了create方法的定义而SubComponentsModule不允许提供任何其它的绑定),你可以使用@ContributesAndroidInjector注解来简化上述的模板代码:

    @Module
    abstract class SubComponentsModule {
        @YourScope
        @ContributesAndroidInjector(modules = [MainModule::class])
        abstract fun contributeMainActivityInjector(): MainActivity
    }
    
  4. 然后在Application Component中注册SubComponent。

    @Component(
        modules = [
            AndroidInjectionModule::class
            , SubComponentsModule::class
            ...
        ],
    )
    interface AppComponent { ... }
    
  5. 接着为Application实现HasAndroidInjector接口,并且为其提供一个DispatchingAndroidInjector<Any>成员,该成员通过@Inject注入,并同时作为androidInjector()的返回值返回。

    class MyApplication : Application(), HasAndroidInjector {
        @Inject
        lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
    
        override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector
        ...
    }
    
  6. 最后,在Activity的onCreate方法中调用AndroidInjection.inject(this)方法完成注入。

实现原理

为了弄清楚dagger-android的注入原理,现在我们从注入开始倒着分析源码。

首先是在Activity中的AndroidInjection.inject(this)

public static void inject(Activity activity) {
    Application application = activity.getApplication();
	  ...
    inject(activity, (HasAndroidInjector) application);
}

private static void inject(Object target, HasAndroidInjector hasAndroidInjector) {
    AndroidInjector<Object> androidInjector = hasAndroidInjector.androidInjector();
    androidInjector.inject(target);
}

可以看到,AndroidInjection首先通过activity获取到application对象,然后调用其androidInjector()方法获取AndroidInjector实例。这个androidInjector()方法来自于HasAndroidInjector接口,我们之前已经在Application上实现了它,该方法会返回一个DispatchingAndroidInjector实例。

@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector

随后AndroidInjection会把对activity的注入委派给DispatchingAndroidInjector实例。接下来我们看看DispatchingAndroidInjector中的注入。

@Override
public void inject(T instance) {
    boolean wasInjected = maybeInject(instance);
    if (!wasInjected) {
      throw new IllegalArgumentException(errorMessageSuggestions(instance));
    }
}

public boolean maybeInject(T instance) {
    Provider<AndroidInjector.Factory<?>> factoryProvider = 
        injectorFactories.get(instance.getClass().getName());
    if (factoryProvider == null) return false;
  
    AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
    try {
      AndroidInjector<T> injector = factory.create(instance);
      injector.inject(instance);
      return true;
    } catch (ClassCastException e) {
      ...
    }
  }

DispatchingAndroidInjector中,首先会根据注入目标instance的类名(也就是具体的Activity名称)在injectorFactories中查找对应的Component工厂。如果找到了工厂类,就创建对应的Component,再通过component实例执行注入。

那么injectorFactories中保存的Component工厂是从哪里来的呢?

没错正是我们在SubComponentsModule中使用MultiBind提供的,通过代码可以看到Activity类对象和Component factory的映射通过DispatchingAndroidInjector的构造函数注入,而DispatchingAndroidInjector又是在Application中注入的,AppComponent中又依赖SubComponentsModule,至此整个注入过程顺利闭环。

private final Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactories;

@Inject
DispatchingAndroidInjector(
    Map<Class<?>, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithClassKeys,
    Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithStringKeys
) {
    this.injectorFactories = merge(injectorFactoriesWithClassKeys, injectorFactoriesWithStringKeys);
}

为Fragment注入

Fragment的注入与Activity的注入过程基本一致,唯一需要注意的是要在Fragment的onAttach生命周期回调中使用AndroidInjection**.**inject(this);

另外dagger-android为Fragment提供了更加灵活的注入方式,Dagger允许我们将Fragment的Component定义为Application Component的SubComponent,或是承载它的Activity的Component的SubComponent,甚至是Fragment Component的SubComponent。定义好SubComponent的位置后,在父Component对应的组件上实现HasAndroidInjector接口即可。

框架基类

因为dagger-android是在运行时通过类名查找对应组件的注入器(Component),所以我们可以在基类中调用AndroidInjection.inject()来完成注入工作,或者实现HasAndroidInjector接口以实现对子模块的注入。

dagger-android也预置了一些已经实现好的基类,如DaggerApplicationDaggerActivityDaggerFragment等等,以帮助我们快速开发。