以下內容為原創,歡迎轉載,轉載請註明 來自天天博客: 使用Dagger 2進行依賴註入 Producers 原文: 本文是在Android中使用Dagger 2框架進行依賴註入的系列文章中的一部分。今天我們將探索下Dagger Producers 使用Java實現非同步依賴註入的Dagger2的一個擴 ...
以下內容為原創,歡迎轉載,轉載請註明
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6234811.html
使用Dagger 2進行依賴註入 - Producers
原文:http://frogermcs.github.io/dependency-injection-with-dagger-2-producers/
本文是在Android中使用Dagger 2框架進行依賴註入的系列文章中的一部分。今天我們將探索下Dagger Producers - 使用Java實現非同步依賴註入的Dagger2的一個擴展。
初始化性能問題
我們都知道Dagger 2是一個優化得很好的依賴註入框架。但是即使有這些全部的微優化,仍然在依賴註入的時候存在可能的性能問題 - “笨重”的第三方庫和/或我們那些主線程阻塞的代碼。
依賴註入是在儘可能短的時間內在正確的地方傳遞所請求的依賴的過程 - 這些都是Dagger 2做得很好的。但是DI也會去創建各種依賴。如果我們需要花費幾百毫秒創建它們,那麼以納秒級的時間去提供依賴還有什麼意義呢?
當我們的app創建了一系列繁重的單例並立即由Dagger2提供服務之後也許可能沒有這麼重要。但是在我們創建它們的時候仍然需要一個時間成本 - 大多數情況下決定了app啟動的時間。
這問題(已經給了提示怎麼去調適它)已經在我之前的一篇博客中描述地很詳細了:Dagger 2 - graph creation performance。
在很短的時間內,讓我們想象這麼一個場景 - 你的app有一個初始化的界面(SplashScreen),需要在app啟動後立即做一些需要的事情:
- 初始化所有tracking libs(Goole Analytics, Crashlytics)然後發送第一份數據給它們。
- 創建用於API和/或資料庫通信的整個棧。
- 我們試圖的交互邏輯(MVP中的Presenters,MVVM中的ViewModels等等)。
即使我們的代碼是優化地非常好的,但是仍然有可能有些額外的庫需要幾十或者幾百毫秒的時間來初始化。在我們啟動界面之前將展示必須初始化和交付的所有請求的依賴(和它們的依賴)。這意味著啟動時間將會是它們每一個初始化時間的總和。
由 AndroidDevMetrics 測量的示例堆棧可能如下所示:
用戶將會在600ms(+額外的系統work)內看到SplashActivity - 所有初始化時間的總和。
Producers - 非同步依賴註入
Dagger 2 有一個名為 Producers 的擴展,或多或少能為我們解決這些問題。
思路很簡單 - 整個初始化流程可以在一個或多個後臺線程中被執行,然後延後再交付給app的主線程。
@ProducerModule
類似於@Module
,這個被用來標記用於傳遞依賴的類。多虧於它,Dagger將會知道去哪裡找到被請求的依賴。
@Produces
類似於@Provide
,這個註解用來標記帶有@ProducerModule
註解的類中的返回依賴的方法。@Produces
註解的方法可以返回ListenableFuture<T>
或者自身的對象(也會在所給的後臺線程中進行初始化)。
@ProductionComponent
類似於@Component
,它負責依賴的傳遞。它是我們代碼與@ProducerModule
之間的橋梁。唯一跟@Component
的不同之處是我們不能決定依賴的scope。這意味著提供給 component 的每一個 Produces 方法 在 每個component 實例 中最多只會被調用一次,不管它作為一個 依賴 用於多少次綁定。
也就是說,每一個服務於@ProductionComponent
的對象都是一個單例(只要我們從這個特殊的component中獲取)。
Producers的文檔已經足夠詳細了,所以這裡沒有必要去拷貝到這裡。直接看:Dagger 2 Producers docs。
Producers的代價
在我們開始實踐前,有一些值得提醒的事情。Producers相比Dagger 2本身有一點更複雜。它看起來手機端app不是他們它們主要使用的目標,而且知道這些事情很重要:
- Producers使用了Guava庫,並且建立在ListenableFuture類之上。這意味著你不得不處理15k的額外方法在你的app中。這可能導致你不得不使用Proguard來處理並且需要一個更長的編譯時間。
- 就如你將看到的,創建
ListenableFutures
並不是沒有成本的。所以如果你指望Producers會幫你從10ms優化到0ms那你可能就錯了。但是如果規模更大(100ms --> 10ms),你就能有所發現。 - 現在無法使用
@Inject
註解,所以你必須要手動處理ProductionComponents。它會使得你的標準整潔的代碼變得混亂。
這裡你可以針對@Inject
註解找到好的間接的解決方案的嘗試。
Example app
如果你仍然希望使用Producers來處理,那就讓我們更新 GithubClient 這個app使得它在註入過程使用Producers。在實現之前和之後我們將會使用 AndroidDevMetrics 來測量啟動時間和對比結果。
這裡是一個在使用producers更新之前的 GithubClient app的版本。並且它測量的平均啟動時間如下:
我們的計劃是處理UserManager讓它的所有的依賴來自Producers。
配置
我們將給一個Dagger v2.1的嘗試(但是當前2.0版本的Producers也是可用的)。
讓我們在項目中加入一個Dagger新的版本:
app/build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'com.frogermcs.androiddevmetrics'
repositories {
maven {
url "https://oss.sonatype.org/content/repositories/snapshots"
}
}
//...
dependencies {
//...
//Dagger 2
compile 'com.google.dagger:dagger:2.1-SNAPSHOT'
compile 'com.google.dagger:dagger-producers:2.1-SNAPSHOT'
apt 'com.google.dagger:dagger-compiler:2.1-SNAPSHOT'
//...
}
如你所見,Producers 作為一個新的依賴,在dagger 2庫的下麵。還有值得一說的是Dagger v2.1終於不需要org.glassfish:javax.annotation:10.0-b28
的依賴了。
Producer Module
現在,讓我們移動代碼從GithubApiModule
到新創建的GithubApiProducerModule
中。原來的代碼可以在這裡找到:GithubApiModule
GithubApiProducerModule.java
@ProducerModule
public class GithubApiProducerModule {
@Produces
static OkHttpClient produceOkHttpClient() {
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logging);
}
builder.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(60 * 1000, TimeUnit.MILLISECONDS);
return builder.build();
}
@Produces
public Retrofit produceRestAdapter(Application application, OkHttpClient okHttpClient) {
Retrofit.Builder builder = new Retrofit.Builder();
builder.client(okHttpClient)
.baseUrl(application.getString(R.string.endpoint))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
return builder.build();
}
@Produces
public GithubApiService produceGithubApiService(Retrofit restAdapter) {
return restAdapter.create(GithubApiService.class);
}
@Produces
public UserManager produceUserManager(GithubApiService githubApiService) {
return new UserManager(githubApiService);
}
@Produces
public UserModule.Factory produceUserModuleFactory(GithubApiService githubApiService) {
return new UserModule.Factory(githubApiService);
}
}
看起來很像?沒錯,我們只是修改了:
@Module
改為@ProducerModule
@Provides @Singleton
改為@Produces
。你還記得嗎?在Producers中我們預設就有一個單例
UserModule.Factory
依賴只是因為app的邏輯原因而添加。
Production Component
現在讓我們創建@ProductionComponent
,它將會為UserManager
實例提供服務:
@ProductionComponent(
dependencies = AppComponent.class,
modules = GithubApiProducerModule.class
)
public interface AppProductionComponent {
ListenableFuture<UserManager> userManager();
ListenableFuture<UserModule.Factory> userModuleFactory();
}
又一次,非常類似原來的Dagger's @Component。
ProductionComponent的構建也是與標準的Component非常相似:
AppProductionComponent appProductionComponent = DaggerAppProductionComponent.builder()
.executor(Executors.newSingleThreadExecutor())
.appComponent(appComponent)
.build();
額外附加的參數是Executor
實例,它告訴ProductionComponent依賴應該在哪裡(哪個線程)被創建。在我們的例子中我們使用了一個single-thread executor,但是當然增加並行級別並使用多線程執行不是一個問題。
獲取依賴
就像我說的,當前我們不能去使用@Inject
註解。相反,我們必須直接詢問ProductionComponent(你可以在SplashActivityPresenter找到這些代碼):
appProductionComponent = splashActivity.getAppProductionComponent();
Futures.addCallback(appProductionComponent.userManager(), new FutureCallback<UserManager>() {
@Override
public void onSuccess(UserManager result) {
SplashActivityPresenter.this.userManager = result;
}
@Override
public void onFailure(Throwable t) {
}
});
這裡重要的是,對象初始化是在你第一次調用appProductionComponent.userManager()
的時候開始的。在這之後UserManager
對象將會被緩存。這表示每一個綁定都擁有跟component實例相同的生命周期。
以上幾乎就是所有了。當然你應該知道在Future.onSuccess()
方法被調用之前userManager實例會時null
。
性能
在最後讓我們來看下現在註入的性能是怎麼樣的:
是的,沒錯 - 這時平均值大約是15ms。它小於同步註入(平均. 25ms)但是並不如你期望的那樣少。這時因為Producers並不像Dagger本身那樣輕量。
所以現在取決於你了 - 是否值得使用Guava, Proguard和代碼複雜度來做這種優化。
請記住,如果你覺得Producers並不是最適合你的app的,你可以在你的app中嘗試使用RxJava或者其他非同步代碼來包裝你的註入。
感謝閱讀!
代碼:
以上描述的完整代碼可見Github repository。
作者
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