热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[Android]使用Dagger2依赖注入自定义Scope(翻译)


以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html

原文: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呈现的简单图表。

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

单例(Application scope)是最长的scope(在实践中是与application一样长)。UserScope作为Application scope的一个子集scope,它可以访问它的对象(我们可以从父scope中得到对象)。ActivityScope(生命周期与Activity一致)也是如此 - 它可以从UserScope和ApplicationScope中得到对象。

Scope生命周期的例子

这里有一个我们app中scope生命周期的案例:

[Android]使用Dagger 2依赖注入 - 自定义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):UserComponentSplashActivityComponent。因为它们都是AppComponent的subcomponents,所以它们两者都可以访问AppModuleGithubApiModule创建的实例。

这些方法的命名法则是:返回类型是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:RepositoriesListActivityComponentRepositoryDetailsActivityComponent

@UserScope
@Subcomponent(
        modules = {
                UserModule.class
        }
)
public interface UserComponent {
    RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);

    RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}

并且更重要的是所有scope的东西都发生在这里。所有UserComponent中从AppComponent继承过来的仍然shi是单例的(是 Applicaton scope)。但是UserModuleUserComponent的那部分)创建的对象将会是“局部单例”,它的生命周期跟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实例,它应该使用@UserScopeScope。我们来检验这个方法哪里被调用(第8行):

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class UserModule_ProvideRepositoriesManagerFactory implements Factory {

  //...
  
  @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 create(UserModule module, Provider userProvider, Provider githubApiServiceProvider) {  
    return new UserModule_ProvideRepositoriesManagerFactory(module, userProvider, githubApiServiceProvider);
  }
}

UserModule_ProvideRepositoriesManagerFactory仅仅是一个工厂模式的现实,它从UserModule中获取到RepositoriesManager实例。我们应该往更深层次挖掘。

UserModule_ProvideRepositoriesManagerFactoryUserComponentImpl中被使用 - 我们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 implements Provider {
  
  //...

  private ScopedProvider(Factory 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。

作者

Miroslaw Stanek

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的多绑定(翻译):__

推荐阅读
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • Firefox火狐浏览器关闭到http://detectportal.firefox.com的流量问题解决办法
    本文介绍了使用Firefox火狐浏览器时出现关闭到http://detectportal.firefox.com的流量问题,并提供了解决办法。问题的本质是因为火狐默认开启了Captive portal技术,当连接需要认证的WiFi时,火狐会跳出认证界面。通过修改about:config中的network.captive-portal-service.en的值为false,可以解决该问题。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了MVP架构模式及其在国庆技术博客中的应用。MVP架构模式是一种演变自MVC架构的新模式,其中View和Model之间的通信通过Presenter进行。相比MVC架构,MVP架构将交互逻辑放在Presenter内部,而View直接从Model中读取数据而不是通过Controller。本文还探讨了MVP架构在国庆技术博客中的具体应用。 ... [详细]
author-avatar
-寒小兮_991
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有