本文是在 Android 中使用 Dagger 2 框架进行依赖注入的系列文章中的一部分。今天我们将探索下 Dagger Producers - 使用 Java 实现异步依赖注入的 Dagger2 的一个扩展。
我们都知道 Dagger 2 是一个优化得很好的依赖注入框架。但是即使有这些全部的微优化,仍然在依赖注入的时候存在可能的性能问题 - "笨重" 的第三方库和 / 或我们那些主线程阻塞的代码。
依赖注入是在尽可能短的时间内在正确的地方传递所请求的依赖的过程 - 这些都是 Dagger 2 做得很好的。但是 DI 也会去创建各种依赖。如果我们需要花费几百毫秒创建它们,那么以纳秒级的时间去提供依赖还有什么意义呢?
当我们的 app 创建了一系列繁重的单例并立即由 Dagger2 提供服务之后也许可能没有这么重要。但是在我们创建它们的时候仍然需要一个时间成本 - 大多数情况下决定了 app 启动的时间。
这问题(已经给了提示怎么去调适它)已经在我之前的一篇博客中描述地很详细了:。
在很短的时间内,让我们想象这么一个场景 - 你的 app 有一个初始化的界面(SplashScreen),需要在 app 启动后立即做一些需要的事情:
即使我们的代码是优化地非常好的,但是仍然有可能有些额外的库需要几十或者几百毫秒的时间来初始化。在我们启动界面之前将展示必须初始化和交付的所有请求的依赖(和它们的依赖)。这意味着启动时间将会是它们每一个初始化时间的总和。
由 测量的示例堆栈可能如下所示:
用户将会在 600ms(+额外的系统 work)内看到 SplashActivity - 所有初始化时间的总和。
Dagger 2 有一个名为 Producers 的扩展,或多或少能为我们解决这些问题。
思路很简单 - 整个初始化流程可以在一个或多个后台线程中被执行,然后延后再交付给 app 的主线程。
类似于
,这个被用来标记用于传递依赖的类。多亏于它,Dagger 将会知道去哪里找到被请求的依赖。
- @Module
类似于
,这个注解用来标记带有
- @Provide
注解的类中的返回依赖的方法。
- @ProducerModule
注解的方法可以返回
- @Produces
或者自身的对象(也会在所给的后台线程中进行初始化)。
- ListenableFuture<T>
类似于
,它负责依赖的传递。它是我们代码与
- @Component
之间的桥梁。唯一跟
- @ProducerModule
的不同之处是我们不能决定依赖的 scope。这意味着提供给 的每一个 在 中最多只会被调用一次,不管它作为一个 用于多少次绑定。
- @Component
也就是说,每一个服务于
的对象都是一个单例(只要我们从这个特殊的 component 中获取)。
- @ProductionComponent
Producers 的文档已经足够详细了,所以这里没有必要去拷贝到这里。直接看:。
在我们开始实践前,有一些值得提醒的事情。Producers 相比 Dagger 2 本身有一点更复杂。它看起来手机端 app 不是他们它们主要使用的目标,而且知道这些事情很重要:
并不是没有成本的。所以如果你指望 Producers 会帮你从 10ms 优化到 0ms 那你可能就错了。但是如果规模更大(100ms --> 10ms),你就能有所发现。
- ListenableFutures
注解,所以你必须要手动处理 ProductionComponents。它会使得你的标准整洁的代码变得混乱。
- @Inject
你可以针对
注解找到好的间接的解决方案的尝试。
- @Inject
如果你仍然希望使用 Producers 来处理,那就让我们更新 这个 app 使得它在注入过程使用 Producers。在实现之前和之后我们将会使用 来测量启动时间和对比结果。
是一个在使用 producers 更新之前的 GithubClient app 的版本。并且它测量的平均启动时间如下:
我们的计划是处理 UserManager 让它的所有的依赖来自 Producers。
我们将给一个 Dagger v2.1 的尝试(但是当前 2.0 版本的 Producers 也是可用的)。
让我们在项目中加入一个 Dagger 新的版本:
- 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
现在,让我们移动代码从
到新创建的
- GithubApiModule
中。原来的代码可以在这里找到:
- GithubApiProducerModule
- @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
依赖只是因为 app 的逻辑原因而添加。
- UserModule.Factory
现在让我们创建
,它将会为
- @ProductionComponent
实例提供服务:
- UserManager
- @ProductionComponent(
- dependencies = AppComponent.class,
- modules = GithubApiProducerModule.class
- )
- public interface AppProductionComponent {
- ListenableFuture userManager();
- ListenableFutureFactory> userModuleFactory();
- }
又一次,非常类似原来的。
ProductionComponent 的构建也是与标准的 Component 非常相似:
- AppProductionComponent appProductionComponent = DaggerAppProductionComponent.builder()
- .executor(Executors.newSingleThreadExecutor())
- .appComponent(appComponent)
- .build();
额外附加的参数是
实例,它告诉 ProductionComponent 依赖应该在哪里(哪个线程)被创建。在我们的例子中我们使用了一个 single-thread executor,但是当然增加并行级别并使用多线程执行不是一个问题。
- Executor
就像我说的,当前我们不能去使用
注解。相反,我们必须直接询问 ProductionComponent(你可以在找到这些代码):
- @Inject
- appProductionComponent = splashActivity.getAppProductionComponent();
- Futures.addCallback(appProductionComponent.userManager(), new FutureCallback() {
- @Override
- public void onSuccess(UserManager result) {
- SplashActivityPresenter.this.userManager = result;
- }
- @Override
- public void onFailure(Throwable t) {
- }
- });
这里重要的是,对象初始化是在你第一次调用
的时候开始的。在这之后
- appProductionComponent.userManager()
对象将会被缓存。这表示每一个绑定都拥有跟 component 实例相同的生命周期。
- UserManager
以上几乎就是所有了。当然你应该知道在
方法被调用之前 userManager 实例会时
- Future.onSuccess()
。
- null
在最后让我们来看下现在注入的性能是怎么样的:
是的,没错 - 这时平均值大约是 15ms。它小于同步注入(平均. 25ms)但是并不如你期望的那样少。这时因为 Producers 并不像 Dagger 本身那样轻量。
所以现在取决于你了 - 是否值得使用 Guava, Proguard 和代码复杂度来做这种优化。
请记住,如果你觉得 Producers 并不是最适合你的 app 的,你可以在你的 app 中尝试使用 RxJava 或者其他异步代码来包装你的注入。
来源: