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他,还是这个原汁原味的老司机),然后是线程安全的问题,至于怎么实现是注入框架的事情了

下面是其他的一些说明

  • 不能标注多个
  • 自定义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}")
}
是同一个实例

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

@Scope
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class MyScope
左边是带@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 <T> Provider<T> provider(Provider<T> 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<T>(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写的

摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

摘自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里的有些不同

参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容