[Android]使用Dagger 2进行依赖注入

使用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

@Produces

@Provide@ProducerModule@ProducesListenableFuture

@ProductionComponent

@Component@ProducerModule@Component
@ProductionComponent

Producers的文档已经足够详细了,所以这里没有必要去拷贝到这里。直接看:Dagger 2 Producers docs。

Producers的代价

在我们开始实践前,有一些值得提醒的事情。Producers相比Dagger 2本身有一点更复杂。它看起来手机端app不是他们它们主要使用的目标,而且知道这些事情很重要:

ListenableFutures@Inject
@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 {<br/>
    url &#34;https://oss.sonatype.org/content/repositories/snapshots&#34;<br/>
}<br/>

}
//… dependencies {

//...

//Dagger 2

compile &#39;com.google.dagger:dagger:2.1-SNAPSHOT&#39;<br/>
compile &#39;com.google.dagger:dagger-producers:2.1-SNAPSHOT&#39;<br/>
apt &#39;com.google.dagger:dagger-compiler:2.1-SNAPSHOT&#39;

//…
}

org.glassfish:javax.annotation:10.0-b28

Producer Module

GithubApiModuleGithubApiProducerModule

GithubApiProducerModule.java

@ProducerModule
public class GithubApiProducerModule { @Produces

static OkHttpClient produceOkHttpClient() {<br/>
    final OkHttpClient.Builder builder = new OkHttpClient.Builder();<br/>
    if (BuildConfig.DEBUG) {<br/>
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();<br/>
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);<br/>
        builder.addInterceptor(logging);<br/>
    }

builder.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)

            .readTimeout(60 * 1000, TimeUnit.MILLISECONDS);

return builder.build();

}

@Produces

public Retrofit produceRestAdapter(Application application, OkHttpClient okHttpClient) {<br/>
    Retrofit.Builder builder = new Retrofit.Builder();<br/>
    builder.client(okHttpClient)<br/>
            .baseUrl(application.getString(R.string.endpoint))<br/>
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())<br/>
            .addConverterFactory(GsonConverterFactory.create());<br/>
    return builder.build();<br/>
}

@Produces

public GithubApiService produceGithubApiService(Retrofit restAdapter) {<br/>
    return restAdapter.create(GithubApiService.class);<br/>
}

@Produces

public UserManager produceUserManager(GithubApiService githubApiService) {<br/>
    return new UserManager(githubApiService);<br/>
}

@Produces

public UserModule.Factory produceUserModuleFactory(GithubApiService githubApiService) {<br/>
    return new UserModule.Factory(githubApiService);<br/>
}<br/>

}

看起来很像?没错,我们只是修改了:

@Module@ProducerModule@Provides @Singleton@Produces
UserModule.Factory

Production Component

@ProductionComponentUserManager
@ProductionComponent(

    dependencies = AppComponent.class,<br/>
    modules = GithubApiProducerModule.class<br/>

)
public interface AppProductionComponent {

ListenableFuture&lt;UserManager&gt; userManager();

ListenableFuture&lt;UserModule.Factory&gt; userModuleFactory();
}

又一次,非常类似原来的Dagger‘s @Component。

ProductionComponent的构建也是与标准的Component非常相似:

AppProductionComponent appProductionComponent = DaggerAppProductionComponent.builder()

.executor(Executors.newSingleThreadExecutor())<br/>
.appComponent(appComponent)<br/>
.build();<br/>

Executor

获取依赖

@Inject
appProductionComponent = splashActivity.getAppProductionComponent();
Futures.addCallback(appProductionComponent.userManager(), new FutureCallback&lt;UserManager&gt;() {

@Override<br/>
public void onSuccess(UserManager result) {<br/>
    SplashActivityPresenter.this.userManager = result;<br/>
}

@Override

public void onFailure(Throwable t) {

}
});

appProductionComponent.userManager()UserManager
Future.onSuccess()null

性能

在最后让我们来看下现在注入的性能是怎么样的:

是的,没错 - 这时平均值大约是15ms。它小于同步注入(平均. 25ms)但是并不如你期望的那样少。这时因为Producers并不像Dagger本身那样轻量。

所以现在取决于你了 - 是否值得使用Guava, Proguard和代码复杂度来做这种优化。

请记住,如果你觉得Producers并不是最适合你的app的,你可以在你的app中尝试使用RxJava或者其他异步代码来包装你的注入。

感谢阅读!

代码:

以上描述的完整代码可见Github repository。

作者

Head of Mobile Development @ Azimo

&gt; [Android]使用Dagger 2依赖注入 - DI介绍(翻译):

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

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

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

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

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

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

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

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