[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):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<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_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<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

作者

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


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

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


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

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


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

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

本文转自天天_byconan博客园博客,原文链接:http://www.cnblogs.com/tiantianbyconan/p/5095426.html ,如需转载请自行联系原作者
相关文章
|
1月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
41 3
|
1月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
86 0
|
21天前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
23天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
30 5
|
1月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
2月前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
62 10
|
2月前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
48 3
|
2月前
|
前端开发 Android开发 开发者
安卓应用开发中的自定义视图基础
【9月更文挑战第13天】在安卓开发的广阔天地中,自定义视图是一块神奇的画布,它允许开发者将想象力转化为用户界面的创新元素。本文将带你一探究竟,了解如何从零开始构建自定义视图,包括绘图基础、触摸事件处理,以及性能优化的实用技巧。无论你是想提升应用的视觉吸引力,还是追求更流畅的交互体验,这里都有你需要的金钥匙。
|
2月前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
2月前
|
API Android开发 iOS开发
掌握安卓与iOS应用开发中的依赖注入技术
本文探讨了在安卓和iOS应用开发中,如何有效利用依赖注入技术来提升代码的模块化、可测试性和可维护性。通过对比分析两种平台下依赖注入的实现方式与工具,本文旨在为开发者提供一套清晰、实用的依赖管理策略,助力打造高质量软件产品。
下一篇
无影云桌面