[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介紹(翻譯):__

> __[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的多綁定(翻譯):__


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM