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


如下内容为原创,欢迎转载,转载请注明
来自每天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html
html

使用Dagger 2依赖注入 - 图表建立的性能

原文:http://frogermcs.github.io/dagger-graph-creation-performance/java

#PerfMatters - 最近很是流行标签,尤为在Android世界中。无论怎样,apps只须要正常工做就能够的时代已通过去了。如今全部的一切都应该是使人愉悦的,流畅而且快速。举个例子,Instagram 花费了半年的时间 只是让app更加快速,更加美观,和更好的屏幕适配性。android

这就是为何今天我想去分享给你一些小的建议,它会在你app启动时间上有很大的影响(尤为是当app使用了一些额外库的时候)。git

对象图表的建立

大多状况下,在app开发过程当中,它的启动时间或多或少会增长。有时随着一每天地开发它是很难被注意到的,可是当你把第一个版本和你能找到的最近的版本比较时区别就会相对比较大了。github

缘由极可能就在于dagger对象图表的建立过程。api

Dagger 2?你可能会问,确切地说 - 就算你移除了那些基于反射的实现方案,而且你的代码是在编译时期生成的,可是别忘了对象的建立仍然发生是在运行时。缓存

对象(还有它的依赖)会在第一次被注入时建立。Jake Wharton 在Dagger 2演示中的一些幻灯片很清楚地展现了这一点:性能优化

如下表示在咱们的 GithubClient 例子app中它是怎样的: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

相关文章
相关标签/搜索