使用 Kotlin 构建 MVVM 应用程序—提高篇:Dagger-Android

1,478 阅读6分钟

写在前面

本篇是对于使用Kotlin构建MVVM应用程序—第四部分:依赖注入 Dagger2 的补充。

依赖注入 Dagger2 这篇文章中,我们了解了 Dagger2 是如何进行依赖注入的。

可以简单的将Dagger2理解成android应用的依赖管理工具。既然Dagger2已经可以满足我们日常的开发需要了,那么Dagger-Android又是拿来干什么的呢?

为什么要有Dagger-Android?

对于这个问题,google在Dagger-Android的文档上有解释:

我们普通的dagger代码如下所示:

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

这会带来一些问题:

  1. 只是复制粘贴上面的代码会让以后的重构比较困难,还会让一些开发者不知道Dagger到底是如何进行注入的(然后就更不易理解了)
  2. 更重要的原因是:它要求注射类型(FrombulationActivity)知道其注射器。 即使这是通过接口而不是具体类型完成的,它打破了依赖注入的核心原则:一个类不应该知道如何实现依赖注入。

为了解决上述的问题,Dagger-Android应运而生。

Dagger-Android

首先我们需要在app/build.gradle加入相应的依赖

	//dagger2  di
    implementation 'com.google.dagger:dagger:2.16'
    kapt 'com.google.dagger:dagger-compiler:2.16'
	//dagger-android
    implementation 'com.google.dagger:dagger-android:2.16'
    implementation 'com.google.dagger:dagger-android-support:2.16' 
    kapt 'com.google.dagger:dagger-android-processor:2.16'

注入方法建议看文档更好,这里简单描述一下:

还是以MVVM-android为例。

  1. 添加ActivityModule.kt
@Module
abstract class ActivityModule {

    @ContributesAndroidInjector
    abstract fun contributePaoActivity(): PaoActivity

}

2 . 修改AppComponent.kt

@Singleton
@Component(modules = arrayOf(
        AndroidInjectionModule::class,
        AppModule::class,
        ActivityModule::class)
)
interface AppComponent {

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

    fun inject(application: PaoApp)
}

相比Dagger2,modules多了AndroidInjectionModule和ActivityModule两个类。

3 . rebuild一下项目,然后新增PaoApp.kt同时实现HasActivityInjector接口

class PaoApp : Application(),HasActivityInjector{

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.builder().application(app).build().inject(app)
    }

    override fun activityInjector() = dispatchingAndroidInjector

}

4 . 最后在Activity的onCreate()方法之前调用 AndroidInjection.inject(this)进行注入

class PaoActivity : RxAppCompatActivity() {
    @Inject
    lateinit var mViewModel : PaoViewModel

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

到此,一个简单的依赖注入就好了。

当然简单是绝不简单的。这些是在写什么?完全云里雾里。刚从Dagger转换成Dagger-Android的直接就劝退了,还不如直接使用Dagger2的好。

确实,对于日常功能迭代的开发团队来说,普通的dagger更易理解,所以Dagger-Android也算是一个可选项,可以作为一个提高,而且google的很多示例里dagger的用法都是Dagger-Android,所以还是有必要懂它的原理。

原理剖析

第四部分中,我们也了解了普通的Dagger是如何进行依赖注入的,这里我们再来回顾一次

由AppModule提供所需的依赖
由AppCompent提供注入的途径
由@Inject标识需要注入的对象
调用
DaggerAppComponent.builder()
   .appModule(AppModule(applicationContext)).build()
   .inject(this)
完成依赖注入

这里的逻辑比较好理解一些,就是普通的

paoActivity.mViewModel = appComponent.paoViewModel

那Dagger-Android相比之下,又是怎么样的逻辑呢?

相比之前,Dagger-Android将Activity/Fragment所需的compoent都放到了一个map对象里,这个map对象由App的dispatchingAndroidInjector对象持有。其中key值为activity/fragment的class,value为提供相应Component的Provider对象。

Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>>

当我们在Activity中调用 AndroidInjection.inject(this)时,又在做什么呢?

public static void inject(Activity activity) {
    checkNotNull(activity, "activity");
    Application application = activity.getApplication();
    if (!(application instanceof HasActivityInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              application.getClass().getCanonicalName(),
              HasActivityInjector.class.getCanonicalName()));
    }
	//找到dispatchingAndroidInjector对象
    AndroidInjector<Activity> activityInjector =
        ((HasActivityInjector) application).activityInjector();
    checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());
	//进行注入
    activityInjector.inject(activity);
  }

activityInjector.inject(activity)里面的逻辑又可以描述为

一个全局的单例map对象,通过key值为activity.class即
map.get(activity.class) 找到activity与之对应的Provider<AndroidInjector.Factory<? extends T>>
。。。
。。。
然后经过层层的深入
找到对应的component
最后依然还是调用activity.viewmodel = component.viewmodel

和普通的dagger对比一下区别就在于:

Dagger-Android在刚开始的时候通过注解处理器分析@Component、@Module、@ContributesAndroidInjector 等等注解,帮我们在App启动的时候建立了一个全局的单例map,并添加相关的映射。

可以在生成的DaggerAppComponet.kt文件中找到

private Map<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>
    getMapOfClassOfAndProviderOfFactoryOf() {
  return Collections
      .<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>
          singletonMap(PaoActivity.class, (Provider) paoActivitySubcomponentBuilderProvider);
}

当注入的时候就间接的通过这个map找到对应activity需要的Component,完成注入。

接下来我们就具体来看看 activityInjector.inject(activity)是如何完成注入的。

注入过程

先来断点调试一下,看看activityInjector是什么?

activityInjector

可以看到activityInjector的真身是DispatchingAndroidInjector,实际调用的是DispatchingAndroidInjector的inject()方法,接着看看inject()方法

inject

调用的是maybeInject(instance),继续深入

maybeInject

到这里就很清晰了,正如前文所说的那样,通过一个单例map根据key值为instance.class

找到相应的factoryProvider,通过get()方法获取到AndroidInjector.Factory<T>对象

而它的真身是DaggerAppComponent的内部类PaoActivitySubcomponentBuilder

private final class PaoActivitySubcomponentBuilder
    extends ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.Builder {
    //....
    }

PaoActivitySubcomponentBuilder 继承了ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.Builder,再来看看ActivityModule_ContributePaoActivity

@Module(subcomponents = ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.class)
public abstract class ActivityModule_ContributePaoActivity {
  private ActivityModule_ContributePaoActivity() {}

  @Binds
  @IntoMap
  @ActivityKey(PaoActivity.class)
  abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
      PaoActivitySubcomponent.Builder builder);

  @Subcomponent
  public interface PaoActivitySubcomponent extends AndroidInjector<PaoActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<PaoActivity> {}
  }
}

这些代码是添加映射的关键代码了,Dagger-Android通过处理@ContributesAndroidInjector自动生成的,按照Dagger-Android文档上的说法是可以自己编写,@ContributesAndroidInjector只是简化了这步操作。

可以看到@Binds、@IntoMap、@ActivityKey 这几个注解,就如前文所说的那样将其保存到单例的map对象之中,key值便是PaoActivity.class

这些都是题外话,再回头继续断点调试,可以看到factory.create(instance)

create

调用了seedInstance()方法,这是一个抽象方法,由前文的PaoActivitySubcomponentBuilder实现。

private final class PaoActivitySubcomponentBuilder
      extends ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.Builder {
    private PaoActivity seedInstance;
@Override
public void seedInstance(PaoActivity arg0) {
  this.seedInstance = Preconditions.checkNotNull(arg0);
}
}

就是一个简单的赋值操作。然后返回类型为AndroidInjector<T>injector,断点可以看到它的真身是PaoActivitySubcomponentImpl

maybeInject

继续往下看,来到 injector.inject(instance);

到了这一步,就跟以前的Dagger没任何区别了。

 private final class PaoActivitySubcomponentImpl
      implements ActivityModule_ContributePaoActivity.PaoActivitySubcomponent {
    private PaoActivitySubcomponentImpl(PaoActivitySubcomponentBuilder builder) {}

    private PaoRepo getPaoRepo() {
      return new PaoRepo(
          DaggerAppComponent.this.providePaoServiceProvider.get(),
          DaggerAppComponent.this.providePaoDaoProvider.get());
    }

    private PaoViewModel getPaoViewModel() {
      return new PaoViewModel(getPaoRepo());
    }

    @Override
    public void inject(PaoActivity arg0) {
      injectPaoActivity(arg0);
    }

    private PaoActivity injectPaoActivity(PaoActivity instance) {
      PaoActivity_MembersInjector.injectMViewModel(instance, getPaoViewModel());
      return instance;
    }
  }
}

到此,经过分析Dagger-Android的注入过程,我们了解了他们的工作原理。fragment的注入是同样的思路。 最后总结归纳一下:

  • 普通的赋值:viewmodel = ViewModel(Repo())
  • Dagger的注入: instance.viewmodel = component.viewmodel
  • Dagger-Android的注入:instance.viewmodel = map.get(instance.class).getComponent().viewmodel

就一个思路的转变,google的大神真是有心了,搞这么多事。 github:github.com/ditclear/MV…

写在最后

Dagger-Android相比于普通的Dagger确实稍微绕了一些,多了一些设计模式和面向接口,光看源码的话很容易绕晕,特别是在不懂得google大神们的思路的时候。

如果说Dagger的复杂度是5,那么Dagger-Android的复杂程度就是7。

如果能明悟的话,逻辑也是很简单的,然后多进行断点调试。就像解算法题一样,Dagger和Dagger-Android可以算是两种思路吧。

如同写在前面的话里提到的,Dagger适合于那些初中级开发者的团队,比较容易理解。Dagger-Android则更适合实力都较强的开发团队。