[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

相關文章
相關標籤/搜索