基于MVP模式的 dagger-android 探索

近期接手了公司一个项目的重构,要基于MVPArms框架来做,而arms又是基于dagger2构建;但是dagger2直接用于android的话用起来还是有些不太舒服,仗着自己对dagger有些了解,花了些时间把arms框架的dagger部分替换成了dagger-android。重构期间也遇到了一些问题,修修补补最后还算成功,对于dagger的理解也更深了一点;为了帮助自己记忆开始写这篇文章,同时也在这里分享给大家。

关于dagger2网上已经有了不少优秀的文章,我这里就直接从dagger-android开始讲起了,如果还对dagger2不太了解,建议先看看这个系列的文章Dagger2完全解析,个人认为非常不错。

1. 一些概念

首先我们来回顾一下dagger三要素

  • 目标类: 需要进行依赖注入的类,即依赖于一些其他类实例的类。

  • module: 准确地说应该是依赖,用于为目标类提供依赖。我们可以通过创建一个module手动new对象对外提供依赖,也可以通过@inject注解类的构造器让dagger自动创建对象提供依赖。

  • commponent: 翻译过来是组件的意思,在dagger中充当ioc容器。我们知道component可以依赖一个或多个module,然后它一般还需要有一个inject方法,类似这样:

    @Component(modules = {
            xxxModule.class,
            xxxModule.class
    })
    public interface AppComponent {
        void inject(App app);
    }
    

    那么它的作用就显而易见了——component作为一个容器,封装了它依赖的所有module提供的创建对象的方式。经过dagger的编译以后,我们在目标类调用component实现类的inject方法将它提供的依赖注入进来,之后就可以通过@inject注解从component里面拿到我们所需的对象了。

2. 如何构建依赖关系

我们知道一个android项目必定有application,还可能有activity、fragment、service...这些组件,通常我们会在application(或者单例类)里面保存一些全局变量,以便在其他地方使用;而在activity中也可能需要暴露出共有变量给属于它的fragments使用(比如在有多个fragment的tab页面中),这样的话我们不得不通过get或者setArguments方法传递这些对象,非常地不优雅。下面我们来看看dagger的方式。

2.1 回顾dagger的构建方式

现在我们从dagger的角度来看,我们可以把整个app看作一个根组件,创建一个appComponent;activity看作它的子组件,然后依次创建xxxActivityComponent,将它声明为appComponent的子组件;

@Component(modules = {ActivityModule.class, ...})
public interface AppComponent {...}  

@Module(subcomponents = {MainComponent.class, ...})
public abstract class ActivityModule {...}  

@ActivityScope
@Subcomponent
public interface MainComponent {
    void inject(MainActivity activity);

    @Subcomponent.Builder
    interface Builder {
        @BindsInstance
        Builder view(MainContract.View view);

        MainComponent build();
    }
}  

然后在每个activity里面注入它自己的component

App.get(this)
    .getAppComponent()
    .mainComponentBuilder()
    .view(this)
    .build()
    .inject(this);

@BindsInstance: 由于项目用的是MVP模式,需要给presenter提供view实例,这里的view实际上就是activity,它只能通过我们在注入的时候传入(我们无法实例化activity),而@BindsInstance注解的view方法就是把activity作为view实例放到容器里了。

以上这些看起来似乎没什么问题,但是要知道我们一个项目里可能很多个activity,每个都需要这样去注入的话是非常麻烦的,而且这种注入方式看起来并不是一目了然。

下面我们来看看dagger.android的注入方式。

2.2 dagger.android的构建方式

首先build.gradle中加入依赖包

implementation "com.google.dagger:dagger:$version_dagger2"
annotationProcessor "com.google.dagger:dagger-compiler:$version_dagger2"
implementation "com.google.dagger:dagger-android:$version_dagger2"
implementation "com.google.dagger:dagger-android-support:$version_dagger2"
annotationProcessor "com.google.dagger:dagger-android-processor:$version_dagger2"

接下来直接看看最终如何在目标类中的注入依赖

AndroidInjection.inject(四大组件或者fragment)
AndroidSupportInjection.inject(v4包下的fragment)

注意,需要BroadcastReceiver中super.onReceive()之前调用, fragment super.onAttach()之前,其他三大组件super.onCreate()之前调用。

如此简单!不再需要显式地在目标类中指明要注入哪个component,我们甚至能把它放到基类中就可以完成注入。这是怎么做到的呢?我们待会再来细说,再此之前我们还需要做一些别的事情。

  1. AppComponent需要做一些调整

    @Component(modules = {
            AndroidInjectionModule.class,
            AndroidSupportInjectionModule.class,
            ActivityModule.class
    })
    public interface AppComponent {...}
    

    AndroidInjectionModule, AndroidSupportInjectionModule分别是为了保证四大组件和fragment能够被注入到容器中,后者是为了支持v4包下的fragment。

  2. 声明activity对应的子component方式需要改变

     @ActivityScope
     @Subcomponent(modules = {MainModule.class})
     public interface MainComponent extends AndroidInjector {
    
         @Subcomponent.Builder
         abstract class Builder extends AndroidInjector.Builder {}
     }  
    
     @Module
     public abstract class MainModule {
         @ActivityScope
         @Binds
         abstract MainContract.View provideView(MainActivity activity);
     }
    

    子component需要继承AndroidInjector<四大组件或者fragment>接口,同时builder也需要继承AndroidInjector.Builder<四大组件或者fragment>。AndroidInjector是干吗用的?我们可以把它理解为注入器,有将commponent注入到目标类能力的东西,但它只是一个接口,具体的实现在子类(就是我们的component的实现类,由dagger编译后生成,代码位于build/source/apt/.../DaggerAppComponent.java)。

    你可能发现我们少了一些东西,我们并没有像之前那样把activity作为MVP中的view传入component,而是直接在module里面直接拿到了activity,那么这个activity是从哪里来的呢?还记得AndroidInjection.inject(xxxActivity)吗,当我们在给activity注入component的时候,同时也将activity本身传入进去了,所以我们可以直接用它。

  3. 需要告诉dagger如何创建一个子component
    首先我们创建一个ActivityModule,用来管理所有activity对应的component

     @Module(subcomponents = MainComponent.class)
     public abstract class ActivityModule {
         @Binds
         @IntoMap
         @ActivityKey(MainActivity.class)
         abstract AndroidInjector.Factory  
         bindMainActivity(MainComponent.Builder builder);
     }
    

    这段代码实际上是告诉dagger MainActivity所对应的component是哪一个,简单点说,就是将activity的具体类型作为key,它对应的component的创建方式(AndroidInjector.Factory,也就是component的工厂对象)作为值存到一个map里面,然后我们通过AndroidInjection.inject(xxxActivity)注入时,dagger就能通过传入的类型去拿到对应的工厂实例,创建component然后注入进来。当然,如果我们需要注入的是其他组件,那么@ActivityKey就应该换成对应的xxxKey了。

  4. 在application里面注入根component

    public class App extends Application implements HasActivityInjector {
       @Inject
       DispatchingAndroidInjector actInjector;
    
       @Override
       public void onCreate() {
           super.onCreate();
           DaggerAppComponent.builder()
                   .appModule(new AppModule(this))
                   .build()
                   .inject(this);
       }
    
       @Override
       public AndroidInjector activityInjector() {
           return actInjector;
       }
    }
    

    如果我们要用dagger.android的方式在activity中注入依赖,我们的application就需要实现HasActivityInjector接口,它只有一个抽象方法,需要返回一个AndroidInjector的实例,而AndroidInjector本身是一个接口。这时候我们通过@inject注入一个DispatchingAndroidInjector进来就好了(如果需要用到其他组件或者fragment,也要实现对应的HasXXXInjector接口),最后别忘了注入根component。

    那么这个DispatchingAndroidInjector有什么作用?它从哪里来到哪里去呢?我们来看一段它的源码。

    @Beta
    public final class DispatchingAndroidInjector implements AndroidInjector {
       ...
       private final Map, Provider>>
           injectorFactories;
    
       @Inject
       DispatchingAndroidInjector(
           Map, Provider>> injectorFactories) {
         this.injectorFactories = injectorFactories;
       }
    }
    

    到这里我们应该能马上明白了,它的构造方法被@inject注解了,所以可以直接被注入。而它内部保存了一个map,在构造器中被注入赋值。其实这个map就是之前我们在上一节讲过的,保存了每个activity(或其他组件)对应的component的工厂实例。这个map又是从哪来的呢?答案就在AndroidInjectionModule里,还记得吗,我们在AppComponent中安装了它,现在来看一看它的代码。

     @Beta
     @Module
     public abstract class AndroidInjectionModule {
       @Multibinds
       abstract Map, AndroidInjector.Factory>
           activityInjectorFactories();
    
       @Multibinds
       abstract Map, AndroidInjector.Factory>
           fragmentInjectorFactories();
           ......
     }
    

    一目了然,它直接对AppComponent提供了装有各种component工厂实例的map。

    @Multibinds 这个注解需要结合之前创建的ActivityModule来看

    @Binds
    @IntoMap
    @ActivityKey(MainActivity.class)
    abstract AndroidInjector.Factory  
    bindMainActivity(MainComponent.Builder builder);
    

    @Binds 把builder转化为它的父类AndroidInjector.Factory,然后@IntoMap 将它绑定到@Multibinds 注解的AndroidInjectionModule中提供Map类型依赖的方法,这样就根据分类将每个子component的工厂实例放入一个Map中,然后作为依赖提供出去。

3. 更优雅的方式

写了写么多东西,看起来dagger.adroid的方式好像并没有好到哪里去,只是避免了在每个目标类中写一长串的注入代码,但是别急,我们还有终极大招。
  先来看看最终代码

@Module
public abstract class ActivityModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = MainModule.class)
    abstract MainActivity provideMainActivity();
}

ActivityModule直接简化成了这个样子,而且我们不再需要MainComponent(不再需要显式地创建子Component),子component依赖的module现在直接声明在@ContributesAndroidInjector里面就好了。

@ContributesAndroidInjector 做了什么呢?其实一切并没有变,它只是隐式地帮我们创建了一个子component,看看编译后的代码build/source/apt/.../ActivityModule_ProvideMainActivity

@Module(
  subcomponents = ActivityModule_ProvideMainActivity.MainActivitySubcomponent.class
)
@Generated("dagger.android.processor.AndroidProcessor")
public abstract class ActivityModule_ProvideMainActivity {
  private ActivityModule_ProvideMainActivity() {}

  @Binds
  @IntoMap
  @ActivityKey(MainActivity.class)
  abstract AndroidInjector.Factory bindAndroidInjectorFactory(
      MainActivitySubcomponent.Builder builder);

  @Subcomponent(modules = MainModule.class)
  @ActivityScope
  public interface MainActivitySubcomponent extends AndroidInjector {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder {}
  }
}

看吧,一切并没有变。

4. 从源码理清思路

知其然不知其所以然往往容易犯错,上一部分的结尾我们分析了DispatchingAndroidInjector的一些东西,现在我们来跟着源码从头理一理思路。
  还是从AndroidInjection.inject(xxx)开始,我们点进源码

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()));
   }

   AndroidInjector activityInjector =
       ((HasActivityInjector) application).activityInjector();
   checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());

   activityInjector.inject(activity);
 }

很简单,判断application是否实现了HasActivityInjector接口,然后调用application的activityInjector函数拿到AndroidInjector实例(正是我们之前在applicaton中@inject进去的DispatchingAndroidInjector),最后调用了DispatchingAndroidInjector的inject方法,我们接着往下面看,回到DispatchingAndroidInjector

@Beta
public final class DispatchingAndroidInjector implements AndroidInjector {
  ......
  @Override
  public void inject(T instance) {
    boolean wasInjected = maybeInject(instance);
    if (!wasInjected) {
      throw new IllegalArgumentException(errorMessageSuggestions(instance));
    }
  }  

  @CanIgnoreReturnValue
  public boolean maybeInject(T instance) {
    Provider> factoryProvider =
        injectorFactories.get(instance.getClass());
    if (factoryProvider == null) {
      return false;
    }

    @SuppressWarnings("unchecked")
    AndroidInjector.Factory factory = (AndroidInjector.Factory) factoryProvider.get();
    try {
      AndroidInjector injector =
          checkNotNull(
              factory.create(instance), "%s.create(I) should not return null.", factory.getClass());

      injector.inject(instance);
      return true;
    } catch (ClassCastException e) {
      throw new InvalidInjectorBindingException(
          String.format(
              "%s does not implement AndroidInjector.Factory<%s>",
              factory.getClass().getCanonicalName(), instance.getClass().getCanonicalName()),
          e);
    }
  }
}

我们直接看最终调用的maybeInject(),它根据我们传入的目标类的class从map中拿到对应的component(我们的子component全部实现了AndroidInjector接口,所以这里的injector就是component)的工厂实例,然后创建了component,最后调用它的inject方法将依赖注入到目标类的实例里面。具体的注入过程可以在dagger为我们生成的xxxComponentImpl里面看到,它是DaggerAppComponent的内部类。

现在来总结一下,首先AndroidInjector接口只有一个inject抽象方法,我们的子component和DispatchingAndroidInjector都是它的子类,当通过AndroidInjection.inject(xxx)注入的时候,实际上是调用application里的DispatchingAndroidInjector的inject方法,而它本身并不做实际的注入,只是起了一个分发的作用,最后找到并调用了当前子component(injecttor)的inject方法进行实际的依赖注入。

5. 结合实例

demo地址:https://github.com/ColorfulHorse/MVPDemo

demo很简单,主要就是一个mainActivity通过viewPager管理了三个fragment,weeklyFragment里面请求了一个七天的天气接口,列表展示了出来,通过数据库做了本地缓存。
看看AppComponent

@Singleton
@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        OkhttpModule.class,
        RetrofitModule.class,
        ApiServiceModule.class,
        DBModule.class,
        ActivityModule.class})
public interface AppComponent {
    void inject(App app);

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

依赖了若干module,分别提供okhttpClient实例,retrofit实例,retrofitService实例,数据库实例;这里数据库用的是ObjectBox库,有兴趣的可以了解一下。
其实也不需要创建这么多module,也可以全部放到一个appModule里面就可以,我这里只是做了一个分类,具体情况视项目复杂度而定。

再看看AcrivityModule

@Module
public abstract class ActivityModule {

    @ActivityScope
    @ContributesAndroidInjector(modules = {
            MainModule.class,
            MainFragmentModule.class
    })
    abstract MainActivity provideMainActivity();

    @ContributesAndroidInjector
    @ActivityScope
    abstract ForecastActivity provideForecastActivity();
}

管理了一个列表activity和一个详情activity, MainActivity有一个MainFragmentModule管理其下的三个fragment

@Module
public abstract class MainFragmentModule {

    @FragmentScope
    @ContributesAndroidInjector
    abstract DailyFragment provideDailyFragment();

    @FragmentScope
    @ContributesAndroidInjector(modules = WeeklyModule.class)
    abstract WeeklyFragment provideWeeklyFragment();

    @FragmentScope
    @ContributesAndroidInjector
    abstract MonthlyFragment provideMonthlyFragment();

}

这样看起来整个结构就比较明朗,依赖关系层次明显,appComponent为根组件,其下activityComponent为子组件,再下面还有fragment对应的子组件。当然这一切只是一个简单的示例,也许还有更好的方式等你探索。

本文到这里也就结束了,更多的东西demo里面都写得比较完整,就不再细讲。
如有不足之处欢迎指出。

你可能感兴趣的:(基于MVP模式的 dagger-android 探索)