今年 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 的注入基本没有什么变化,更多的是 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 等。
第一,是方便管理多级 Component
,要求在最顶层的即 Application 的 Component
中引入 AndroidInjectionModule 或 AndroidSupportInjectionModule,代码形式以下:
@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 设置单独的注入管理类,那么能够参考 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
类知足如下条件,就能简化:
在简化以前,要先在 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);
}复制代码
只不过这个办法只能把传入的实例和返回类型进行绑定,其余复杂的依赖提供方法(好比须要利用传入参数手动实例化,或进行条件判断)仍是不能简化的。(这不是废话吗...)
注入操做很明显能够简化,毕竟模式彻底相同,简化的方法就是提供了模板父类 DaggerApplication
、DaggerAppCompatActivity
和 DaggerFragment
,而后让须要注入操做的类继承它们便可。
最后,Application 的代码就以下所示:
public class MyApplication extends DaggerApplication {
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}
}复制代码
而 Activity 和 Fragment 则最好是让 BaseActivity
和 BaseFragment
去继承上面提到的两个模板父类,在通常的代码中自只须要用 @Inject
标出须要注入的元素便可,无需任何额外操做。这样,在编写 Activity 和 Fragment 的时候无需考虑依赖注入的细节,只要按照正常流程编写代码,而后不断检查、测试代码,不断标记出须要注入的元素便可。
最后,再次提醒相关代码不可能所有演示出来,能够去 Dagger 2 Android Demo 查看具体细节,尤为简化部分的代码重复内容较多文章不做赘述,须要的能够自行查看 simplify 分支。另外,若是项目有 bug 也欢迎直接提出 issue。