[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中它是怎样的:

  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介绍(翻译):

http://www.cnblogs.com/tiantianbyconan/p/5092083.html


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

http://www.cnblogs.com/tiantianbyconan/p/5092525.html


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

http://www.cnblogs.com/tiantianbyconan/p/5095426.html


[Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译):

http://www.cnblogs.com/tiantianbyconan/p/5098943.html


[Android]Dagger2Metrics - 测量DI图表初始化的性能(翻译):

http://www.cnblogs.com/tiantianbyconan/p/5193437.html


[Android]使用Dagger 2进行依赖注入 - Producers(翻译):

http://www.cnblogs.com/tiantianbyconan/p/6234811.html


[Android]在Dagger 2中使用RxJava来进行异步注入(翻译):

http://www.cnblogs.com/tiantianbyconan/p/6236646.html


[Android]使用Dagger 2来构建UserScope(翻译):

http://www.cnblogs.com/tiantianbyconan/p/6237731.html


[Android]在Dagger 2中Activities和Subcomponents的多绑定(翻译):

http://www.cnblogs.com/tiantianbyconan/p/6266442.html

本文转自天天_byconan博客园博客,原文链接:http://www.cnblogs.com/tiantianbyconan/p/5098943.html ,如需转载请自行联系原作者
相关文章
|
3月前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
67 10
|
20天前
|
算法 JavaScript Android开发
|
2月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
200 9
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
85 8
|
2月前
|
安全 Android开发 数据安全/隐私保护
安卓与iOS的对决:移动操作系统的性能与创新
在当今智能手机市场,安卓和iOS两大操作系统一直处于竞争状态。本文将深入探讨它们在性能、安全性和用户体验方面的不同,并分析这些差异如何影响用户的选择。
56 3
|
2月前
|
API Android开发 iOS开发
掌握安卓与iOS应用开发中的依赖注入技术
本文探讨了在安卓和iOS应用开发中,如何有效利用依赖注入技术来提升代码的模块化、可测试性和可维护性。通过对比分析两种平台下依赖注入的实现方式与工具,本文旨在为开发者提供一套清晰、实用的依赖管理策略,助力打造高质量软件产品。
|
2月前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
73 0
|
3月前
|
数据库 Android开发 开发者
打造高效安卓应用:从代码优化到性能提升
【8月更文挑战第2天】在移动设备的海洋中,安卓应用的效能直接关系到用户体验的好坏。本文旨在深入探讨如何通过代码层面的优化技巧和策略来提升安卓应用的性能。我们将一起探索减少内存消耗、避免不必要的CPU使用以及提高应用响应速度的方法。文章将结合具体的代码示例,为开发者提供可行的优化建议,帮助他们构建更流畅、更高效的安卓应用。
65 2
|
4月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
【7月更文挑战第28天】在 Android 开发中, NDK 让 Java 与 C++ 混合编程成为可能, 从而提升应用性能。**为何选 NDK?** C++ 在执行效率与内存管理上优于 Java, 特别适合高性能需求场景。**环境搭建** 需 Android Studio 和 NDK, 工具如 CMake。**JNI** 构建 Java-C++ 交互, 通过声明 `native` 方法并在 C++ 中实现。**实战** 示例: 使用 C++ 计算斐波那契数列以提高效率。**总结** 混合编程增强性能, 但增加复杂性, 使用前需谨慎评估。
141 4