Android 依赖注入可以更简单 —— 新版本 Dagger 2 使用教学
今年 3 月 21 号 Dagger 2 在 2.10 版本之后针对 Android 方面做了很大的优化,使用方法也随之有了不少变化。本次改动除了让 Dagger 2 的使用更加符合控制反转原则,还针对 Android 端做出了专门的优化(即所谓 dagger.android) —— 大大简化了 Android 端 Dagger 2 的使用。现在,不妨随着本文一起探寻新版本 Dagger 2 的基本使用方法。
阅读前提
要注意阅读本文内容前最好对 2.10 版本前的 Dagger 2 比较熟悉,至少要明白它的依赖注入管理机制,最好是实际项目中使用过,不然阅读时会比较迷茫。当然,一切的 Dagger 2 的说明文章很多,善用搜索引擎就可以找到不错的教程,这里就不再赘述了。
另外,本文的 demo 项目中用到了 Google Samples 项目 Android Architecture 使用的 MVP 实现方式,这个也最好有所了解。主要是了解它的 todo-mvp-dagger 分支的结构组织方法即可,至少要明白它的功能层次和组织结构。
以前的做法有问题?
想想我们在 2.10 版本之前是怎么在 Android 端使用 Dagger 2 的?是不是类似下面的代码:
public class FrombulationActivity extends Activity {@Inject Frombulator frombulator;@Overridepublic 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 文档。
emmmmm,好像也没什么问题啊?
不,其实这段代码大有问题!
第一点,代码重复度过高:我们要在几乎每一个需要注入依赖项的 Activity 和 Fragment 里面来一套这样的代码。这是因为我们在 Android 编程中要用到的 Activity、Fragment 等类是继承自系统的,同时生命周期都由系统管理,所以使用 Dagger 2 的时候就不得不进行手动的处理,也就只有在纯粹自己写的 Java 类中使用 Dagger 2 才感觉更舒服一些。
第二点,违反了控制反转原则:原有的用法使得被注入的目标类必须要了解注入管理工具的详细信息,才能让注入工作顺利进行。即使可以通过接口使得实际代码中不必书写太多的实际类名,但这仍然造成了严重的目标类和管理类的紧耦合。
其实,我们以前在使用 Dagger 2 的时候为了解决重复问题也是使用 Live Template 或者设计一个通用的工具函数;而为了保证注入目标类正常工作和注入管理正常进行,就必须在设计业务代码的时候并行设计注入管理代码。
幸好,2.10 版本针对 Android 系统做出了很大的优化,甚至单独做了 Android 方面的依赖注入管理库,提供了针对 Android 的注解和辅助类大大减少了要写的代码。
下面我就用一个简单的 demo 讲解一下新版本的 Dagger 2 的基本使用方法,项目地址 请戳这里。注意项目有两个分支,master 分支用来演示基本使用方法,simplify 分支演示简化用法。
基本使用方法
实际上,新版本的 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 注入管理
Application 的注入基本没有什么变化,更多的是 Dagger 2 官方建议最好使用 Builder 模式构建 Component
,方便灵活的向 Component
中添加构建属性,比如:
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {void inject(MyApplication application);@Component.Builderinterface Builder {@BindsInstanceBuilder application(Application application);AppComponent build();}
}复制代码
Module
和旧版相比没什么太大变化:
@Module
public class AppModule {@Singleton@ProvidesContext provideContext(Application application) {return application;}
}复制代码
而一般在调用数据库或存取 SharedPreferences 文件时,常常用到
Context
,一般会提供 Application 而非 Activity 等。
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.Builderabstract 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@ProvidesContext 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 注入管理
如果需要对 Fragment 设置单独的注入管理类,那么可以参考 Activity 的方式,不同的是父类的 Module
信息要放到 Activity 的 Component
注解中,所有管理类信息如下(本例中也用到了简单的 MVP 结构):
DummyActivity 相关:
@ActivityScoped
@Subcomponent(modules = {DummyActivityModule.class, DummyFragmentBindingModule.class})
public interface DummyActivityComponent extends AndroidInjector<DummyActivity> {@Subcomponent.Builderabstract class Builder extends AndroidInjector.Builder<DummyActivity> {}
}
/*************************************/
@Module
public class DummyActivityModule {@ActivityScoped@ProvidesDummyContract.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.Builderabstract class Builder extends AndroidInjector.Builder<DummyFragment> {}
}
/*************************************/
@Module
public class DummyFragmentModule {@FragmentScoped@ProvidesDummyContract.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 {@InjectDispatchingAndroidInjector<Activity> mInjector;@Overridepublic void onCreate() {super.onCreate();DaggerAppComponent.builder().application(this).build().inject(this);}@Overridepublic AndroidInjector<Activity> activityInjector() {return mInjector;}
}复制代码
第二,无需管理 Fragment 的 Injector
的 Activity 直接注入自己需要的依赖即可:
public class MainActivity extends BaseActivity {// ...@Overrideprotected void onCreate(Bundle savedInstanceState) {AndroidInjection.inject(this);super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}
}复制代码
第三,需要管理 Fragment 的 Injector
的 Activity 需要实现接口 HasSupportFragmentInjector
,方式类似第一步:
public class DummyActivity extends BaseActivityimplements HasSupportFragmentInjector {@Inject DispatchingAndroidInjector<Fragment> mInjector;// ...@Overrideprotected void onCreate(final Bundle savedInstanceState) {AndroidInjection.inject(this);super.onCreate(savedInstanceState);setContentView(R.layout.activity_dummy_layout);// ...}// ...@Overridepublic AndroidInjector<Fragment> supportFragmentInjector() {return mInjector;}
}复制代码
第四,对 Fragment 注入依赖:
public final class DummyFragment extends BaseFragment {// ...@Overridepublic 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
类满足以下条件,就能简化:
- 除了对应的 Builder 外没有其他的方法
- Builder 类也不会添加其他的方法去自定义构建属性
在简化之前,要先在 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 {@Bindsabstract Context provideContext(Application application);
}复制代码
同理,在 MVP 中如果需要提供 Presenter
接口也可以使用这个办法,比如:
@Module
public abstract class DummyActivityModule {@ActivityScoped@Bindsabstract DummyContract.Presenter bindDummyPresenter(DummyPresenter presenter);
}复制代码
只不过这个办法只能把传入的实例和返回类型进行绑定,其他复杂的依赖提供方法(比如需要利用传入参数手动实例化,或进行条件判断)还是不能简化的。(这不是废话吗...)
简化注入操作
注入操作很明显可以简化,毕竟模式完全相同,简化的方法就是提供了模板父类 DaggerApplication
、DaggerAppCompatActivity
和 DaggerFragment
,然后让需要注入操作的类继承它们即可。
最后,Application 的代码就如下所示:
public class MyApplication extends DaggerApplication {@Overrideprotected AndroidInjector<? extends DaggerApplication> applicationInjector() {return DaggerAppComponent.builder().application(this).build();}
}复制代码
而 Activity 和 Fragment 则最好是让 BaseActivity
和 BaseFragment
去继承上面提到的两个模板父类,在一般的代码中自只需要用 @Inject
标出需要注入的元素即可,无需任何额外操作。这样,在编写 Activity 和 Fragment 的时候无需考虑依赖注入的细节,只要按照正常流程编写代码,然后不断检查、测试代码,不断标记出需要注入的元素即可。
最后,再次提醒相关代码不可能全部演示出来,可以去 Dagger 2 Android Demo 查看具体细节,尤其简化部分的代码重复内容较多文章不作赘述,需要的可以自行查看 simplify 分支。另外,如果项目有 bug 也欢迎直接提出 issue。
参考
- Dagger 2 官方 Android 使用文档
- New Android Injector with Dagger 2 — part 1
- New Android Injector with Dagger 2 — part 2
转载于:https://juejin.im/post/5a20b606f265da430e4ef364
Android 依赖注入可以更简单 —— 新版本 Dagger 2 使用教学相关推荐
- Android 依赖注入: Dagger 2 实例解说(一)
本文原创,转载请注明出处:http://blog.csdn.net/zjbpku [Duplicated] link to Dagger on Android - Dagger2具体解释 关于D ...
- Android 依赖注入 DI - Dagger2
1.依赖注入 (Dependency Injection) 1.1 面向接口编程 public interface Drivable {void drive(); }public class Bike ...
- Android 依赖注入框架 Dagger2使用
前言 Dagger 2这个匕首确实很难上手,上手后又比较难瞬间掌握,可以这么说,刚开始使用就是用来尝(zhuang)鲜(X)的,但相信随着使用的加深,会不断体会到它对于整个项目架构的极强辅助作用,能使 ...
- Android依赖注入:Google Guice on Android的使用及相关资源
本文转自:http://blog.csdn.net/sangming/article/details/8878104 RoboGuice 使用谷歌自己的Guice库,给Android带来了简单和易用的 ...
- 三星s20 android auto,Automagic一个更简单的方式来自动化您的Android手机 | MOS86
你有没有想过你的智能手机会在你回家的时候开始播放音乐?当你关闭社交网络的时候怎么样?Automagic是一种替代方案,通过使用易于理解的流程图执行许多相同的任务,向用户提供了一种更简单的自动化Andr ...
- Android依赖注入的实践——Dagger2
个人博客CoorChice,https://chenbingx.github.io/ ,最新文章将会首发CoorChice的博客,欢迎探索哦 ! 同时,搜索微信公众号CoorChice,或扫描文章末尾 ...
- android 全局注入,一种简单的Android全局注入方案
Xposed和Cydia Substrate是android上两款比较知名的全局Hook框架,但都不尽善尽美,有时为了实现某个功能往往要绕许多弯路去配合框架,结果也差强人意,与其浪费精力去熟悉框架不如 ...
- Android mock for循环,Android单元测试(五):依赖注入,将mock方便的用起来
在上一篇文章中,咱们讲了要将mock出来的dependency真正使用起来,须要在测试环境下经过某种方式set 到用到它的那个对象里面进去,替换掉真实的实现.咱们前面举的例子是:html public ...
- dagger2 注入_Android依赖注入– Dagger 2
dagger2 注入 In this tutorial, we'll discuss and implement Dependency Injection (DI) in our android ap ...
最新文章
- 自监督媲美全监督,港中文、商汤场景去遮挡方法入选 CVPR 2020 Oral
- Java 基础【08】.class getClass () forName() 详解
- 一致性算法(paxos,raft,ZAB)——paxos算法(大概)
- java环境变量配置(win7)
- 华工软院17级“软件需求分析”课程大作业
- Radio / Select 设置 checked 没反应
- Aspose.Word 的常见使用(不用模板创建)
- 2017年计算机导论试题,2017年云南农业大学基础与信息工程学院813计算机导论与数据结构考研题库...
- 【译】Objectively Speaking 2: A Crash Course in Objective-C for iOS 6
- Nginx限制访问次数和并发数
- 数字地球与计算机技术联系,数字地球与地球空间信息科学的关系
- OFD在线预览方案评测
- PPT学习整理(四)表格
- 分子动力学理论部分总结(未整理完)
- 【CodeForces】[372A]Counting Kangaroos is Fun
- 美团笔试题(3)外卖满减
- 2022年湖北省文化产业示范园(基地)发展专项资金申报条件以及奖励补贴情况!
- 二分+思维点点之间最大距离
- 点亮led灯的个数_一个点亮LED灯需要多大电流?
- 2019年1月28日学习日记
热门文章
- R语言数据可视化 ggplot2基础3 添加几何对象
- 体感Kinect手势识别开发基本原理
- VC++文件编程操作实例
- Fiddler 环境和过滤规则详解
- ElasticSearch中distinct,count和group by的实现
- 牛客网——10进制 VS 2进制
- 我对孩子学习编程的一点思考
- 数据库设计中的五个范式
- python封装类在当前文件中使用_name_下调用不了_学python中对于类怎么也不明白,对了是看到简明教程11章,类与变量的对象....想要通俗易懂的答案....
- 清华镜像源地址_PyCharm安装第三方库(内含添加国内镜像源方法)