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

来自天天博客: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. 最牛的打字效果JS插件 typing.js

    最新在做公司的一个项目,需要实现一个敲打代码的动画效果,粗意味比较简单,果断自己直接开写,写着写着发现是一个坑.需要支持语法高亮,并不能直接简单的用setTimeout来动态附件innerHTML.苦 ...

  2. ARM的栈指令

    ARM的指令系统中关于栈指令的内容比较容易引起迷惑,这是因为准确描述一个栈的特点需要两个参数: 栈地址的增长方向:ARM将向高地址增长的栈称为递增栈(Descendent Stack),将向低地址增长 ...

  3. ASP.NET Web API Model-ParameterBinding

    ASP.NET Web API Model-ParameterBinding 前言 通过上个篇幅的学习了解Model绑定的基础知识,然而在ASP.NET Web API中Model绑定功能模块并不是被 ...

  4. ASP.NET Web API Model-ValueProvider

    ASP.NET Web API Model-ValueProvider 前言 前面一篇讲解了Model元数据,Model元数据是在Model绑定中很重要的一部分,只是Model绑定中涉及的知识点比较多 ...

  5. Android开发学习之路-RecyclerView滑动删除和拖动排序

    Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...

  6. jQuery 根据值或者文本选中select

    今天因为有项目需要动态操作select选中 习惯在百度上搜了一下 ,结果还是挺多的.试了其中一个 发现不能使用.打开第2,3 个发现都是一样的然后自己稍微研究了一下 //初始化select,第一个选中 ...

  7. 00.Web大前端时代之:HTML5+CSS3入门系列~Bug反馈文章

    感谢广大网友的热心提醒,现已发现如下错误: 感谢 “ ”对画布笔记系列的反馈(QQ:350223285) 这个是失误,strokeStyle和stroke对应 待续.... 欢迎提出更多问题,感谢大家 ...

  8. 【Win 10 应用开发】加载外部的 srt 字幕

    据说系统内置的多媒体功能支持 srt. ssa 等字幕,老周测试过几种格式的字幕均能加载. SRT 字幕是最简单的字幕结构,甚至你用记事本都能做出来,就是分为几行来写. 第一行是字幕的编号,应该是从1 ...

  9. 爬虫技术 -- 进阶学习(十)网易新闻页面信息抓取(htmlagilitypack搭配scrapysharp)

    最近在弄网页爬虫这方面的,上网看到关于htmlagilitypack搭配scrapysharp的文章,于是决定试一试~ 于是到https://www.nuget.org/packages/Scrapy ...

  10. Java jacob调用打印机打印word文档

    前面说了Java如何生成复杂的Word文档,今年记录下Java如何调用打印机打印word文档. 起初用的是自带的PrintJob,但是系统提供的打印机制并不成熟完整.网上的代码也是千篇一律,在我的打印 ...