依赖注入(DI)技术在安卓开发领域已经非常流行了。DI可以减少重复代码的编写,更便于调试和测试,使得开发出优秀的APP更加简单。尽管各种DI框架看着都非常强大,但也需要非常注意它们对APP性能带来的开销。本文中我们对比了三个最流行的DI框架,来帮助开发者了解它们各自的优劣。
我们比较的是以下三个DI框架:
Roboguice和Dagger的工作原理有所差异,Roboguice在运行时利用反射进行依赖注入,而Dagger则利用编译期的注解处理来生成依赖注入的代码,尽可能的减少了反射的使用。当然这一差异在APP的性能上的提现非常明显,因为在安卓平台反射的开销非常大。尽管Roboguice的开销更大,由于它使用起来更加简单,很多开发者还是会倾向于Roboguice,他们认为APP性能的提升相比于开发过程中的付出要得不偿失,然而这种思想是最应该摈弃的。(注意我们这里并不讨论Butter Knife,因为它是进行View绑定的,并不是通常所说的依赖注入,当然如果你需要进行View绑定的话,Butter Knife是不错的选择。)
本文接下来的部分,会首先展示对依赖注入框架的性能测试结果,然后展示对市场上百万下载量的APP进行性能测试的结果,最后对比一下三个框架的方法数量。
微基准测试
我们创建了一个Github仓库,包含了三个示例APP。建议先花费几分钟查看一下,如果你使用过这三个框架,工程的内容就会很好理解了。三个APP包含了相同的依赖图,我们使用不同的框架注入这些依赖,并计算性能开销。同时我们还输出了一些log,以确保依赖对象被成功注入。
我们总共进行了5455次注入:注入4800个模型对象(A* .. F*)到Test*
注入600个Test对象到TestManager
注入40个Test*到MainActivity
注入15个TestManager到MainActivity
(单个框架特有的功能,例如Roboguice的View注入,并没有被使用)
我们使用nimbledroid.com来测试执行时间,下面是我们在同一个测试APP中使用三个不同依赖注入框架的测试结果:
上面的数据可能有点迷惑性,接下来我们逐步分析。
NimbleDroid默认会显示APP的冷启动时间。你可以通过导航(Analysis Details -> Icicle Graph)查看具体每个方法的执行时间,就像下面这样:
调用 com.google.inject.internal.InjectorImpl.injectMembers 耗费3923毫秒
令人惊讶的是,Roboguice注入依赖耗费了3923毫秒!性能开销太大了!考虑到我们这里只是注入了简单地测试对象,实际场景中Roboguice的性能开销会更大,因为实际APP的依赖对象通常关系复杂,创建也会耗费更多的时间。如此漫长的注入时间使得依赖注入得不偿失了,意识到这一点非常重要,毕竟建议的APP启动时间是2秒以内,但我们花在依赖注入上的时间就已经将近4秒了。
现在,让我们看看Dagger 1和Dagger 2的测试结果。
Dagger 1:
调用 dagger.ObjectGraph$DaggerObjectGraph.inject 耗费154毫秒
Dagger 2:
调用 com.nimbledroid.demo.dagger2.DaggerD2EComponent.inject 耗费62毫秒
我们可以看到,Roboguice的耗时是Dagger 1的25倍,而Dagger 2速度更快。
实际APP例子
现在让我们测试一下市场上的APP。一个绝佳的例子就是Skype了。
调用 roboguice.RoboGuice.setBaseApplicationInjector 耗费2415毫秒
可以看到,他们使用Roboguice时耗费了2415毫秒。
一段时间后,他们切换到了Dagger,把依赖注入的时间减少到了170毫秒。
调用 com.skype.android.DaggerSkypeApplicationComponent.inject 耗费171毫秒
以下是存在类似问题的流行APP:Amex Mobile (~1,700ms 开销),而最新的版本 (~274ms 开销) 切换到了Dagger
方法数
使用Roboguice还有另外一个缺点。通过查看我们使用Roboguice的测试APP的详细信息,我们发现:
方法数:33,189
在“Method Count”部分,NimbleDroid显示Roboguice测试APP有33,189个方法。
反观Dagger 1的数据:
方法数:23,997
和Dagger 2的数据:
方法数:23,930
33200个方法和24000个方法!安卓存在单个dex文件65K方法数限制,将近10000个方法数的差别还是非常巨大的。使用Roboguice也使得整个APK文件增大300KB左右。
注意:我们的测试中没有使用proguard,因为我们希望测试出这三个框架未优化时的开销。
建议:在安卓APP中使用Roboguice需要格外小心,因为相比于Dagger,它会带来更大的性能开销和方法数量。如果可以的话,请使用Dagger 2,因为它比Dagger 1更快。