以下内容为原创,欢迎转载,转载请注明

来自天天博客: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中它是怎样的:

  1. App第一次(被kill之后)被启动。Application对象并没有@Inject属性,所以只有AppComponent对象被创建。
  2. App创建了SplashActivity - 它有两个@Inject属性:AnalyticsManagerSplashActivityPresenter
  3. AnalyticsManager依赖已被创建的Application对象。所以只有AnalyticsManager构造方法被调用。
  4. SplashSctivityPresenter依赖:SplashActivityValidatorUserManagerSplashActivity已被提供,ValidatorUserManager应该被创建。
  5. UserManager依赖需要被创建的GithubApiService。之后UserManager被创建。
  6. 现在我们拥有了所有依赖,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

作者

Miroslaw Stanek

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依赖注入 - 图表创建的性能(翻译)的更多相关文章

  1. [Android]使用Dagger 2依赖注入 - DI介绍(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...

  2. [Android]使用Dagger 2依赖注入 - API(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html 使用Dagger 2依赖注入 - API ...

  3. [Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html 使用Dagger 2依赖注入 - 自定义 ...

  4. 依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...

  5. 依赖注入[4]: 创建一个简易版的DI框架[上篇]

    本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...

  6. .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  7. Android 使用dagger2进行依赖注入(基础篇)

    0. 前言 Dagger2是首个使用生成代码实现完整依赖注入的框架,极大减少了使用者的编码负担,本文主要介绍如何使用dagger2进行依赖注入.如果你不还不了解依赖注入,请看这一篇. 1. 简单的依赖 ...

  8. .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...

  9. .NET 中依赖注入组件 Autofac 的性能漫聊

    Autofac 是一款超赞的 .NET IoC 容器 ,在众多性能测评中,它也是表现最优秀的一个.它管理类之间的依赖关系, 从而使 应用在规模及复杂性增长的情况下依然可以轻易地修改.它的实现方式是将常 ...

随机推荐

  1. mono3.2.3+Jexus5.5+openSuSE13.1的asp.net

    读书的时候,我似乎有系统地学习过asp.net,但是基本已经还掉了...工作之后有做过一个内部用的网站,但也没有正式使用,的确只能算是个课程设计型的东西,不能做产品.后来工作需求是做Win8下的APP ...

  2. ABP框架理论研究总结(典藏版)

    目前,我已经完成了Module-Zero的翻译,请查看我的<Module-Zero学习目录>. 到现在为止,使用ABP框架开发正式项目已经3个月有余了,期间翻阅了大量文档资料,包括ABP官 ...

  3. C++虚函数和函数指针一起使用

    C++虚函数和函数指针一起使用,写起来有点麻烦. 下面贴出一份示例代码,可作参考.(需要支持C++11编译) #include <stdio.h> #include <list> ...

  4. Glide源码导读

    最近比较无聊,为了找点事干,就花了两天时间把Glide的源码大概看了一下.刚开始看Glide的源码头脑还是比较乱的,因为作者引入了几个概念,又大量用了泛型,如果不了解这些概念读起代码来就比较痛苦,我也 ...

  5. ABP源码分析三十一:ABP.AutoMapper

    这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...

  6. Entity Framework 6 Recipes 2nd Edition(9-5)译->删除一个断开的实体

    9-5. 删除一个断开的实体 问题 我们要把一个把WCF上取回的对象做上删除的标志. 解决方案 假设我们有如Figure 9-5所示实体的支付与票据的模型. Figure 9-5. 一个支付与票据的模 ...

  7. Atitit 软件架构方法的进化与演进cs bs soa roa  msa  attilax总结

    Atitit 软件架构方法的进化与演进cs bs soa roa  msa  attilax总结 1.1. 软件体系架构是沿着单机到 CS 架构,再到 BS 的三层架构甚至多层架构逐步发展过来的,关于 ...

  8. SqlService过期的解决方案

    看图吧,不喜欢说话,图里面我都打备注了 0SQLService异常 1找到安装中心 2升级版本 3监测ing 4输入升级key 5同意并下一步 6下一步 7下一步 8下一步 9收工 10可以打开了

  9. UploadFile控件,提交图片后,页面预览显示刚刚提交的图片

    最近在用asp.net来写一个新闻系统后台,然后由于不用用网上的flash插件来上传图片什么的,我就用asp.net的控件来写,但是控件总归有一些用的不够灵活的地方.这次测试提出,文章在修改的时候,需 ...

  10. MySql - InnoDB - 事务 , Php版

    (出处:http://www.cnblogs.com/linguanh/) 1,前序 由于要重构APP(社交类) 服务端接口的部分代码,故接触到了 innoDB,以及事务这个词,下面主要是以例子的形式 ...