依赖注入框架性能对比

依赖注入(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次注入:

  1. 注入4800个模型对象(A* .. F*)到Test*
  2. 注入600个Test对象到TestManager
  3. 注入40个Test*到MainActivity
  4. 注入15个TestManager到MainActivity

(单个框架特有的功能,例如Roboguice的View注入,并没有被使用)

我们使用nimbledroid.com来测试执行时间,下面是我们在同一个测试APP中使用三个不同依赖注入框架的测试结果:

上面的数据可能有点迷惑性,接下来我们逐步分析。

NimbleDroid默认会显示APP的冷启动时间。你可以通过导航(Analysis Details -> Icicle Graph)查看具体每个方法的执行时间,就像下面这样:

Roboguice Iricle Graph
调用 com.google.inject.internal.InjectorImpl.injectMembers 耗费3923毫秒

令人惊讶的是,Roboguice注入依赖耗费了3923毫秒!性能开销太大了!考虑到我们这里只是注入了简单地测试对象,实际场景中Roboguice的性能开销会更大,因为实际APP的依赖对象通常关系复杂,创建也会耗费更多的时间。如此漫长的注入时间使得依赖注入得不偿失了,意识到这一点非常重要,毕竟建议的APP启动时间是2秒以内,但我们花在依赖注入上的时间就已经将近4秒了。

现在,让我们看看Dagger 1和Dagger 2的测试结果。

Dagger 1:

Dagger 1 Iricle Graph
调用 dagger.ObjectGraph$DaggerObjectGraph.inject 耗费154毫秒

Dagger 2:

Dagger 2 Iricle Graph
调用 com.nimbledroid.demo.dagger2.DaggerD2EComponent.inject 耗费62毫秒

我们可以看到,Roboguice的耗时是Dagger 1的25倍,而Dagger 2速度更快。

实际APP例子

现在让我们测试一下市场上的APP。一个绝佳的例子就是Skype了。

在历史版本中,Skype使用了Roboguice进行依赖注入

Old skype icicle graph
调用 roboguice.RoboGuice.setBaseApplicationInjector 耗费2415毫秒

可以看到,他们使用Roboguice时耗费了2415毫秒。

一段时间后,他们切换到了Dagger,把依赖注入的时间减少到了170毫秒。

New skype iricle graph
调用 com.skype.android.DaggerSkypeApplicationComponent.inject 耗费171毫秒

以下是存在类似问题的流行APP:

  1. Groupon 店铺优惠券 (~700ms 开销)
  2. ScanLife 条形码和二维码 (~1,200ms 开销)
  3. Amex Mobile (~1,700ms 开销),而最新的版本 (~274ms 开销) 切换到了Dagger
  4. Fandango Movies (~3,400ms 开销)

方法数

使用Roboguice还有另外一个缺点。通过查看我们使用Roboguice的测试APP的详细信息,我们发现:

Roboguice app info
方法数:33,189

在“Method Count”部分,NimbleDroid显示Roboguice测试APP有33,189个方法。

反观Dagger 1的数据:

Dagger 1 app info
方法数:23,997

和Dagger 2的数据:

Dagger 2 app info
方法数:23,930

33200个方法和24000个方法!安卓存在单个dex文件65K方法数限制,将近10000个方法数的差别还是非常巨大的。使用Roboguice也使得整个APK文件增大300KB左右。

注意:我们的测试中没有使用proguard,因为我们希望测试出这三个框架未优化时的开销。

建议:在安卓APP中使用Roboguice需要格外小心,因为相比于Dagger,它会带来更大的性能开销和方法数量。如果可以的话,请使用Dagger 2,因为它比Dagger 1更快。