作为后端软件开发人员,我习惯将 Spring 作为我最喜欢的依赖注入引擎。替代方案包括 Java EE 的 CDI,它以不同的方式实现了相同的结果。然而,两者都是在运行时注入:这意味着在应用程序启动时需要付出一定的性能成本,即满足所有依赖项所需的时间。在应用程序服务器上,应用程序的生命周期以天(如果不是几周)为单位,启动时间开销是可以接受的。如果服务器只是大型集群中的一个节点,它甚至是完全透明的。
作为 Android 用户,当我启动一个应用程序并且它在打开之前滞后几秒钟时,我并不高兴。如果我们在那个时间上再增加几秒钟,就用户友好性而言将是非常糟糕的。更糟糕的是,DI 引擎的内存消耗将是一场灾难。这就是 Square 开发称为 Dagger 的编译时依赖注入机制的原因。请注意,Google 目前正在开发 Dagger 2 。在继续之前,我必须承认 Dagger 2 的文档是简洁的——充其量。但这是另一篇博文的绝佳机会
Dagger 2 与注释处理器一起工作:编译时,它将分析您的注释代码并生成组件之间的连接代码。好消息是这段代码与您手动编写的代码非常相似,没有秘密的黑魔法(与运行时 DI 及其代理相反)。以下代码显示要注入的类:
public class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
private final EventBus eventBus;
public TimeSetListener(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
eventBus.post(new TimeSetEvent(hourOfDay, minute));
}
}
请注意,代码在各个方面都完全独立于 Dagger。无法推断最终将如何注入。有趣的部分是如何使用 Dagger 注入所需的
eventBus
依赖项。有两个步骤:
-
在上下文中获取对
eventBus
实例的引用 - 使用相关参数调用构造函数
接线配置本身是在所谓的模块中完成的:
public class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
private final EventBus eventBus;
public TimeSetListener(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
eventBus.post(new TimeSetEvent(hourOfDay, minute));
}
}
请注意,
EventBus
作为参数传递给方法,由上下文提供。此外,范围明确为
@Singleton
。
到工厂的绑定发生在 组件 中,它引用所需的模块(或更多):
public class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
private final EventBus eventBus;
public TimeSetListener(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
eventBus.post(new TimeSetEvent(hourOfDay, minute));
}
}
这非常简单……直到有人注意到 Android 中的一些(如果不是大多数的话)对象具有由 Android 本身管理的生命周期,而无需调用我们的
注入友好构造函数
。活动就是这样的对象:它们由框架实例化和启动。只有通过诸如
onCreate()
之类的专用生命周期方法,我们才能将代码挂接到对象中。这个用例看起来更糟,因为字段注入是强制性的。更糟糕的是,它还需要调用 Dagger:在这种情况下,它充当普通工厂。
public class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
private final EventBus eventBus;
public TimeSetListener(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
eventBus.post(new TimeSetEvent(hourOfDay, minute));
}
}
我们第一次看到与 Dagger 的耦合,但它是一个很大的耦合。什么是
DaggerApplicationComponent
?前一个
ApplicationComponent
的实现,以及一个提供它们实例的工厂。由于它不提供
inject()
方法,我们必须在我们的接口中声明它:
public class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
private final EventBus eventBus;
public TimeSetListener(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
eventBus.post(new TimeSetEvent(hourOfDay, minute));
}
}
作为记录,生成的类如下所示:
public class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
private final EventBus eventBus;
public TimeSetListener(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
eventBus.post(new TimeSetEvent(hourOfDay, minute));
}
}
天下没有免费的午餐。尽管编译时 DI 乍一看非常有吸引力,但当用于我们的代码不管理生命周期之外的对象时,它就变得不那么吸引人了。缺点变得很明显:耦合到 DI 框架,更重要的是增加了对类进行单元测试的难度。但是,考虑到 Android 的限制,这可能是可以达到的最佳效果。