[Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)
以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html
使用Dagger 2依赖注入 - 自定义Scope
原文:http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
这章是展示使用Dagger 2在Android端实现依赖注入的系列中的一部分。今天我会花点时间在自定义Scope(作用域)上面 - 它是很实用,但是对于刚接触依赖注入的人会有一点困难。
Scope - 它给我们带来了什么?
几乎所有的项目都会用到单例 - 比如API clients,database helpers,analytics managers等。因为我们不需要去关心实例化(由于依赖注入),我们不应该在我们的代码中考虑关于怎么得到这些对象。取而代之的是@Inject
注解应该提供给我们适合的实例。
在Dagger 2中,Scope机制可以使得在scope存在时保持类的单例。在实践中,这意味着被限定范围为@ApplicationScope
的实例与Applicaiton对象的生命周期一致。@ActivityScope
保证引用与Activity的生命周期一致(举个例子我们可以在这个Activity中持有的所有fragment之间分享一个任何类的单例)。
简单来说 - scope给我们带来了“局部单例”,生命周期取决于scope自己。
但是需要弄清楚的是 - Dagger 2默认并不提供@ActivityScope
或者/并且 @ApplicationScope
这些注解。这些只是最常用的自定义Scope。只有@Singleton
scope是默认提供的(由Java自己提供)。
Scope - 实践案例
为了更好地去理解Dagger 2中的scope,我们直接进入实践案例。我们将要去实现比Application/Activity scope更加复杂一点的scope。为此我们将使用 上一文章 中的 GithubClient 例子。我们的app应需要三种scope:
- @Sigleton - application scope
- @UserScope - 用于与被选中的用户联系起来的类实例的scope(在真实的app中可以是当前登录的用户)。
- @ActivityScope - 生命周期与Activity(在我们例子中的呈现者)一致的实例的scope
讲解的@UserScope
是今天方案与以前文章之间的主要的不同之处。从用户体验的角度来说它没有带给我们任何东西,但是从架构的观点来说它帮助我们在不传入任何意图参数的情况下提供了User实例。使用方法参数获取用户数据的类(在我们的例子中是RepositoriesManager
)中我们可以通过构造参数(它将通过依赖图表提供)的方式来获取User实例并在需要的时候被初始化,而不是在app启动的时候创建它。这意味着RepositoriesManager
可以在我们从Github API获取到用户信息(在RepositoriesListActivity
呈现之前)之后被创建。
这里有个我们app中scopes和components呈现的简单图表。
单例(Application scope)是最长的scope(在实践中是与application一样长)。UserScope作为Application scope的一个子集scope,它可以访问它的对象(我们可以从父scope中得到对象)。ActivityScope(生命周期与Activity一致)也是如此 - 它可以从UserScope和ApplicationScope中得到对象。
Scope生命周期的例子
这里有一个我们app中scope生命周期的案例:
单例的生命周期是从app启动后的所有的时期,当我们从Github API(在真实app中是用户登录之后)得到了User
实例时UserScope被创建了,然后当我们回退到SplashActivity(在真实app中是用户退出之后)时被销毁。
实现
在Dagger 2中,Scope的实现归结于对Components的一个正确的设置。一般情况下我们有两种方式 - 使用Subcomponent
注解或者使用Components依赖。它们两者最大的区别就是对象图表的共享。Subcomponents可以访问它们parent的所有对象图表,而Component依赖只能访问通过Component接口暴露的对象。
我选择第一种使用 @Subcomponent
注解,如果你之前使用过Dagger 1,它几乎与从ObjectGraph
创建一个subgraphs(子图表)是一样的。此外,对于创建一个subgraphs的方法我们会使用类似的命名法则(但这不是强制性的)。
我们从AppComponent
的实现开始:
@Singleton
@Component(
modules = {
AppModule.class,
GithubApiModule.class
}
)
public interface AppComponent {
UserComponent plus(UserModule userModule);
SplashActivityComponent plus(SplashActivityModule splashActivityModule);
}
它将会是其它subcomponents的根Components:UserComponent
和Activities Components。正如你注意到的那样(尤其如果你在前面的文章中看过的AppComponent 实现)所有的返回依赖图表对象的公开方法全部消失了。因为我们有subcomponents了,我们不需要去公开去暴露依赖了 - 无论如何subgraphs都可以访问它们全部了。
作为替代,我们新增了两个方法:
UserComponent plus(UserModule userModule);
SplashActivityComponent plus(SplashActivityModule splashActivityModule);
这表示,我们可以从AppComponent
创建两个子Components(subcomponents):UserComponent
和SplashActivityComponent
。因为它们都是AppComponent的subcomponents,所以它们两者都可以访问AppModule
和GithubApiModule
创建的实例。
这些方法的命名法则是:返回类型是subcomponent类,方法名字随意,参数是这个subcomponent需要的modules。
如你所见,UserComponent
需要另一个module(它通过plus()
方法的参数传入)。这样,我们通过增加一个新的用于生成对象的module,继承AppComponent
图表。UserComponent
类看起来这样:
@UserScope
@Subcomponent(
modules = {
UserModule.class
}
)
public interface UserComponent {
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}
当然@UserScope
注解是我们自己创建的:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
我们可以从UserComponent
创建另外两个subcomponents:RepositoriesListActivityComponent
和RepositoryDetailsActivityComponent
。
@UserScope
@Subcomponent(
modules = {
UserModule.class
}
)
public interface UserComponent {
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}
并且更重要的是所有scope的东西都发生在这里。所有UserComponent
中从AppComponent
继承过来的仍然shi是单例的(是 Applicaton scope)。但是UserModule
(UserComponent
的那部分)创建的对象将会是“局部单例”,它的生命周期跟UserComponent
实例是一样的。
所以,每次一创建另一个UserComponent
实例将会调用:
UserComponent appComponent = appComponent.plus(new UserModule(user))
从UserModule
中获取的对象将是不同的实例。
但是这里很重要的一点是 - 我们要负责UserComponent
的生命周期。所以我们应该关心它的初始化和释放。在我们的例子中,我为它增加了两个额外的方法:
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
//...
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
//...
}
createUserComponent()
方法会在我们从Github API(在SplashActivity
中)获取到User
对象时调用。releaseUserComponent()
方法会在我们从RepositoriesListActivity
(这个时候我们不再需要user scope了)中返回时调用。
Dagger 2中的Scope - 内部实现
查看它的内部的工作原理是很不错的。通常在这种情况下可以确定,在Dagger 2的scope机制下并不存在什么魔法。
我们从UserModule.provideRepositoriesManager()
方法开始研究。它提供了RepositoriesManager
实例,它应该使用@UserScope
Scope。我们来检验这个方法哪里被调用(第8行):
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class UserModule_ProvideRepositoriesManagerFactory implements Factory<RepositoriesManager> {
//...
@Override
public RepositoriesManager get() {
RepositoriesManager provided = module.provideRepositoriesManager(userProvider.get(), githubApiServiceProvider.get());
if (provided == null) {
throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
}
return provided;
}
public static Factory<RepositoriesManager> create(UserModule module, Provider<User> userProvider, Provider<GithubApiService> githubApiServiceProvider) {
return new UserModule_ProvideRepositoriesManagerFactory(module, userProvider, githubApiServiceProvider);
}
}
UserModule_ProvideRepositoriesManagerFactory
仅仅是一个工厂模式的现实,它从UserModule
中获取到RepositoriesManager
实例。我们应该往更深层次挖掘。
UserModule_ProvideRepositoriesManagerFactory
在UserComponentImpl
中被使用 - 我们component的实现(line 15):
private final class UserComponentImpl implements UserComponent {
//...
private UserComponentImpl(UserModule userModule) {
if (userModule == null) {
throw new NullPointerException();
}
this.userModule = userModule;
initialize();
}
private void initialize() {
this.provideUserProvider = ScopedProvider.create(UserModule_ProvideUserFactory.create(userModule));
this.provideRepositoriesManagerProvider = ScopedProvider.create(UserModule_ProvideRepositoriesManagerFactory.create(userModule, provideUserProvider, DaggerAppComponent.this.provideGithubApiServiceProvider));
}
//...
}
provideRepositoriesManagerProvider
对象在我们每次请求它时负责提供RepositoriesManager
实例。如我们所见,provider是通过ScopedProvider
实现的。来看下它的部分代码:
public final class ScopedProvider<T> implements Provider<T> {
//...
private ScopedProvider(Factory<T> factory) {
assert factory != null;
this.factory = factory;
}
@SuppressWarnings("unchecked") // cast only happens when result comes from the factory
@Override
public T get() {
// double-check idiom from EJ2: Item 71
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
instance = result = factory.get();
}
}
}
return (T) result;
}
//...
}
再简单不过了吧?第一次调用ScopedProvider
从factory(我们的例子中是UserModule_ProvideRepositoriesManagerFactory
)中获取实例并像单例模式一样存储起来。我们的scoped provider只是UserComponentImpl
中的一个属性,所以简单说就是ScopedProvider
返回一个与依赖于Component的单例。
在这里你可以查看 ScopedProvider 的所有的实现。
就是这样。我们弄清楚了Dagger 2中Scope底层是怎么工作的。现在我们知道,它们没有以任何方式于Scope注解连接。自定义注解只是给了我们一个简单的方式来进行编译时代码校验和标记一个类是单例/非单例。所有的scope相关东西都是与Component的生命周期相关联。
以上就是今天的全部内容。我希望从现在开始scopes会变得更加容易使用。感谢阅读!
代码:
以上描述的完整代码可见Github repository。
作者
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的多绑定(翻译):__
[Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)的更多相关文章
- [Android]使用Dagger 2依赖注入 - API(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html 使用Dagger 2依赖注入 - API ...
- [Android]使用Dagger 2依赖注入 - DI介绍(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...
- [Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html 使用Dagger 2依赖注入 - 图表创 ...
- [Android]使用Dagger 2进行依赖注入 - Producers(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6234811.html 使用Dagger 2进行依赖注入 - P ...
- [Android]使用Dagger 2来构建UserScope(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6237731.html 使用Dagger 2来构建UserSco ...
- Android 使用dagger2进行依赖注入(基础篇)
0. 前言 Dagger2是首个使用生成代码实现完整依赖注入的框架,极大减少了使用者的编码负担,本文主要介绍如何使用dagger2进行依赖注入.如果你不还不了解依赖注入,请看这一篇. 1. 简单的依赖 ...
- Java 控制反转和依赖注入模式【翻译】【整理】
Inversion of Control Containers and the Dependency Injection pattern --Martin Fowler 本文内容 Component ...
- .Net Core 学习依赖注入自定义Service
1. 定义一个服务,包含一个方法 public class TextService { public string Print(string m) { return m; } } 2. 写一个扩展方法 ...
- [Android]在Dagger 2中使用RxJava来进行异步注入(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客: # 在Dagger 2中使用RxJava来进行异步注入 > 原文: 几星期前我写了一篇关于在Dagger 2中使用*Producers*进行 ...
随机推荐
- 乐乎环球WiFi
乐乎环球WiFi招商加盟 随身WiFi设备 乐乎环球Wifi是由北京蔚蓝创智科技有限公司研发的产品,是一款可以在全球100多个国家和地区实现免漫游4G高速上网的随身WiFi设备.和普通MiFi设备相比 ...
- 将MPM雪模拟移植到Maya
同事实现了一个迪士尼的MPM雪模拟论文,我将其移植到Maya中 论文题目是 A material point method for snow simulation 代码在这里: https://git ...
- 腾讯云 安装mono
一.yum -y update 运行出现以下错误: http://centos.tencentyun.com/contrib/x86_64/repodata/filelists.xml.gz: [Er ...
- mono-3.4.0 源码安装时出现的问题 [do-install] Error 2 [install-pcl-targets] Error 1 解决方法
Mono 3.4修复了很多bug,继续加强稳定性和性能(其实Mono 3.2.8 已经很稳定,性能也很好了),但是从http://download.mono-project.com/sources/m ...
- Web API 入门指南 - 闲话安全
Web API入门指南有些朋友回复问了些安全方面的问题,安全方面可以写的东西实在太多了,这里尽量围绕着Web API的安全性来展开,介绍一些安全的基本概念,常见安全隐患.相关的防御技巧以及Web AP ...
- SQL Server数据库损坏、检测以及简单的修复办法
简介 在一个理想的世界中,不会存在任何数据库的损坏,就像我们不会将一些严重意外情况列入我们生活中的日常一样,而一旦这类事情发生,一定会对我们的生活造成非常显著的影响,在SQL Server中也 ...
- ABP(现代ASP.NET样板开发框架)系列之7、ABP Session管理
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之7.ABP Session管理 ABP是“ASP.NET Boilerplate Project (ASP.NET ...
- Esay ui数据加载等待提示
以视频上传为例: //视频上传 function uploadVedio(fileName){ load();//开始加载时弹出加载层 $.post('< ...
- Electron安装
1.安装nodejs和npm 官网下载地址:https://nodejs.org/en/download/ 安装包:下载.msi 安装完成后: nodejs.npm都会安装好,path环境变量也自动设 ...
- ElasticSearch 5学习(7)——分布式集群学习分享2
前面主要学习了ElasticSearch分布式集群的存储过程中集群.节点和分片的知识(ElasticSearch 5学习(6)--分布式集群学习分享1),下面主要分享应对故障的一些实践. 应对故障 前 ...