Android 中的编译时依赖注入权衡

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

作为后端软件开发人员,我习惯将 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 依赖项。有两个步骤:

  1. 在上下文中获取对 eventBus 实例的引用
  2. 使用相关参数调用构造函数

接线配置本身是在所谓的模块中完成的:


 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 的限制,这可能是可以达到的最佳效果。

相关文章