Android 依赖注入可以更简单 —— 新版本 Dagger 2 使用教学

4,605 阅读10分钟

今年 3 月 21 号 Dagger 2 在 2.10 版本之后针对 Android 方面做了很大的优化,使用方法也随之有了不少变化。本次改动除了让 Dagger 2 的使用更加符合控制反转原则,还针对 Android 端做出了专门的优化(即所谓 dagger.android) —— 大大简化了 Android 端 Dagger 2 的使用。现在,不妨随着本文一起探寻新版本 Dagger 2 的基本使用方法。


阅读前提

要注意阅读本文内容前最好对 2.10 版本前的 Dagger 2 比较熟悉,至少要明白它的依赖注入管理机制,最好是实际项目中使用过,不然阅读时会比较迷茫。当然,一切的 Dagger 2 的说明文章很多,善用搜索引擎就可以找到不错的教程,这里就不再赘述了。
另外,本文的 demo 项目中用到了 Google Samples 项目 Android Architecture 使用的 MVP 实现方式,这个也最好有所了解。主要是了解它的 todo-mvp-dagger 分支的结构组织方法即可,至少要明白它的功能层次和组织结构。


以前的做法有问题?

想想我们在 2.10 版本之前是怎么在 Android 端使用 Dagger 2 的?是不是类似下面的代码:

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

代码例子取自 Google 官方的 Dagger 2 文档

emmmmm,好像也没什么问题啊?

不,其实这段代码大有问题!

第一点,代码重复度过高:我们要在几乎每一个需要注入依赖项的 Activity 和 Fragment 里面来一套这样的代码。这是因为我们在 Android 编程中要用到的 Activity、Fragment 等类是继承自系统的,同时生命周期都由系统管理,所以使用 Dagger 2 的时候就不得不进行手动的处理,也就只有在纯粹自己写的 Java 类中使用 Dagger 2 才感觉更舒服一些。

第二点,违反了控制反转原则:原有的用法使得被注入的目标类必须要了解注入管理工具的详细信息,才能让注入工作顺利进行。即使可以通过接口使得实际代码中不必书写太多的实际类名,但这仍然造成了严重的目标类和管理类的紧耦合。

其实,我们以前在使用 Dagger 2 的时候为了解决重复问题也是使用 Live Template 或者设计一个通用的工具函数;而为了保证注入目标类正常工作和注入管理正常进行,就必须在设计业务代码的时候并行设计注入管理代码。
幸好,2.10 版本针对 Android 系统做出了很大的优化,甚至单独做了 Android 方面的依赖注入管理库,提供了针对 Android 的注解和辅助类大大减少了要写的代码。

下面我就用一个简单的 demo 讲解一下新版本的 Dagger 2 的基本使用方法,项目地址 请戳这里。注意项目有两个分支,master 分支用来演示基本使用方法,simplify 分支演示简化用法。


基本使用方法

实际上,新版本的 Dagger 2 可以有多种使用方式,需要用户构建不同数量的接口、抽象类,但我们先不将最简化的使用方式,因为用基本但复杂的方法更好理解 Dagger 2 的使用逻辑和结构。
要使用基本的功能,只需要在 app 的 build.gradle 文件中加入下列依赖代码:

implementation 'com.google.dagger:dagger:2.13'
implementation 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-compiler:2.13'

Application 注入管理

Application 的注入基本没有什么变化,更多的是 Dagger 2 官方建议最好使用 Builder 模式构建 Component,方便灵活的向 Component 中添加构建属性,比如:

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {

    void inject(MyApplication application);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }
}

Module 和旧版相比没什么太大变化:

@Module
public class AppModule {

    @Singleton
    @Provides
    Context provideContext(Application application) {
        return application;
    }
}

而一般在调用数据库或存取 SharedPreferences 文件时,常常用到 Context,一般会提供 Application 而非 Activity 等。

Activity 注入管理

第一,是方便管理多级 Component,要求在最顶层的即 Application 的 Component 中引入 AndroidInjectionModuleAndroidSupportInjectionModule,代码形式如下:

@Singleton
@Component(modules = {AppModule.class,              AndroidSupportInjectionModule.class})
public interface AppComponent {
    // ...
}

@BindInstance 注解方便在 Builder 中加入设置项,传入要求的实例就能设置 Builder 中的对应属性。

第二,Activity 的 Component(也可以是某个功能模块的 Component)用 @Subcomponent 注解而非 @Component,同时它还要继承 AndroidInjector<T>(T 一般为对应的 Activity)。当然,如果有对应的 Module 也不要忘记在注解中用 moduls 添加。比如:

@ActivityScoped
@Subcomponent(modules = MainActivityModule.class)
public interface MainActivityComponent extends AndroidInjector<MainActivity> {

    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainActivity> {
    }
}

此处 的 Builder 可继承 AndroidInjector.Builder<T>,必须对应 @Subcomponent.Builder 注解的抽象类,方便生成 Builder 代码。

第三,要在第二步中的 Subcomponent 的父 Module 中用注解标记出 Subcomponents 的内容。
可以把 AppModule 当作父 Module

@Module(subcomponents = {MainActivityComponent.class})
public class AppModule {

    @Singleton
    @Provides
    Context provideContext(Application application) {
        return application;
    }
}

也可以另外写一个 `ActivityBindingModule 当作父 Module

@Module(subcomponents = {MainActivityComponent.class, DummyActivityComponent.class})
public abstract class ActivityBindingModule {
    // ...
}

相对来说,subcomponents 写在哪里其实并不重要,ActivityBindingModule 也不止这个上面一个作用,但个人认为写到这里更方便管理。

第四,绑定 Subcomponent 的 Builder 到 AndroidInjector.Factory<T>,方便 Dagger 2 借助正确的 Builder 类型生成对应的 AndroidInjector 代码,为方便管理最好写到一个统一的 Module 中:

@Module(subcomponents = {MainActivityComponent.class, DummyActivityComponent.class})
public abstract class ActivityBindingModule {

    @Binds
    @IntoMap
    @ActivityKey(MainActivity.class)
    abstract AndroidInjector.Factory<? extends Activity> bindMainActivityInjectorFactory(
            MainActivityComponent.Builder builder);
}

第五,把第四不得 Module 信息加入到 Application 的 Component 注解:

@Singleton
@Component(
        modules = {AppModule.class, ActivityBindingModule.class,
                AndroidSupportInjectionModule.class}
)
public interface AppComponent {

    // ...
}

注意:我这里没有采用官方文档的写法,使用 Activity 的 Module 来管理 subcomponents 属性,以及绑定 Builder。因为本文描述的方案逻辑上更好理解,也能更集中地管理相似的代码,这也是从 Google Samples 演示项目 Android Architecture 学到的方法。

Fragment 注入管理

如果需要对 Fragment 设置单独的注入管理类,那么可以参考 Activity 的方式,不同的是父类的 Module 信息要放到 Activity 的 Component 注解中,所有管理类信息如下(本例中也用到了简单的 MVP 结构):

DummyActivity 相关:

@ActivityScoped
@Subcomponent(modules = {DummyActivityModule.class, DummyFragmentBindingModule.class})
public interface DummyActivityComponent extends AndroidInjector<DummyActivity> {

    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<DummyActivity> {
    }
}
/*************************************/
@Module
public class DummyActivityModule {

    @ActivityScoped
    @Provides
    DummyContract.Presenter provideDummyPresenter(DummyPresenter presenter) {
        return presenter;
    }
}

DummyFragment 相关:

@Module(subcomponents = DummyFragmentComponent.class)
public abstract class DummyFragmentBindingModule {

    @Binds
    @IntoMap
    @FragmentKey(DummyFragment.class)
    abstract AndroidInjector.Factory<? extends Fragment> bindDummyFragment(
            DummyFragmentComponent.Builder builder);
}
/*************************************/
@FragmentScoped
@Subcomponent(modules = DummyFragmentModule.class)
public interface DummyFragmentComponent extends AndroidInjector<DummyFragment> {

    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<DummyFragment> {
    }
}
/*************************************/
@Module
public class DummyFragmentModule {

    @FragmentScoped
    @Provides
    DummyContract.View provideDummyView(DummyFragment fragment) {
        return fragment;
    }

    // ...
}

本例子中的 MVP 实现方式,参考了 Google Samples 的 Android Architecture 的 todo-mvp-dagger 分支,即把 Activity 仅仅当作 Fragment 的管理容器,Fragment 作为 MVP 中的 View 角色来对待。

完成注入

第一,Applicaiton 要实现接口 HasActivityInjector,用来实现自动管理 Activity 的 Injector,具体实现方法是固定不变的:

public class MyApplication extends Application implements HasActivityInjector {

    @Inject
    DispatchingAndroidInjector<Activity> mInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().application(this).build().inject(this);
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return mInjector;
    }
}

第二,无需管理 Fragment 的 Injector 的 Activity 直接注入自己需要的依赖即可:

public class MainActivity extends BaseActivity {
    // ...
     @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

第三,需要管理 Fragment 的 Injector 的 Activity 需要实现接口 HasSupportFragmentInjector,方式类似第一步:

public class DummyActivity extends BaseActivity
        implements HasSupportFragmentInjector {
    @Inject DispatchingAndroidInjector<Fragment> mInjector;
    // ...
    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dummy_layout);
        // ...
    }
    // ...

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

第四,对 Fragment 注入依赖:

public final class DummyFragment extends BaseFragment {
    // ...
    @Override
    public View onCreateView(
            @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        AndroidSupportInjection.inject(this);
        View view = inflater.inflate(R.layout.fragment_dummy_layout, container, false);
        unbinder = ButterKnife.bind(this, view);
        return view;
    }
    // ...
}

简化用法

仔细观察上面的例子,我们应该能发现其实新版本的 Dagger 2 并没有更改原来的设定:仍然使用 Component 作注入器,使用 Module 作依赖提供者,也仍然使用多层级树形 Component 来统合管理整个项目的依赖,同时 @Quilifier@Scope 的作用也没变。仅仅只是把每个 Activity 和 Fragment 的注入代码精简了,无需知道注入的详细细节了。
但是,仅仅如此的化,你一定会很疑惑:这个改法确实是更符合依赖反转原则了,可实在也没节省多少代码啊?别急,基本用法一般只是用来了解原理的,实际使用不会这么干的!

简化管理类

首先,我们要考虑到底是什么代码重复最多,最容易使用工具生成呢?当然是 @Subcomponent 标注的代码!因为上面的例子中我们可以看出它们的功能相对较为简单,只是为了构建一个树形结构方便管理,所以大部分编程情景下这部分的代码其实没有什么额外的功能。而 Dagger 2 也贴心的提供了简化的方案:
只要 @Subcomponent标注的 Component 类满足以下条件,就能简化:

  • 除了对应的 Builder 外没有其他的方法
  • Builder 类也不会添加其他的方法去自定义构建属性

在简化之前,要先在 app 的 build.gradle 文件中添加下述依赖:

annotationProcessor 'com.google.dagger:dagger-android-processor:2.13'

然后,就能使用 APT 工具自动生成相应的 @Subcomponent 代码,比如 Activity 的 Component 就无需再写,而是在 ActivityBindingModule 中写入如下的代码:

@Module
public abstract class ActivityBindingModule {

    @ActivityScoped
    @ContributesAndroidInjector(modules = MainActivityModule.class)
    abstract MainActivity bindMainActivity();

    @ActivityScoped
    @ContributesAndroidInjector(
            modules = {DummyActivityModule.class, DummyFragmentBindingModule.class}
    )
    abstract DummyActivity bindDummyActivity();
}

而 Fragment 的 @Subcomponent 注解也如法炮制:

@Module
public abstract class DummyFragmentBindingModule {

    @FragmentScoped
    @ContributesAndroidInjector(modules = DummyFragmentModule.class)
    abstract DummyFragment bindDummyFragment();
}

注意DummyFragmentBindingModule 的注解信息必须加到同 DummyActivityModule 一样的地方,即本例中的 bindDummyActivity() 的注解中。

这样,即使 Activity 大量增加也不用写大量没什么变化的 Component 代码了。当然 Module 还是需要的,只不过也可以有部分的简化。

比如 Applicaiton 的 Module 使用 @Binds 把 Application 实例和 Context 进行绑定:

@Module
public abstract class AppModule {

    @Binds
    abstract Context provideContext(Application application);
}

同理,在 MVP 中如果需要提供 Presenter 接口也可以使用这个办法,比如:

@Module
public abstract class DummyActivityModule {

    @ActivityScoped
    @Binds
    abstract DummyContract.Presenter bindDummyPresenter(DummyPresenter presenter);
}

只不过这个办法只能把传入的实例和返回类型进行绑定,其他复杂的依赖提供方法(比如需要利用传入参数手动实例化,或进行条件判断)还是不能简化的。(这不是废话吗...)

简化注入操作

注入操作很明显可以简化,毕竟模式完全相同,简化的方法就是提供了模板父类 DaggerApplicationDaggerAppCompatActivityDaggerFragment,然后让需要注入操作的类继承它们即可。
最后,Application 的代码就如下所示:

public class MyApplication extends DaggerApplication {

    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        return DaggerAppComponent.builder().application(this).build();
    }
}

而 Activity 和 Fragment 则最好是让 BaseActivityBaseFragment 去继承上面提到的两个模板父类,在一般的代码中自只需要用 @Inject 标出需要注入的元素即可,无需任何额外操作。这样,在编写 Activity 和 Fragment 的时候无需考虑依赖注入的细节,只要按照正常流程编写代码,然后不断检查、测试代码,不断标记出需要注入的元素即可。

最后,再次提醒相关代码不可能全部演示出来,可以去 Dagger 2 Android Demo 查看具体细节,尤其简化部分的代码重复内容较多文章不作赘述,需要的可以自行查看 simplify 分支。另外,如果项目有 bug 也欢迎直接提出 issue。


参考