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

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


阅读前提

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


之前的作法有问题?

想一想咱们在 2.10 版本以前是怎么在 Android 端使用 Dagger 2 的?是否是相似下面的代码:android

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 文档git

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

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

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

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

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

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


基本使用方法

实际上,新版本的 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。


参考

相关文章
相关标签/搜索