使用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 2compile '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 {@Producesstatic 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();}@Producespublic 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();}@Producespublic GithubApiService produceGithubApiService(Retrofit restAdapter) {return restAdapter.create(GithubApiService.class);}@Producespublic UserManager produceUserManager(GithubApiService githubApiService) {return new UserManager(githubApiService);}@Producespublic 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>() {@Overridepublic void onSuccess(UserManager result) {SplashActivityPresenter.this.userManager = result;}@Overridepublic void onFailure(Throwable t) {}
});

这里重要的是,对象初始化是在你第一次调用appProductionComponent.userManager()的时候开始的。在这之后UserManager对象将会被缓存。这表示每一个绑定都拥有跟component实例相同的生命周期。

以上几乎就是所有了。当然你应该知道在Future.onSuccess()方法被调用之前userManager实例会时null

性能

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

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

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

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

感谢阅读!

代码:

以上描述的完整代码可见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

本文转自天天_byconan博客园博客,原文链接:

http://www.cnblogs.com/tiantianbyconan/p/6234811.html

,如需转载请自行联系原作者

[Android]使用Dagger 2进行依赖注入 - Producers(翻译)相关推荐

  1. 用 Dagger 2 实现依赖注入

    原文地址:Dependency Injection with Dagger 2 原文作者:CodePath 译文出自:掘金翻译计划 译者: tanglie1993 校对者:mnikn, Zhiw 用 ...

  2. [Android]使用Dagger 2依赖注入 - DI介绍(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...

  3. [Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html 使用Dagger 2依赖注入 - 自定义 ...

  4. [Android]在Dagger 2中使用RxJava来进行异步注入(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6236646.html 在Dagger 2中使用RxJava来进 ...

  5. 依赖注入利器 - Dagger ‡

    转载请标明出处:http://blog.csdn.net/shensky711/article/details/53715960 本文出自: [HansChen的博客] 概述 声明需要注入的对象 如何 ...

  6. 从 Dagger 到 Hilt,谷歌为何执着于让我们用依赖注入?

    来源 | 扔物线 责编 | Carol 文章开始之前,首先来看个视频: 开始 说到依赖注入,做 Android 的人都会想到一个库:Dagger:说到 Dagger,大家的反应普遍是一套三连:牛逼.高 ...

  7. Android mock for循环,Android单元测试(五):依赖注入,将mock方便的用起来

    在上一篇文章中,咱们讲了要将mock出来的dependency真正使用起来,须要在测试环境下经过某种方式set 到用到它的那个对象里面进去,替换掉真实的实现.咱们前面举的例子是:html public ...

  8. Android神匕首—Dagger2依赖注入框架详解

    简介 Dagger-匕首,鼎鼎大名的Square公司旗下又一把利刃(没错!还有一把黄油刀,唤作ButterKnife) Dagger2 是一个Android依赖注入框架,由谷歌开发,最早的版本Dagg ...

  9. Android单元测试(五):依赖注入,将mock方便的用起来

    在上一篇文章中,我们讲了要将mock出来的dependency真正使用起来,需要在测试环境下通过某种方式set 到用到它的那个对象里面进去,替换掉真实的实现.我们前面举的例子是: public cla ...

最新文章

  1. 快速搭建第一个Mybatis程序
  2. 喜讯不断,BCH又迎来两个代币发行方案
  3. 2021牛客暑期多校训练营3A-Guess and lies【dp】
  4. JavaBean和Servlet
  5. QT 010 Qt 4.2 在线手册含UML图解释 User's Guide Documentation
  6. 第一次搭建vue项目--添加依赖包、启动项目
  7. python读取数据文件、并把里面的数据变成x的二维坐标_(数据科学学习手札60)用Python实现WGS84、火星坐标系、百度坐标系、web墨卡托四种坐标相互转换...
  8. linux重定向串口打印到telnet
  9. Linux C++ socket编程实例
  10. Unity3d学习之路-简单打飞碟小游戏
  11. 数据备份与恢复、系统备份与恢复
  12. Android 自动点击工具,安卓自动点击器免费版
  13. html渐变编织背景,CSS hover背景/文字渐变效果
  14. 代码弱鸡竟然在CSDN写烘焙博客
  15. gms认证流程_谷歌GMS测试认证具体流程
  16. 福建师范大学网络教育学院 计算机应用基础 第三次作业,福建师范大学网络教育学院_《计算机应用基础》第三次作业...
  17. 2020-2021年度第2学期课程回顾总结
  18. 奇数点偶数点fft的matlab,奇偶链表(奇数节点位于偶数节点之前) Odd Even Linked List...
  19. EVILBOX: ONE靶机
  20. 计算机基础:源代码如何被计算机执行

热门文章

  1. WINDOWS下与LINUX下写C程序的区别
  2. Java数据保留小数
  3. 御龙在天找回以前的服务器,奋起直追!掉队系统找回经验
  4. php文件教程,php的文件上传入门教程(实例讲解)
  5. .net 事件委托 java_仿net事件委托的java事件模型实现(转csdn)
  6. php扩展dio,PHP Dio扩展新函数dio_fdopen参数返回--bad file descriptor的分
  7. mysql 恢复数据库 source_mysql数据库备份及恢复命令 mysqldump,source的用法 | 很文博客...
  8. android多音字排序,Android拼音排序
  9. win10卸载电脑管家就蓝屏_win10 动不动就蓝屏,都不敢用啦。该怎么解决呢?
  10. flink 写kafka_网易云音乐基于 Flink + Kafka 的实时数仓建设实践