Dagger 应该是这么多开源项目最难用的,好在有了Dagger.android(理解起来还是很拗口)
那就直接看源码吧。
Demo On GitHub(MVP-Dagger2-Rxjava2)
后台开发Spring很早很早就有依赖注入,Dragger(2) 出现后大型的Android 项目开发依赖管理也美好了。本文主要是总结使用dagger.android不是dagger,如有错误还请大神指导。
虽然dagger很强大,以前在学习使用Dagger2的时候感觉理解绕,相似的模版代码还很多,哪里需要注入还要写DaggerXXX..inject(this);而且也违背依赖注入的核心原则:一个类不应该知道如何实现依赖注入;它要求注射类型知道其注射器, 即使这是通过接口而不是具体类型完成的。
//那么多个Activity,到处都要写这种代码你不会觉得烦吗😡
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
谷歌大神们又研究出一套专门用于Android的注入方式,拓展Dagger2.android https://google.github.io/dagger//android.html,用了以后你简单的配置后加上下面的代码就可以处处依赖注入了。
AndroidInjection.inject(this); //一处声明,处处依赖注入
开始使用
首先在Gradle 中引入依赖,目前dagger-android 还是Beta版本,建议及时更新最新
compile 'com.google.dagger:dagger-android:2.13'
compile 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.13'
为什么不用Dagger2,要等Dagger2.android 出来才用
- 项目规模小,即使使用Dagger2 也不能显著提高生产力
- dagger 难以上手,项目组可能难以推广,或者都是Copy
- 违背依赖注入核心原则,模版代码多
dagger2.Android 的出现很大程度的解决/缓解了以上问题
在项目中快速使用
建议小范围或demo 大胆使用,修改添加一些东西慢慢尝试,注释都在代码里面;基本上的根据已有的规则慢慢熟悉就回了,demo 链接 https://github.com/AnyLifeZLB/MVP-Dagger2-Rxjava2,都在代码里详细的注释了。
此处需要一张图
下面是结合Demo翻译官方的链接 https://google.github.io/dagger//android.html
Dagger 2相比比其他大多数依赖注入框架的主要优势之一就是其严格的代码生成实现(无反射)意味着它可以在Android应用程序中使用。然而,在Android开发中使用Dagger的时候还是有一些注意事项。
哲学问题 Philosophy
虽然编写Android 程序也是使用Java,但在风格方面通常是很不一样的。通常情况下,这种差异是为了适应移动平台独特的 性能考虑。
而且,通常应用于Android的许多模式与Java的模式也不太一样。甚至很多Effective Java这本书上的建议 对于Android开发来说都是不合适的。
为了达到符合习惯和可移植代码的目标,Dagger依靠ProGuard来后处理编译的字节码。这使得Dagger能够在服务器和Android开发生成的源代码看起来和感觉都是非常的相近而又自然,同时使用不同的工具链来产生在两个环境中都能有效执行的字节码。此外,Dagger有一个明确的目标,确保它生成的Java源代码与ProGuard优化保持一致(也就是dagger 生产的代码都支持Produard 的混淆)。
当然,并不是所有的问题都可以用这种方式来解决,但它是提供Android特定兼容性的主要机制。
咱们长话短说
所以在Android开发中使用Dagger最好你也使用ProGuard
推荐的ProGuard设置 Recommended ProGuard Settings
注意在ProGuard设置有对应的考虑你使用了Dagger。
Watch this space for ProGuard settings that are relevant to applications using Dagger.
让Dagger2飞起来的东西- dagger.android
在还没有dagger.android 的时候估计大家都会感觉使用dagger有点繁琐和困难。
在Android开发中使用Dagger的主要困难是之一是许多Android框架类是由操作系统本身进行实例化的,像 Activity和Fragment,但只有Dagger可以创建所有的注入对象系统框架类才能玩好的工作,结果悲剧的是您必须在生命周期方法中执行成员注入。这意味着许多类最终看起来像这个鬼样子:
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!
//您必须在生命周期方法中执行成员注入,FrombulationActivity才工作
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
// ... now you can write the exciting code
}
}
这样就会有些问题
1.即使Dagger使我们的代码耦合性更低,但是如果要面临重构,我们仍然不得不去面对每个Activity中这样数行需要我们「复制」+「粘贴」的代码,这会给我们的重构带来一定的难度(试想一下,如果我们的应用有数十个乃至上百个这样的Activity或者Fragment容器,我们的重构计划,首先就要面对这样数百行的代码)。
并且随着新的开发人员加入(他们也许并不知道这些代码的意义,但是他们会复制粘贴),越来越少的人知道这些代码都干了些什么。
2.更重要的是,它要求注射类型(FrombulationActivity)知道其注射器。 即使这是通过接口而不是具体类完成的,它打破了依赖注入的核心原则:一个类不应该知道如何被依赖注入的。
开心的是Google推出了dagger.android
他提供了一种很简洁的方式来解决上述问题.
下面的流程的翻译结合了我在GitHub 的Demo做了一些备注,请知悉,
Injecting Activity
objects
- 添加
AndroidInjectionModule
到你的application级别的component, 在这里提供全局的基础依赖。在demo中是maincomponent,在这里提供全局的并且是唯一的东西,SharedPrefence,DB,HTTP,etc。
@Singleton
@Component(modules = {
MainModule.class, //全局的Module,要确保提供的对象是全局唯一的
AllActivityModule.class, //减少模版代码,需要依赖注入的只需要添加两行代码就好了
AndroidInjectionModule.class, //在应用程序的MainComponent(applocation 中inject了)中,注入AndroidInjectionModule,
// 以确保Android的类(Activity、Fragment、Service、BroadcastReceiver及ContentProvider等)可以绑定。
// 一般把AndroidInjectionModule放在ApplicationComponent中,其他的Component依赖Application即可
AndroidSupportInjectionModule.class, //使用的Fragment 是V4 包中的?不然就只需要AndroidInjectionModule
})
//YourApplicationComponent
public interface MainComponent {
void inject(MyApplication application);
}
-
定义好YourActivitySubcomponent接口 并继承
AndroidInjector<YourActivity>
和@Subcomponent.Builder注解的内部抽象类Builder,该内部类继承extendsAndroidInjector.Builder<YourActivity>
:@Subcomponent(modules = ...) public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Builder public abstract class Builder extends AndroidInjector.Builder<YourActivity> {} }
写到这里大家估计有点警觉了,YourActivitySubcomponent?是不是我项目中有很多的Activity都需要写Activity1234Subcomponent?Demo 中就只写了一个BaseActivityComponent,讲完这一小段后面再详细的总结一下。
-
定义好了YourActivitySubcomponent后定义一个YourActivityModule,这个YourActivityModule依赖YourActivitySubcomponent及其Builder。当然需要添加到你的ApplicationComponent去(demo 优化了流程,这个ApplicationComponent 就是MainComponent),ApplicationComponent 在我们的Application 中执行了inject。
@Module(subcomponents = YourActivitySubcomponent.class) abstract class YourActivityModule { @Binds @IntoMap @ActivityKey(YourActivity.class) abstract AndroidInjector.Factory<? extends Activity> bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder); } @Component(modules = {..., YourActivityModule.class}) interface YourApplicationComponent {} //在项目Demo中是MainComponent
重要提示:在步骤2中,假如你的subcomponent和他的Builder没有其他的方法或者supertypes,你可以简单的使用@ContributesAndroidInjector
来简单的帮你生成代码。为了取代步骤2和3,你需要定义一个抽象的module方法返回你的activity并用@ContributesAndroidInjector注解,然后绑定到对应的subcomponent.如果subcomponent需要有作用域scope,你也要使用这个作用域注解module 中的抽象方法。
Pro-tip: If your subcomponent and its builder have no other methods or supertypes than the ones mentioned in step #2, you can use@ContributesAndroidInjector
to generate them for you. Instead of steps 2 and 3, add an abstract
module method that returns your activity, annotate it with @ContributesAndroidInjector
, and specify the modules you want to install into the subcomponent. If the subcomponent needs scopes, apply the scope annotations to the method as well.
```
@ActivityScope
@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })
abstract YourActivity contributeYourActivityInjector();
```
这个重要提示很重要,后面会讲根据提示怎么精简这个流程
这个重要提示很重要,后面会讲根据提示怎么精简这个流程
这个重要提示很重要,后面会讲根据提示怎么精简这个流程
-
下一步,让你的application实现
HasActivityInjector
同时 用@Inject
一个DispatchingAndroidInjector<Activity>
对象,该对象是重写的activityInjector()
方法返回的:public class YourApplication extends Application implements HasActivityInjector { @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector; @Override public void onCreate() { super.onCreate(); DaggerYourApplicationComponent.create() .inject(this); } @Override public AndroidInjector<Activity> activityInjector() { return dispatchingActivityInjector; } }
-
最后, 在你的Activity的
Activity.onCreate()
方法中, 调用AndroidInjection.inject(this)
,记得一定要在调用super.onCreate();
之前调用:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); } }
恭喜你,你已经完成了跑步8公里✅
流程说完了,但是是怎么工作起来的呢?
通过AndroidInjection.inject()方法从Application中获取DispatchingAndroidInjector<Activity>对象,同时将Activity传入inject(Activity)方法中。DispatchingAndroidInjector<Activity>对象通过Activity类查询AndroidInjector.Factory(就是YourActivitySubcomponent.Builder),创建ActivityInjector(就是YourActivitySubcomponent),并将Activity传入它的inject(Activity)方法中。
翻译到这里就差不多了,后面关于Fragment不准备翻译了,GitHub 的Demo里面有怎样注入Activity,Fragment,service 的简单例子。
怎么精简这个流程,应该说怎么少写重复的无意义的模版代码?
YourActivity,
When to inject
Constructor injection is preferred whenever possible because javac
will ensure that no field is referenced before it has been set, which helps avoid NullPointerException
s. When members injection is required (as discussed above), prefer to inject as early as possible. For this reason, DaggerActivity
calls AndroidInjection.inject()
immediately in onCreate()
, before callingsuper.onCreate()
, and DaggerFragment
does the same in onAttach()
, which also prevents inconsistencies if the Fragment
is reattached.
It is crucial to call AndroidInjection.inject()
before super.onCreate()
in an Activity
, since the call to super
attaches Fragment
s from the previous activity instance during configuration change, which in turn injects the Fragment
s. In order for the Fragment
injection to succeed, the Activity
must already be injected. For users of ErrorProne, it is a compiler error to call AndroidInjection.inject()
after super.onCreate()
.
FAQ
Scoping AndroidInjector.Factory
AndroidInjector.Factory
is intended to be a stateless interface so that implementors don’t have to worry about managing state related to the object which will be injected. When DispatchingAndroidInjector
requests a AndroidInjector.Factory
, it does so through a Provider
so that it doesn’t explicitly retain any instances of the factory. Because the AndroidInjector.Builder
implementation that is generated by Dagger does retain an instance of the Activity
/Fragment
/etc that is being injected, it is a compile-time error to apply a scope to the methods which provide them. If you are positive that your AndroidInjector.Factory
does not retain an instance to the injected object, you may suppress this error by applying@SuppressWarnings("dagger.android.ScopedInjectoryFactory")
to your module method.