Dagger2解析4-Scope

Dagger2系列:

  1. Dagger2解析-1
  2. Dagger2解析2-Component的依赖关系
  3. Dagger2解析3-SubComponent

Dagger版本:2.11

继续填坑,这篇简单讲下@Scope吧,

1.Scope

首先看一下Scope类本身,它是javax.inject包下的一个注解类,先来看它doc文档中的一些说明:

Identifies scope annotations. A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type. By default, if no scope annotation is present, the injector creates an instance (by injecting the type's constructor), uses the instance for one injection, and then forgets it. If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection. If multiple threads can access a scoped instance, its implementation should be thread safe. The implementation of the scope itself is left up to the injector.
In the following example, the scope annotation @Singleton ensures that we only have one Log instance:

大致意思是Scope是一个识别作用域的注解,例如默认情况下,没有标注@Scope注解时,每次注入依赖时都会创建新的实例,然后就不管它了(就好比飙车依赖老司机,开车前先请一个老司机回来,把他塞到车里,但却没加微信,那么下次飙车就又得重新请一个老司机了),如果标注了Scope注解,那么注入器可能就会保持这个实例,下次注入需要这个依赖时就可以重用了(留下了老司机的微信,飙车直接call他,还是这个原汁原味的老司机),然后是线程安全的问题,至于怎么实现是注入框架的事情了

Dagger2解析4-Scope_第1张图片

下面是其他的一些说明

  • 不能标注多个
  • 自定义Scope的用法:
    • 只能标注在@interface上
    • 要需要标注@Retention(RUNTIME)(表示这是一个运行时的注解)
    • 不能有属性
    • 还有两点没看懂,大概说的继承和可标注处的事情吧
  • 这个注解可以帮助注入器检测到依赖标明了作用域而但注入器却没标明的情况,对此保守的注入器会抛出错误(放在dagger上应该就是Scope不符合的情况下直接会编译错误吧)

文档中提到了@Singleton,这是一个标注了@Scope的注解

/**
 * Identifies a type that the injector only instantiates once. Not inherited.
 *
 * @see javax.inject.Scope @Scope
 */
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

从Scope文档中的示例可以看出,这个Singleton的写法并没有什么特殊的,仅仅是在文档注释写了句标记单例的类型
实际上,Scope就是作为一种类型的标记而已,而这种标记的目的是为了更好地区分作用域

2.通过@Scope注解生成的代码

回到Dagger上,照例上代码,这次加上@Singleton,或者其他的Scope都行

@Singleton
class Member @Inject constructor()

class Target {
    @Inject
    lateinit var member: Member
}

@Singleton
@Component
interface TargetComponent {
    fun inject(target: Target)
}

再看结果

fun test() {
    val target1 = Target()
    val target2 = Target()
    val targetComponent = DaggerTargetComponent.builder().build()
    targetComponent.inject(target1)
    targetComponent.inject(target2)
    Log.e("dagger2", "target1:${target1.member}")
    Log.e("dagger2", "target2:${target2.member}")
}
Dagger2解析4-Scope_第2张图片
是同一个实例

接下来是生成的代码,和之前的生成的一样,唯一不同的是Component,这里我们对比一下
ps:就算换个Scope,生成的也是一样的,例如

@Scope
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class MyScope
Dagger2解析4-Scope_第3张图片
左边是带@Singleton标注的,右边的是不带的

主要的变化就是原来的Member_Factory.create()创建出来的Provider被放到DoubleCheck.provider()转变为另外一种Provider了,那现在看看DoubleCheck.provider()

 /** Returns a {@link Provider} that caches the value from the given delegate provider. */
 public static  Provider provider(Provider delegate) {
   checkNotNull(delegate);
   if (delegate instanceof DoubleCheck) {
     /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped
      * binding, we shouldn't cache the value again. */
     return delegate;
   }
   return new DoubleCheck(delegate);
 }

这里创建了一个DoubleCheck的对象,它实现了Provider接口,构造函数参数是原来的Provider,实际上也就是创建代理

那么看看它的get方法(就是用来提供依赖实例的方法,来自Provider接口)

public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          /* Get the current instance and test to see if the call to provider.get() has resulted
           * in a recursive call.  If it returns the same instance, we'll allow it, but if the
           * instances differ, throw. */
          // 由于是使用代理创建的实例,防止递归的情况下返回不同的实例,得做出判断,不是同一实例的直接抛出异常
          Object currentInstance = instance;
          if (currentInstance != UNINITIALIZED && currentInstance != result) {
            throw new IllegalStateException("Scoped provider was invoked recursively returning "
                + "different results: " + currentInstance + " & " + result + ". This is likely "
                + "due to a circular dependency.");
          }
          instance = result;
          /* Null out the reference to the provider. We are never going to need it again, so we
           * can make it eligible for GC. */
          // 实例创建出来了,Provider没卵用了,可以滚蛋了
          provider = null;
        }
      }
    }
    return (T) result;
  }

嗯,这就是一个典型的双重锁单例,但条件判断不是用null判断的,这应该说明provider本身允许产生null

最后这个Provider(DoubleCheck)给了Injector,注入时调用它的get方法拿出的实例就是同一个实例,于是就实现了复用

但需要注意的是,这个单例只针对同一个Component实例的情况下,毕竟Component本身也是能重复创建的,每个Component都有injector对象,所以说injector里的Provider能复用对象也同样无法保证单例

基于这个情况,就可以明白了其他dagger2文章里讲述的关于生命周期同步(例如和Activity同步,和Fragment同步)是什么情况了,实际上就是控制Componet的生命周期

3.所谓的作用域

下面借鉴一下Dependency injection with Dagger 2 - Custom scopes的例子,懒得再自己写了,例子中是java写的

Dagger2解析4-Scope_第4张图片
摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

Dagger2解析4-Scope_第5张图片
摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

在这个例子中,把app简单的分为几个维度

  • Application scope:全局的,跟随应用的生命周期
  • UserScope:一个用户从登录到注销,跟随用户的行为
  • ActivityScope:更随每个Activity的生命周期

他们之间的包含关系是Application scope->UserScope(不一定有)->ActivityScope

再啰嗦一句,这些维度只是按标注这么区分而已,代码中不管理好Compoent生命周期的话仍旧没卵用

@Singleton
@Component(modules = {AppModule.class, GithubApiModule.class})
public interface AppComponent {
    // 省略一些代码,就按上面那几个维度举例,提供一个UserScope级别和一个ActivityScope级别的组件,AppComponent 本身就是应用级别的
    // 之前的SubComponent写法,将会继承AppComponent的依赖关系
    UserComponent plus(UserModule userModule);
    // Splash页面不隶属与UserComponent的(应用第一页不用登录)
    SplashActivityComponent plus(SplashActivityModule splashActivityModule);
}
@UserScope
@Subcomponent(modules = {UserModule.class})
public interface UserComponent {
    // 之前的SubComponent写法,将会继承UserComponent 的依赖关系
    // 忘了是啥页面了,反正是需要登录后才能查看的,于是这些子组件就隶属于UserComponent了
    RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
    RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}

这里得补充一下,UserComponent做为AppComponent的子组件,@Scope是可以不一样的,也就是作用域是不一样的,但得和前面定义(或者说我们希望)的作用域是相符的,就是说子组件的作用域要比父组件小(UserScope用户登录到退出的作用域小于ApplicationScope应用全局的作用域),从Subcomponet实例的获取方式也能看出,父组件的实例创建出来后才可能获得子组件。不明白的请看上一篇文章Dagger2解析-3

接着来看一下组件实例化的地方,例子中放在了GithubClientApplication中

public class GithubClientApplication extends Application {

    private AppComponent appComponent;
    private UserComponent userComponent;

     // 省略...
    @Override
    public void onCreate() {
        // 省略...
        initAppComponent();
    }

    private void initAppComponent() {
        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
    }

    public UserComponent createUserComponent(User user) {
        userComponent = appComponent.plus(new UserModule(user));
        return userComponent;
    }

    public void releaseUserComponent() {
        userComponent = null;
    }
    // 省略...
}
  • 可以看到AppComponent在onCreate就初始化了,初始化方法是私有的,也就是只调用一次。这种管理比较简单,找个全局单例的工具类,把这个组件放在里面也是可以的,方式有多种,只要保证AppComponent在app生命周期内只存在一个实例就行
  • UserComponet是提供了一个初始化方法给外部调用(登录后调用的),还提供了release方法用于注销登录时调用,这就是UserScope的生命周期管理了,登录后调用了createUserComponent才存在,而注销后调用了releaseUserComponent清空引用,被gc回收后消失,是和登录注销行为挂钩的

最后看一下Activity内部的,以RepositoriesListActivity为例

public class RepositoriesListActivity extends BaseActivity {
    // 省略一堆代码...
    // presenter作为activity的依赖
    @Inject
    RepositoriesListActivityPresenter presenter;
    @Override
    protected void setupActivityComponent() {
        GithubClientApplication.get(this).getUserComponent()
                .plus(new RepositoriesListActivityModule(this))
                .inject(this);
    }
    // 省略一堆代码...
}

从前面的UserComponent里的

RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);

看出,在RepositoriesListActivity里的执行注入的是RepositoriesListActivityComponent

ps:RepositoriesListActivity的依赖以RepositoriesListActivityPresenter presenter为例,其他的一样的就不提了
这个组件仅仅是调用inject方法注入presenter后就完事了,并没保存组件实例,所以这个组件里的依赖也并没有缓存presenter,也就是它们是跟随Activity生命周期的,Activity销毁后,GC回收时会顺带回收presenter(前提是presenter没泄露出去被其他地方持有)

上面例子的源码在这GithubClient,和文章Dependency injection with Dagger 2 - Custom scopes里的有些不同

参考:

  • [Dependency injection with Dagger 2 - Custom scopes]
    http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

你可能感兴趣的:(Dagger2解析4-Scope)