都说不用 dagger2
的 mvp
模式都是耍流氓,但是,但是我之前一直都在耍流氓?所以这两天入门了一下 dagger2
。
参考的项目时googlesamples/android-architecture的 dagger
分支。还有就是 dagger
里面自带的example。
dagger的定义
A fast dependency injector for Android and Java.
Android和Java的依赖快速注入器。
定义是如此的简洁,那么到底是撒意思呢?应该如何理解?我现在的理解就是它可以快速自动的构建出我们所需要的依赖对象,这里的依赖对象可以理解为某一个成员变量。例如在 MVP
中,VP
层就是互相关联的, V
要依赖对应的 P
,而 P
也要依赖对应的 V
。dagger
能解决的就是这种依赖关系,通过注入的方式,将双方的耦合再次降低,在实际的使用中体现为一个注解想要的对象就创建好了,咱们不用再去管理所依赖对象的创建等情况了。
dagger
入门
注入,肯定涉及到 将什么 注入 到哪里 的三个问题,那接下来就围绕着三个方面展开咯!
自定义 Module
注入
在 Module
中使用 @Provides
提供相关的注入对象是第一种实现方式。具体做法是定义一个类,加上 @Moudule
的注解。然后在里面使用 @Provides
定义返回注入对象的方法。
@Module
public final class ApplicationModule {
private final Context mContext;
ApplicationModule(Context context) {
mContext = context;
}
@Provides
Context provideContext() {
return mContext;
}
@Provides
DailyApiService provideDailyApiService() {
return BaseDataManager.getDailyApiService();
}
@Provides
int provideAge() {
return 500;
}
}
关于 Module
中的方法,按照官方解释咱们需要有以下的注意事项,每个方法应该是无参的,返回的就是需要注入的类型的。
构造方法注入
如果我们可以访问对应的构造方法,那儿这里也提供了另外一种注入的方式,就是在构造方法是使用 @Inject
的注解了。
这个注解可以定义在构造方法上,方法上,字段上,但是这里有一个优先顺序,就是最先执行构造方法上的,其次是相关字段的,最后就是相关方法的。字段的权限不能是私有的。如果有多个构造方法,@Inject只能指定其中的一个。
@Inject
public Person(String birthday, String name) {
this.birthday = birthday;
this.name = name;
}
到这里基本上差不多说完了第一部分 将什么 的问题。
注入 到哪里
对于 注入 到哪里 这两个问题,这里首先要引入 @Component
的注解了,举个场景,例如我们在 Activity
里经常会使用到全局的 Application
,在 Application
的初始化中也可能初始化好某些类( ApiService
),那么这个关系就是 Activity
依赖 Application
, Application
依赖 ApiService
。
首先我们定义一个 AppComponent
,这是一个接口类,dagger
会自动生成具体实现类。 @Component
定义了需要注入的 Modules
对象 还有可以指定 所依赖的其他 Component
组件。对于 @Component
的使用,它应该定义在接口或者抽象类中,并且里面至少要有一个方法,至于每个方法的命名,没有特别的指定符合相关命名规范就好了。
@Singleton
@Component(modules = { ApplicationModule.class})
public interface AppComponent {
Context context();
DailyApiService getdailyApiService();
int getAge();
}
通过以上步骤,就已经定义好了 Component
和需要注入的 Module
了,然后在 APP
中来试着注入我们定义的 age
字段看看。这里第一个问题是咱们怎么获取对应的 AppComponent
实现类呢?定义好了 Component
和 Module
之后记得 rebuild
一下,然后 dagger
会自动生成对应的 Component
实现类,名字的话就是 Dagger+ComponentName
(如果你是内部类的话似乎还有外部的 ClassName
),然后调用其的 builder()
方法,传入依赖的 Module
或者 Component
,最后就创建好了对应的实现类了。
但是到这里,你会发现 log
显示的 age
根本就不是我们指定的 500
,为什么呢?怎么就没有注入成功呢?!因为我们还没有执行真正的注入这个动作啊,还缺了最后一个方法,执行我们的注入,上面只是把对应的Component
创建出来了而已,并没有注入呢!那么最后这一步怎么定义呢?
根据文档,我们需要在 AppComponent
接口中再定义一个方法,这个方法只能有一个参数,这个参数就是指定我们之前所说的哪里,这个方法可以返回 void
或者该类型,咱们通常的写法都是返回 void
的。在接口中添加该方法,修改了 Component
记得要 rebuild
哟。
void inject(App app);
在 App
中最后调用 mAppComponent.inject(this)
,再次运行,这次就可以看到已经注入成功了,到这里,dagger
的入门就搞定了。用一个比喻就是 Component
相当于一个注射器,是个容器,还有一个针头,里面装的就是使用 @Provides
或者 @Inject
定义的需要注入的对象,通过inject(Type type)
的方法指定需要注入到指定的对象中,这样就完成了整个注入过程。
dagger进阶
相同类型多次注入
@Qualifier
如果我们在 Module
中有重复的类型返回,例如我定义两个 int
类型的provides
在 Module
中的话,编译直接会报错:
xxx is bound multiple times:
那如果我们真的需要注入同一类型多次呢,这个问题总会有解决方案的吧?要是真的这么坑估计也没人用 dagger
了吧!哈哈。下面说明这个问题应该如何解决。
在 dagger
中具有相同类型返回的情况时,可以使用@Qualifier
的注解来区分,而 dagger
已经为我们提供了一个其子类 @Named("xx")
的注解。
例如在上面的 Module
中可以如果需要返回两个 int
类型的话需要这么声明:
@Singleton
@Provides
@Named("number")
int provideNumber() {
return 5;
}
@Provides
@Named("age")
int provideAge() {
return 500;
}
并且在调用的位置,也需要使用同样的 @Named("age")
的来指定你需要注入的是哪个具体的对象。
懒加载
在上面的比喻中,一针扎进去,是撒都给你打进去了,那么如果有些我想要在调用的时候才加载呢?这里 dagger
提供了 Lazy
的方式来注入。
对应的获取就是:
@Inject
Lazy str;//延迟加载
你没有看错,就是这么简单,获取时使用 Lazy
包装一下就行了,在真正需要加载的时候调用 str.get()
来加载。
获取某一个注入对象多次
比如我们需要一次性创建出10个 Person
对象,这个怎么处理呢?这里就需要使用 Provider
的类来指定了。获取方式也是 get()
就好了,每次都会创建出行的对象。
Component
的依赖
如果我们定义了某一个 Activity
的 Component
,并且它依赖咱们的 AppComponent
里面的 APIService
的话就要这样定义了:
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ListStoryPresenterModule.class)
public interface ListStoryComponent {
void inject(ListStory view);
}
在 AppComponent
中需要将获取 APIService
的方法暴露出来,不然还是无法注入成功的。
那如果我觉得暴露这些方法太麻烦了,那需要怎么办呢?最简单就是使用 @SubComponent
,在所属的父 Component
中定义一个 SubComponent
,该 SubComponent
中将会包含父 Component
的所有方法。父 Component
不显示声明都可以。
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ListStoryPresenterModule.class)
public interface ListStoryComponent {
String getName(String name);
ListPresenter.View getView();
SubComponent getSubComponent(SubModule module);
}
@Subcomponent(modules = SubModule.class)
public interface SubComponent {
void inject(ListStory context);
}
子类注入
void inject(Activity view)
注入的方法是可以接受该类型的子类的,但是在子类中定义的 @Inject
是无效的,比如说上面说的那个 AppComponent
的例子中如果最后申明的注入方法是:
void inject(Application app);
你会发现,age
的值一样不会注入成功的。再举官方的实例:我们定义了 void inject(Self self)
的方法,只有A 和 B 能注入,其子类中的C是无法注入的。
class Parent {
@Inject A a;
}
class Self extends Parent {
@Inject B b;
}
class Child extends Self {
@Inject C c;
}
那么需要怎么解决这个问题呢?目前我的感觉只能在对应的 Component
中定义一系列需要实现的子类的注入方法咯。
void inject(App app);
void inject(SubActivityA activity);
void inject(SubActivityB activity);
作用域
最后还有一个问题就是 Component
的作用域,@Scope
就是其中来限定相关作用域,例如说我们需要初始化一些全局的工具类,像 Gson
、 SharedPreferences
这些,那我们就需要一个单例就行了,那么 dagger
如何保证注入的对象就是单例的呢?那就需要使用这个@Scope的注解了。然后应该在基类里面初始化一个全局通用的 Component
,然后在每个子类里面一次调用注入的方法。另外这个注解 Component
和 Module
是要成对使用的,不然会报错的。然后在具体的子类中实现initInject()
的方法。 对了,@Scope
已经实现了的是 @Singleton
,我们可以直接使用 。
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppComponent appComponent = ((App) getApplication()).getAppComponent();
initInject(appComponent);
}
public abstract void initInject(AppComponent appComponent);
}
小结
使用 dagger
的好处就是在有依赖对象的时候,进一步不用去考虑所依赖对象的创建什么的,对我们就是隐藏了构造对应对象的过程,省去了创建的相关代码。当然我也只是这两天刚刚上手使用 dagger
,所以不免有相关优势没有挖掘出来,或者自己有相关的理解偏差。另外建议大家多看看 dagger
的相关注解,除了是英文这个缺点,真的很详尽的。