近期接手了公司一个项目的重构,要基于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,我们甚至能把它放到基类中就可以完成注入。这是怎么做到的呢?我们待会再来细说,再此之前我们还需要做一些别的事情。
-
AppComponent需要做一些调整
@Component(modules = { AndroidInjectionModule.class, AndroidSupportInjectionModule.class, ActivityModule.class }) public interface AppComponent {...}
AndroidInjectionModule, AndroidSupportInjectionModule分别是为了保证四大组件和fragment能够被注入到容器中,后者是为了支持v4包下的fragment。
-
声明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本身传入进去了,所以我们可以直接用它。 -
需要告诉dagger如何创建一个子component
首先我们创建一个ActivityModule,用来管理所有activity对应的component@Module(subcomponents = MainComponent.class) public abstract class ActivityModule { @Binds @IntoMap @ActivityKey(MainActivity.class) abstract AndroidInjector.Factory extends Activity> bindMainActivity(MainComponent.Builder builder); }
这段代码实际上是告诉dagger MainActivity所对应的component是哪一个,简单点说,就是将activity的具体类型作为key,它对应的component的创建方式(AndroidInjector.Factory extends Activity>,也就是component的工厂对象)作为值存到一个map里面,然后我们通过
AndroidInjection.inject(xxxActivity)
注入时,dagger就能通过传入的类型去拿到对应的工厂实例,创建component然后注入进来。当然,如果我们需要注入的是其他组件,那么@ActivityKey就应该换成对应的xxxKey了。 -
在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 extends Activity>> activityInjectorFactories(); @Multibinds abstract Map , AndroidInjector.Factory extends Fragment>> fragmentInjectorFactories(); ...... } 一目了然,它直接对AppComponent提供了装有各种component工厂实例的map。
@Multibinds
这个注解需要结合之前创建的ActivityModule来看@Binds @IntoMap @ActivityKey(MainActivity.class) abstract AndroidInjector.Factory extends Activity> 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 extends Activity> 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里面都写得比较完整,就不再细讲。
如有不足之处欢迎指出。