我一直认为,在使用Dagger2时,如果我们不需要保证总是获得相同的实例,则应该使用@Reusable
范围而不是@Singleton
,因为@Singleton
使用了双重检查,这既昂贵又缓慢。
但是,我进行了简单的性能测试,结果如下:
Reusable 4474 ms
Singleton 3603 ms
这是代码:
@Singleton
@Component
interface AppComponent {
fun getReusable(): ReusableFoo
fun getSingleton(): SingletonFoo
}
@Reusable
class ReusableFoo @Inject constructor()
@Singleton
class SingletonFoo @Inject constructor()
class TestClass {
@Test
fun test() {
val component = DaggerAppComponent.builder().build()
measure {
component.getReusable()
}
measure {
component.getSingleton()
}
}
private fun measure(block: () -> Unit) {
val start = System.currentTimeMillis()
(0..1000000000).forEach { block() }
println(System.currentTimeMillis() - start)
}
}
在构造较重的类(我尝试过使用
Retrofit
)和使用@Provide
带注释的方法而不是构造函数注入(inject)时,会出现相同的现象。我在测试中犯了错误,还是
@Reusable
慢一些?如果是这样,我们应该在哪里使用它?它比@Singleton
有什么好处吗? 最佳答案
正如David Medenjak提到并在评论中链接的那样,micro benchmarks in the JVM are difficult to get right。即使将您的结果看成是面值,这些 call 在一个紧密的十亿次 call 内循环中平均在1ns之内,彼此之间的比率为20%。
尽管我写了separate SO answer的更多细节,但我可以解决您的“它有没有好处”的问题:
@Reusable
的主要性能优势是在多线程应用程序中进行构造时,因为在竞争条件下@Reusable
可能会为单独的线程创建单独的对象,而不是同步创建。支付了创建成本后(就像您在每个块的第一次调用中所做的那样),接下来的十亿次调用是免费的(或接近免费),尤其是使用JVM内联和缓存时,并且在同一线程的同一堆栈中。尽管您的基准测试没有揭示它,但是如果绑定(bind)的创建存在任何线程争用,您仍然可以使用@Reusable
看到更好的性能。 @Reusable
的主要内存优势是可重用实例保留在直接使用它的最窄组件中。如果您有一个Android Fragment组件作为@Reusable
绑定(bind)的唯一使用者,那么当您破坏Fragment并收集其Component时,Android将释放并回收该内存。 @Reusable
和自定义作用域不同,@Singleton
的主要Dagger可用性优势是@Reusable
绑定(bind)可以包含在任何组件中,而不管该组件上有多少个范围注释。如果您具有@Singleton
绑定(bind),则绝对需要将绑定(bind)安装在带有@Singleton
注释的组件中。 @Reusable
的最大可用性优势是,与用@Singleton
或@ActivityScoped
注释的绑定(bind)不同,您声明的是绑定(bind)不是有状态的,或者要求是@Singleton
。如果要在Dagger之外使用绑定(bind)(或有一天要替换Dagger),则需要记录或确定@Singleton
绑定(bind)是否一定是@Singleton
还是仅仅是一个优化机会。使用@Reusable
,歧义消失了。