[Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)
以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html
使用Dagger 2依赖注入 - 图表创建的性能
原文:http://frogermcs.github.io/dagger-graph-creation-performance/
#PerfMatters - 最近非常流行标签,尤其在Android世界中。不管怎样,apps只需要正常工作就可以的时代已经过去了。现在所有的一切都应该是令人愉悦的,流畅并且快速。举个例子,Instagram 花费了半年的时间 只是让app更加快速,更加美观,和更好的屏幕适配性。
这就是为什么今天我想去分享给你一些小的建议,它会在你app启动时间上有很大的影响(尤其是当app使用了一些额外库的时候)。
对象图表的创建
大多情况下,在app开发过程中,它的启动时间或多或少会增加。有时随着一天天地开发它是很难被注意到的,但是当你把第一个版本和你能找到的最近的版本比较时区别就会相对比较大了。
原因很可能就在于dagger对象图表的创建过程。
Dagger 2?你可能会问,确切地说 - 就算你移除了那些基于反射的实现方案,并且你的代码是在编译时期生成的,但是别忘了对象的创建仍然发生是在运行时。
对象(还有它的依赖)会在第一次被注入时创建。Jake Wharton 在Dagger 2演示中的一些幻灯片很清楚地展示了这一点:
以下表示在我们的 GithubClient 例子app中它是怎样的:
- App第一次(被kill之后)被启动。Application对象并没有
@Inject属性,所以只有AppComponent对象被创建。 - App创建了
SplashActivity- 它有两个@Inject属性:AnalyticsManager和SplashActivityPresenter。 AnalyticsManager依赖已被创建的Application对象。所以只有AnalyticsManager构造方法被调用。SplashSctivityPresenter依赖:SplashActivity,Validator和UserManager。SplashActivity已被提供,Validator和UserManager应该被创建。UserManager依赖需要被创建的GithubApiService。之后UserManager被创建。- 现在我们拥有了所有依赖,
SplashActivityPresenter被创建。

有点混乱,但是就结果来说,在SplashActivity被创建之前(我们假设对象注入的操作只会在onCreate()方法中执行)我们必须要等待以下构造方法(可选配置):
GithubApiService(它也使用了一些依赖,如OkHttpClient,一个RestAdapter)UserManagerValidatorSplashActivityPresenterAnalyticsManager
一个接一个地被创建。
嘿,别担心,更复杂地图表也几乎被立即创建。
问题
现在让我们想象下,我们有两个外部的库需要在app启动时被初始化(比如,Crashlytics, Mixpanel, Google Analytics, Parse等等)。想象下我们的HeavyExternalLibrary看起来如下:
public class HeavyExternalLibrary {
private boolean initialized = false;
public HeavyExternalLibrary() {
}
public void init() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
initialized = true;
}
public void callMethod() {
if (!initialized) throw new RuntimeException("Call init() before you use this library");
}
}
简单说 - 构造方法是空的,并且调用几乎不花费任何东西。但是有一个init()方法,它耗时500ms并且在我们使用这个库之前必须要被调用。确保在我们module的某处的某一时刻调用了init():
//AppModule
@Provides
@Singleton
HeavyExternalLibrary provideHeavyExternalLibrary() {
HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary();
heavyExternalLibrary.init();
return heavyExternalLibrary;
}
现在我们的HeavyExternalLibrary成为了SplashActivityPresenter的一部分:
@Provides
@ActivityScope
SplashActivityPresenter
provideSplashActivityPresenter(Validator validator, UserManager userManager, HeavyExternalLibrary heavyExternalLibrary) {
return new SplashActivityPresenter(splashActivity, validator, userManager, heavyExternalLibrary);
}
然后会发生什么?我们app启动时间需要500ms还多,只是因为HeavyExternalLibrary的初始化,这过程会在SplashActivityPresenter依赖图表创建中执行。
测量
Android SDK(Android Studio本身)给我们提供了一个随着应用执行的时间的可视化的工具 - Traceview。多亏这个我们可以看见每个方法花了多少时间,并且找出注入过程中的瓶颈。
顺便说一下,如果你以前没有见过它,可以在Udi Cohen的博客看下这篇Android性能优化相关的文章。
Traceview可以直接从Android Studio(Android Monitor tab -> CPU -> Start/Stop Method Tracing)启动,它有时并不是那么精确的,尤其是当我们尝试在app启动时点击Start。
对于我们而言,幸运的是当我们知道确切的需要被测量的代码位置时,有一个可以使用的方法。Debug.startMethodTracing()可以用来指定我们代码中需要被启动测量的位置。Debug.stopMethodTracing()停止追踪并且创建一个新的文件。
为了实践,我们测量了SplashActivity的注入过程:
@Override
protected void setupActivityComponent() {
Debug.startMethodTracing("SplashTrace");
GithubClientApplication.get(this)
.getAppComponent()
.plus(new SplashActivityModule(this))
.inject(this);
Debug.stopMethodTracing();
}
setupActivityComponent()是在onCreate()中调用的。
文档结果被保存在/sdcard/SplashTrace.trace中。
在你的terminal中把它pull出来:
$ adb pull /sdcard/SplashTrace.trace
现在阅读这个文件所要做的全部事情只是把它拖拽到Android Studio:

你应该会看到类似以下的东西:

当然,我们这个例子中的结果是非常清晰的:AppModule_ProvideHeavyExternalLibraryFactory.get()(HeavyExternalLibrary被创建的地方)花费了500ms。
真正好玩的地方是,缩放trace尾部的那一小块地方:

看到不同之处了吗?比如构建类:AnalyticsManager花了小于1ms。
如果你想看到它,这里有这个例子中的SplashTrace.trace文件。
解决方案
不幸的是,对于这类性能问题,有时并没有明确的回答。这里有两种方式会给我们很大的帮助。
懒加载(临时的解决方案)
首先,我们要思考是否你需要所有的注入依赖。也许其中一部分可以延迟一定时间后再加载?当然这并不解决真正的问题(UI线程将会在第一次调用Lazy<>.get()方法的时候阻塞)。但是在某些情况下对启动耗时有帮助(尤其是很少地方会使用到的一些对象)。查看Lazy<>接口文档获取更多的信息和例子代码。
简单说,每一个你使用@Inject SomeClass someClass的地方都可以替换成@Inject Lazy<SomeClass> someClassLazy(构造方法注入也是)。然后获取某个类的实例时必须要调用someClassLazy.get()。
异步对象创建
第二种选择(它仍然只是更多的想法而不是最终的解决方案)是在后台线程中的某处进行对象的初始化,缓存所有方法的调用并在初始化之后再回调它们。
这种方案的缺点是它必须要单独地准备我们要包含的所有类。并且它只有在方法调用可以被执行的将来(就像任何的analytics - 在一些事件被发生之后才可以),这些对象才可能正常工作。
以下就是我们的HeavyExternalLibrary使用这种解决方案后的样子:
public class HeavyLibraryWrapper {
private HeavyExternalLibrary heavyExternalLibrary;
private boolean isInitialized = false;
ConnectableObservable<HeavyExternalLibrary> initObservable;
public HeavyLibraryWrapper() {
initObservable = Observable.create(new Observable.OnSubscribe<HeavyExternalLibrary>() {
@Override
public void call(Subscriber<? super HeavyExternalLibrary> subscriber) {
HeavyLibraryWrapper.this.heavyExternalLibrary = new HeavyExternalLibrary();
HeavyLibraryWrapper.this.heavyExternalLibrary.init();
subscriber.onNext(heavyExternalLibrary);
subscriber.onCompleted();
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).publish();
initObservable.subscribe(new SimpleObserver<HeavyExternalLibrary>() {
@Override
public void onNext(HeavyExternalLibrary heavyExternalLibrary) {
isInitialized = true;
}
});
initObservable.connect();
}
public void callMethod() {
if (isInitialized) {
HeavyExternalLibrary.callMethod();
} else {
initObservable.subscribe(new SimpleObserver<HeavyExternalLibrary>() {
@Override
public void onNext(HeavyExternalLibrary heavyExternalLibrary) {
heavyExternalLibrary.callMethod();
}
});
}
}
}
当HeavyLibraryWrapper构造方法被调用,库的初始化会在后台线程(这里的Schedulers.io())中执行。在此期间,当用户调用callMethod(),它会增加一个新的subscription到我们的初始化过程中。当它完成时(onNext()方法返回一个已初始化的HeavyExternalLibrary对象)被缓存的回调会被传送到这个对象。
目前为止,这个想法还是非常简单并且仍然是在开发之中。这里可能会引起内存泄漏(比如,我们不得不在callMethod()方法中传入一些参数),但一般还是适用于简单的情况下的。
还有其它方案?
性能优化的过程是非常孤独的。但是如果你想要分享你的ideas,请在这里分享吧。
感谢你的阅读!
代码:
以上描述的完整代码可见Github repository。
作者
Head of Mobile Development @ Azimo
> __[Android]使用Dagger 2依赖注入 - DI介绍(翻译):__
> __[Android]使用Dagger 2依赖注入 - API(翻译):__
> __[Android]使用Dagger 2依赖注入 - 自定义Scope(翻译):__
> __[Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译):__
> __[Android]Dagger2Metrics - 测量DI图表初始化的性能(翻译):__
> __[Android]使用Dagger 2进行依赖注入 - Producers(翻译):__
> __[Android]在Dagger 2中使用RxJava来进行异步注入(翻译):__
> __[Android]使用Dagger 2来构建UserScope(翻译):__
> __[Android]在Dagger 2中Activities和Subcomponents的多绑定(翻译):__
[Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)的更多相关文章
- [Android]使用Dagger 2依赖注入 - DI介绍(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...
- [Android]使用Dagger 2依赖注入 - API(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html 使用Dagger 2依赖注入 - API ...
- [Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html 使用Dagger 2依赖注入 - 自定义 ...
- 依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...
- 依赖注入[4]: 创建一个简易版的DI框架[上篇]
本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...
- .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]
原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...
- Android 使用dagger2进行依赖注入(基础篇)
0. 前言 Dagger2是首个使用生成代码实现完整依赖注入的框架,极大减少了使用者的编码负担,本文主要介绍如何使用dagger2进行依赖注入.如果你不还不了解依赖注入,请看这一篇. 1. 简单的依赖 ...
- .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...
- .NET 中依赖注入组件 Autofac 的性能漫聊
Autofac 是一款超赞的 .NET IoC 容器 ,在众多性能测评中,它也是表现最优秀的一个.它管理类之间的依赖关系, 从而使 应用在规模及复杂性增长的情况下依然可以轻易地修改.它的实现方式是将常 ...
随机推荐
- CentOs7 +Jexus 5.8.2部署Asp.Net Core WebApi 1.0生产环境
Jexus 是一款运行于 Linux 平台,以支持 ASP.NET.PHP 为特色的集高安全性和高性能为一体的 WEB 服务器和反向代理服务器.最新版 5.8.2 已经发布,有如下更新: 1,现在大 ...
- 页面与ViewModel(下)
在上一篇博客中,笔者分享了一些从页面整体的角度对页面与ViewModel的思考.在本文中笔者希望从相对细节的角度分享一些对页面与ViewModel的思考. 比如,当我们在更新View Model中的绑 ...
- 探索c#之Async、Await剖析
阅读目录: 基本介绍 基本原理剖析 内部实现剖析 重点注意的地方 总结 基本介绍 Async.Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下. ...
- 2000条你应知的WPF小姿势 基础篇<34-39 Unhandled Exceptions和Resource>
在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000Things You Should Know About C# 和 2,0 ...
- Binder in Java
Android在Native层实现了进程间的Binder通信,但是上层应用程序的开发及Framework的实现都是Java,用Java层再实现一次肯定是不合理的,Java可以通过JNI调用Native ...
- Java基础之IO流
很长时间都没有更新了,最近在补充JavaSE的一些细节部分 关于IO流的一些总结 首先要介绍的是File类,File类用于对文件和目录的一些操作 1.创建文件CreateNewFile() 2.对文件 ...
- Android开发学习之路-动态高斯模糊怎么做
什么是高斯模糊? 高斯模糊(英语:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop.GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪 ...
- PHP安全之Web攻击
一.SQL注入攻击(SQL Injection) 攻击者把SQL命令插入到Web表单的输入域或页面请求的字符串,欺骗服务器执行恶意的SQL命令.在某些表单中,用户输入的内容直接用来构造(或者影响)动态 ...
- 【转】string.Format对C#字符串格式化
转自:http://blog.csdn.net/samsone/article/details/7556781 1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) str ...
- 计算机程序的思维逻辑 (43) - 剖析TreeMap
40节介绍了HashMap,我们提到,HashMap有一个重要局限,键值对之间没有特定的顺序,我们还提到,Map接口有另一个重要的实现类TreeMap,在TreeMap中,键值对之间按键有序,Tree ...