Dagger2在Android平台上的新姿势

前言

最近看Google新的框架sample时android-architecture-components发现了dagger2在Android平台上新的写法,很简洁,值得学习。特意看了看dagger2的官方文档Android部分,讲得很详细也很到位。因为是新姿势,中文的学习资料还比较少,所以我决定翻译一下官方文档。以下是主要是对dagger2 Android部分官方文档的翻译,并增加了一些内容帮助理解。文档其它部分的已经有人翻译过了dagger2官方文档中文。如果你还不了解dagger2,或者不太熟悉dagger2中的subcomponent和multibindings的话(以下内容需要熟悉这些内容才能看懂),可以先看一下dagger2官方文档中文。dagger2的学习曲线还是很陡峭的,官方文档永远是最好的学习资料。

Android Architecture Components是2017年Google I/O 大会上新推出的应用框架,主要用于解决UI组件的生命周期和数据持久化问题,帮助我们轻易地处理配置变化(像屏幕旋转)时数据的存储问题。构建感知生命周期的Observer,防止内存泄漏。非常值得学习。
Architecture Components官方文档
中文翻译

我已经放弃使用dagger.android了,具体原因可以查看当定义Dagger2 Scope时,你在定义什么?

开始

相较于其它的依赖注入框架而言,Dagger2其中一个主要的优势是,它不使用反射来生成其实现。这意味着它可以被用在Android应用上(意思就是不影响APP的性能)。然而,把Dagger2应用在Android平台上,还是有一些点需要注意。

dagger.android

在Android平台上使用Dagger的一个主要的不同是,很多类的实例化依赖于操作系统本身,像是Activity和Fragment,但是Dagger最理想的工作方式是它能够构造所有需要注入的类的实例。所以,你必须在它们(Activity、Fragment等)的生命周期中进行成员的注入。很多类看上去跟下面类似:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //先写如下代码, 否则frombulator可能为null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ...其它代码
  }
}

这么做有以下问题:

  1. 复制粘贴同样的代码使得以后想要重构变得困难。越来越多的这样复制粘贴的代码,开发者反而对这段代码的作用了解的更少。
  2. 更加重要的是,它需要被注入类(FrombulationActivity)知道它的注入类。即使这是通过接口实现的,而不是具体的类。但是,这仍然破坏了依赖注入的的核心原则:一个类不应该对它是如何被注入的有任何的了解。

补充说明:以上这样的代码其实在Android平台上并不常见,更常见的是类似如下的代码:

@Singleton
@Component(modules= AppModule.class)
public interface AppComponent {
    Application getApplication();
    //其它需要暴露出来的类,供dependencies使用
}

@ActivityScope
@Component(dependencies = AppComponent::class, modules= ActivityModule.class)
interface ActivityComponent {
    void inject(YourActivity activity)
}

public class YourActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    DaggerActivityComponent.builder()
        .appComponent(YourApp.getAppComponent())
        .activityModule(new ActivityModule(this))
        .build()
        .inject(this);
    // ...其它代码
  }
}

也就是通过component之间依赖(dependencies)的方式来管理不同component之间的关系;而Google文档上的例子是通过子组件(subcomponent)的方式来管理component间的关系,并且如果想使用本文所介绍的新的姿势,就必须使用子组件的方式,抛弃原来依赖的方式。就管理component之间的关系而言,这两种方式都是可以的,我也一直都是使用依赖的方式来构建不同的component,这么做相较于子组件的方式而言,最大的优势就是简单,直接依赖另外一个component,被依赖的component暴露出相应的类即可。而子组件的方式写起来比较麻烦。关键是还得额外学习subcomponent的是怎么回事,光整明白component就够累的了,还要啥subcomponent。 But,解锁了这篇文章介绍的dagger2和Android结合的新姿势,老司机就需要考虑这么一个问题了,需不需要换个姿势呢?!我个人觉得,还是有必要的,原因如下:

  1. 符合依赖注入的的核心原则:一个类不应该对它是如何被注入的有任何的了解。这个核心原则体现在我们的代码上就是,在Activity、Fragment等类需要注入对象时,可以直接使用@Inject注解一个对象即可,不需要生成component(subcomponent)了,使用更加的方便。
  2. 我们都是有追求的老司机,借此学习一下subcomponent的使用有益无害。
  3. 代码量更少。没有最懒的程序猿,只有更懒得程序猿。

Activity的注入

  1. 在你的Application Component中加入AndroidInjectionModule模块,以提供所有基本类型的绑定。
@Singleton
@Component(modules= {
AndroidInjectionModule.class, 
...
})
public interface AppComponent {
    ...
}

AndroidInjectionModule并没有什么特别的,只是一个普通的module,该module提供了5个Map,Map的key是Android四大组件和Fragment的class对象,Map的value是相应的注入器的工厂方法。

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

    @Multibinds
    abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      fragmentInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends Service>, AndroidInjector.Factory<? extends Service>>
      serviceInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends BroadcastReceiver>, AndroidInjector.Factory<? extends BroadcastReceiver>>
      broadcastReceiverInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends ContentProvider>, AndroidInjector.Factory<? extends ContentProvider>>
      contentProviderInjectorFactories(); 
    
    private AndroidInjectionModule() {}
}
  1. 声明你的subcomponent并且实现接口AndroidInjector<YourActivity>,该subcomponent需要有一个被@Subcomponent.Builder注解的并扩展自AndroidInjector.Builder<YourActivity>的构造器:
@Subcomponent(modules = ...)
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
}
  1. 声明过subcomponent之后,把它通过如下方式加入到主component体系中:定义一个提供该subcomponent builder的module,并且把该module加入到你的AppComponent中。
@Module(subcomponents = YourActivitySubcomponent.class)
abstract class YourActivityModule {
  @Binds
  @IntoMap
  @ActivityKey(YourActivity.class)
  abstract AndroidInjector.Factory<? extends Activity>
      bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
}

@Singleton
@Component(modules = {
AndroidInjectionModule.class,
YourActivityModule.class,...
})
interface AppComponent {}

注意:如果你的subcomponent和它的builder除了第2步中提及的方法或者超类没有其它的内容,你可以用 @ContributesAndroidInjector生成2、3步中的一切。现在不需要步骤2和3,你只需声明一个abstract module,返回你所需的activity(用 @ContributesAndroidInjector注解),可以声明subcomponent需要的其它的module。如果这个subcomponent需要scope注解,也可以声明:

@Module
public abstract class ActivityBulidersModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = {/*subcomponent需要的module*/})
    abstract YourActivity contributeYourActivity();
}

@Singleton
@Component(modules = {
AndroidInjectionModule.class,
ActivityBulidersModule.class,...
})
interface AppComponent {}

@ContributesAndroidInjector注解是dagger-android-2.11中提供的,它会生成如下代码:

@Module(subcomponents = ActivityBulidersModule_YourActivityInjector.YourActivitySubcomponent.class)
public abstract class ActivityBulidersModule_YourActivityInjector {
  private ActivityBulidersModule_YourActivityInjector() {}

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

    @ActivityScope
    @Subcomponent(modules = {/*subcomponent需要的module*/})
    public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
        @Subcomponent.Builder
        abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
    }
}

是不是跟我们自己写的一毛一样,@ContributesAndroidInjector只是帮我们自动生成了一些代码,并没有什么特别的,但前提是第2步没有其它方法或者超类型

  1. 下一步,让你的Application实现HasActivityInjector并且@Inject DispatchingAndroidInjector<Activity>而后从方法activityInjector()(接口HasActivityInjector中的方法)返回:
public class YourApplication extends Application implements HasActivityInjector {
  @Inject 
  DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

  @Override
  public void onCreate() {
    super.onCreate();
    DaggerAppComponent.create()
        .inject(this);
  }

  @Override
  public AndroidInjector<Activity> activityInjector() {
    return dispatchingActivityInjector;
  }
}
  1. 最后,在 Activity.onCreate() 方法中在super.onCreate()之前调用AndroidInjection.inject(this)
public class YourActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
  }
}
  1. 恭喜你!完成了!

如何工作的?

AndroidInjection.inject() 从Application中获取了一个 DispatchingAndroidInjector<Activity>,并把activity实例传入方法 inject(Activity)中。 DispatchingAndroidInjector 根据activity的class来查找 AndroidInjector.Factory(即 YourActivitySubcomponent.Builder),创建 AndroidInjector (即YourActivitySubcomponent), 然后把你的activity实例传入方法 inject(YourActivity)中。

更多关于实现的原理请查看我的另外一篇文章Dagger2在Android平台上的新魔法

Fragment注入

注入Fragment就跟注入Activity一样。以相同的方式定义subcomponent,把Activity类型替换为Fragment,@ActivityKey替换为@FragmentKey,HasActivityInjector替换为HasFragmentInjector。
和Activity在onCreate()中注入不同,Fragment的注入在方法onAttach()中。

和为Activity添加module不同,在Fragment中你可以选择在哪添加module。你可以把你的Fragment的subcomponent声明为另一个Fragment component的子component,或者是Activity component的子component,或者是Application component的子component——这取决于你想在哪注入你的subcomponent。在决定了Fragment subcomponent是哪个component的子component之后,让相应的类型实现接口HasFragmentInjector。

原文说的很绕,其实上面一段话的核心意思就是,Fragment的subcomponent可以是Fragment、Activity、Application component(或subcomponent)的subcomponent,只要其对应的类型(Fragment、Activity、Application)实现了HasFragmentInjector接口即可。作为对比,Activity则只能是Application component的subcomponent,所以只能是Application实现HasActivityInjector接口。

例如,你的Fragment subcomponent是YourActivitySubcomponent的子component。你的代码类似于此:

public class YourActivity extends Activity
    implements HasFragmentInjector {
  @Inject 
  DispatchingAndroidInjector<Fragment> fragmentInjector;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    // ...
  }

  @Override
  public AndroidInjector<Fragment> fragmentInjector() {
    return fragmentInjector;
  }
}

public class YourFragment extends Fragment {
  @Inject SomeDependency someDep;

  @Override
  public void onAttach(Activity activity) {
    AndroidInjection.inject(this);
    super.onAttach(activity);
    // ...
  }
}

@Subcomponent(modules = ...)
public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourFragment> {}
}

@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
  @Binds
  @IntoMap
  @FragmentKey(YourFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment>
      bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
}

@Subcomponent(modules = { YourFragmentModule.class, ... }
public interface YourActivityOrYourApplicationComponent { ... }

同样我们可以简写:

@Module
public abstract class FragmentBuildersModule {
    @ContributesAndroidInjector
    abstract YourFragment contributeYourFragment();
}

Support libraries

对于使用Android support library的用户,有dagger.android.support提供了支持。注意用支持库中的Fragment,应该绑定AndroidInjector.Factory<? extends android.support.v4.app.Fragment>,但是仍应该实现AndroidInjector.Factory<? extends Activity> 而不是 <? extends AppCompatActivity> (或者 FragmentActivity)。dagger.android.support中有AndroidSupportInjectionModule,提供了对android.support.v4.app.Fragment的支持:

@Module(includes = AndroidInjectionModule.class)
public abstract class AndroidSupportInjectionModule {
  @Multibinds
  abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      supportFragmentInjectorFactories();

  private AndroidSupportInjectionModule() {}
}

如何获取

在你的build.gradle中增加

dependencies {
  compile 'com.google.dagger:dagger-android:2.x'
  compile 'com.google.dagger:dagger-android-support:2.x' // 如果你使用support libraries
  annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
}

参考:
Dagger&Android

推荐阅读更多精彩内容