[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
)UserManager
Validator
SplashActivityPresenter
AnalyticsManager
一个接一个地被创建。
嘿,别担心,更复杂地图表也几乎被立即创建。
问题
现在让我们想象下,我们有两个外部的库需要在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 容器 ,在众多性能测评中,它也是表现最优秀的一个.它管理类之间的依赖关系, 从而使 应用在规模及复杂性增长的情况下依然可以轻易地修改.它的实现方式是将常 ...
随机推荐
- 《徐徐道来话Java》(1):泛型的基本概念
泛型是一种编程范式(Programming Paradigm),是为了效率和重用性产生的.由Alexander Stepanov(C++标准库主要设计师)和David Musser(伦斯勒理工学院CS ...
- 用lucene替代mysql读库的尝试
采用lucene对mysql中的表建索引,并替代全文检索操作. 备注:代码临时梳理很粗糙,后续修改. import java.io.File; import java.io.IOException; ...
- Java Spring mvc 操作 Redis 及 Redis 集群
本文原创,转载请注明:http://www.cnblogs.com/fengzheng/p/5941953.html 关于 Redis 集群搭建可以参考我的另一篇文章 Redis集群搭建与简单使用 R ...
- Linux Nano命令
Nano命令指南 今天在输命令时,无意中输入了nano,对这个命令不太熟悉,结果不知道如何才能退出,保存,赶快查了一下资料,原来是这样的啊. 打开文件与新建文件 使用nano打开或新建文件,只需键入: ...
- 将自己打代码添加到cocoapods
1,Github 上创建新站点 2, 从gitHub上 clone 一份,将源码拷贝到该目录下提交3,开源库发布之后,需要打上tag git tag 0.0.1 git push --tags git ...
- Code First Migrations
在MVC开发当中难免会对类进行修改,修改后再次运行就会出现异常,提示上下文的模型已在数据库创建后发生改变. 支持“AppContext”上下文的模型已在数据库创建后发生更改.请考虑使用 Code Fi ...
- 扎克伯格开发的家用AI: Jarvis
扎克伯格本周二在facebook发布了一篇文章,介绍自己利用个人时间开发的一套在自己家里使用的AI系统,并将它命名为Jarvis,对!就是电影钢铁侠里的AI助手Jarvis. 文章并没有讲细节的技术c ...
- TODO List - 任务表
TODO List - 任务表 Angular1 --> Ionic1 --> Vue --> Weex Python --> Django --> Tornado -- ...
- Unicode和UTF-8的关系
Unicode和UTF-8都是表示编码,这个我一直都知道,但是这两个实际上是干什么用的,到底是怎么编码的,为什么有了Unicode还要UTF-8,它们之间有什么联系又有什么区别呢?这个问题一直困扰着我 ...
- CSS画图
The Shapes of CSS All of the below use only a single HTML element. Any kind of CSS goes, as long as ...