有赞移动

Dagger2 — Fantastic DI tools

Dagger2 是什么

Dagger2 是一个静态依赖注入框架,用来管理变量的生命周期。它替代了抽象工厂模式

Dependency injection isn’t just for testing. It also makes it easy to create reusable, interchangeable modules. You can share the same AuthenticationModule across all of your apps. And you can run DevLoggingModule during development and ProdLoggingModule in production to get the right behavior in each situation.

为什么用 Dagger2

我讨厌 getInstance 充斥着我的项目

Dagger 中最常用的 annotation 是@Singleton@Reuseable,都是用来创建单例,我们再也不用因为要使用单例,去写一大堆makeInstancegetInstance;我们再也不用纠结到底在makeInstance中传入Context还是在getInstance中传入Context,没错,这一切烦恼都将不存在——我们只用在构造函数中传入Context,然后在外部标记它为单例即可。

延迟初始化

我们用到许多的组件,可能需要调用init方法,传入Context,这部分的工作一般会在ApplicationonCreate方法完成的,于是我们会看到如下的代码:

1
2
3
4
5
public void onCreate() {
A.init(this);
....
B.init(this);
}

这种写法第一使得组件管理混乱,onCreate 中逻辑可能十分复杂,第二,很多组件的初始化会耗费一些时间,当所有耗费时间的组件都放在一起,且在 App 启动的时刻,可能导致 App 启动时间飞涨,所以,我们需要采用“懒加载”的方式,初始化这些组件,Dagger 在我们需要注入组件的过程中,只在第一次检查没有这个实例的时候,才会生成这个组件,因此我们可以把“初始化”工作后移。

静态注入 vs 动态注入

动态注入的方式如Spring MVC的方式,使用反射的方式进行注入,这种方式在服务端可能没有太大的问题,在性能要求较高的嵌入式设备上,我们必须尽量减少使用反射、动态代理等方式改变类的结构,或者为类注入成员变量。

Dagger 使用 annotationProcessor 生成 Injector,只需经过一些改造,就可以实现依赖注入的功能,它虽然没有动态依赖注入那么简洁漂亮低侵入性,但是依然可以为我们带来一些惊喜的功能,同时又不对当前的性能造成影响。

作用域控制

Dagger 提供了 Scope 的控制,这使得我们对变量的生命周期使用不仅仅局限于单例 or 非单例,我们可以创建许多个 Scope 管理不同变量的生命周期。比如 ActivityScopeServiceScope 或者超越 Activity 小于 Application的变量周期。Dagger 使得控制此类变量变得十分得心应手。

别名控制

如果这时候有一个需求:一个类在整个Application生命周期中,有且仅有两个实例,且在任意时刻,你可以根据需要,获取其中一个你要的实例,你会如何控制?

Dagger 提供了@Named注解,为生成的实例提供了别名,我们在需要注入的地方,指定好我们想要的别名,这个别名对应的实例,依然是被作用域控制着的,这给我们的变量管理提供了很大的方便。

Dagger2 接入成本

学习成本

使用 Dagger2 之前,你至少需要知道 Dependency Injection 是什么。
你要知道 Dagger2 中的几个重要的组件,ModuleComponentScope等是拿来干嘛的。
https://google.github.io/dagger/users-guide.html
这是官方文档,学习的门槛是至少知道DI是什么。

显式声明被注入对象

Dagger2 有一些约定,首先我个人觉得最难接受的是需要显式声明被注入的对象,例如:

1
2
void inject(A a);
void inject(B b);

如果这时候,你的类 C 是继承于 B 的,也需要显式声明一次,这是为了让 dagger-generator 能准确地静态分析被注入的对象。

侵入性

  1. Dagger2 需要被注入的成员变量不为私有声明,也就是说,你只可以是public和默认的包访问权限 (因为生成的代码在同一个包下面),那么如果这时候需要接入 Dagger2 的话,需要更改声明习惯。
  2. Dagger2 需要在类中提供获取Component的方法,一般获取Componenet的方法都会放在基类里。提供该方法后,调用inject或者其他自定义命名的方法,才能完成注入,这对原代码来说是增加了接入的代码操作。
  3. 对原先单例的改造,因为不需要getInstance了,所以对单例涉及的地方都需要改造,包括单例类本身和把引用单例的类改成可被注入的类。

生成过多的代码

如果项目正在接近方法数的极限,那么这个时候,接入 Dagger2 估计一下子会破了方法数的限制,Dagger 为了致力于生成“人类可读”的代码下了不少功夫,因此也造成生成的类文件方法过于冗余,当然这种冗余在项目庞大的时候,是有必要的。