为何Dagger的@Reusable作用域比@Singleton更慢?测试存疑
为什么@Reusable在你的测试中比@Singleton慢?
这问题挺有意思的,我先帮你拆解下测试结果背后的原因,再聊聊@Reusable的正确用法和优势。
你的测试为什么会出现反直觉的结果?
首先得明确Dagger对@Reusable和@Singleton的核心处理逻辑:
@Singleton:强制全局唯一,Dagger会在组件中维护一个单例实例,第一次获取时完成创建,后续所有调用直接返回这个实例(用双重检查锁保证线程安全)。@Reusable:不保证实例复用,它只是告诉Dagger「这个对象可以被复用,但不需要严格唯一」。在你的测试场景中,直接调用component.getReusable()时,Dagger每次都会创建新的ReusableFoo实例——因为没有触发复用的条件(比如没有多个依赖共享这个实例)。
你的测试循环了10亿次获取实例:
- 对于
@Singleton,只有第一次调用会执行对象创建,剩下的999,999,999次都是直接返回已有引用,几乎没有额外开销。 - 对于
@Reusable,每一次调用都要执行完整的对象创建流程(包括内存分配、构造函数初始化,哪怕是空构造也有JVM底层的开销),10亿次的累计开销自然远大于单例的锁检查+返回操作。
这不是因为@Singleton的双重检查锁开销小,而是对象创建的开销远超过了锁检查的开销,你的测试场景刚好放大了这个差异。
@Reusable的适用场景是什么?
@Reusable的设计目的是在「不需要全局唯一」的前提下,让Dagger有机会复用实例来优化性能,适用场景包括:
- 无状态工具类:比如纯函数式的工具类,创建开销不大,但被多个依赖注入点使用时,复用可以减少对象创建次数和内存占用。
- 避免绑定到固定作用域:
@Singleton绑定到Application级组件的生命周期,而@Reusable可以在任意组件中使用,组件销毁后实例可以被回收,避免单例可能带来的内存泄漏。 - 灵活的复用策略:当你希望Dagger根据上下文决定是否复用(比如在同一个注入链中复用实例,但不同注入链可以有不同实例),而不是强制全局唯一。
举个例子:如果有一个Logger类,它不需要全局唯一,但多个ViewModel都依赖它,用@Reusable的话,Dagger会在同一个ViewModel注入过程中复用同一个Logger实例,而不是给每个ViewModel都新建一个,但不同的ViewModel实例可以有各自的Logger。
@Reusable相比@Singleton的优势
- 灵活性更高:不绑定到特定组件作用域,可以在任意组件中使用,适配不同的生命周期需求。
- 避免全局状态问题:单例容易引入全局状态,导致代码耦合和测试困难,
@Reusable不需要强制唯一,减少了这种风险。 - 内存友好:实例生命周期和组件绑定,组件销毁后实例可以被GC回收,避免单例长期占用内存导致的泄漏。
如何正确测试@Reusable的复用特性?
如果想验证@Reusable的复用逻辑,可以修改测试场景:创建一个依赖ReusableFoo的类,然后多次获取这个类,检查是否复用了同一个ReusableFoo实例:
class FooConsumer @Inject constructor(val reusableFoo: ReusableFoo) // 在测试中 val consumer1 = component.getFooConsumer() val consumer2 = component.getFooConsumer() println(consumer1.reusableFoo === consumer2.reusableFoo) // 应该返回true,说明Dagger复用了实例
这种场景下,@Reusable的优势才会体现出来——避免了重复创建ReusableFoo实例。
内容的提问来源于stack exchange,提问作者Piotr Aleksander Chmielowski
相关产品推荐
相关产品推荐

