Android Dagger2 从零单排(三) @Scope

  转发请注明出处:https://www.jianshu.com/p/b26024bc3107
  Dagger2作为Android界最具杀伤力的匕首,本系列文章将用最通俗的语言带领你揭开它的真面目。
  边缘OB:从零单排带你从低分局打到高分局,从First Blood(第一滴血)到Holy Shit(超越神的杀戮),每盘Rampage(暴走)不在话下!
  Android Dagger2 从零单排(一) 基础注解
  Android Dagger2 从零单排(二) @Qualifier
  Android Dagger2 从零单排(三) @Scope
  Android Dagger2 从零单排(四) Dependencies与SubComponent

  上一篇我们详细介绍了限定标识符@Qualifier的使用,本篇我们来研究作用域@Scope的用途,我们也可以理解为作用范围或生命周期。
  @Scope只能用于注解上,我们需要用@Scope自定义作用域注解,控制所提供依赖的生命周期,使提供的依赖可以做到与视图的生命周期相同、局部单例或者是全局单例等。
  例子一,我们先看一个例子:

public class Bus {
    private String driver;
    public Bus(String driver) {
        this.driver = driver;
    }
}

public class ParkingActivity extends Activity {
    @Inject
    Bus mBus;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dagger);
        ParkingComponent component = DaggerParkingComponent.builder().parkingModule(new ParkingModule("楼下老李")).build();
        component.inject(this);
        ((TextView) findViewById(R.id.text)).setText("属性注入成功 = " + mBus.toString());
        component.inject(this);
        ((TextView) findViewById(R.id.text2)).setText("属性注入成功 = " + mBus.toString());
    }
}

@Component(modules = ParkingModule.class)
public interface ParkingComponent {
    void inject(ParkingActivity activity);
}

@Module
public class ParkingModule {
    private String driver;
    public ParkingModule(String driver) {
        this.driver = driver;
    }
    @Provides
    public String provideDriver() {
        return driver;
    }
    @Provides
    public Bus provideBus(String driver) {
        return new Bus(driver);
    }
}

  如果看过从零单排(一)的应该有印象,这是在例子二的基础上修改了Activity注入代码,mBus会注入两次,打印出每一次的地址值,会发现两次的地址值不相同,每次依赖注入都会新建实例,如果我们想每次注入返回的实例都是一样的呢?
  首先想到,我们可以在Bus类维护一个Bus实例,再写一个静态的方法,只要实例不为空每次都把这个实例返回,在提供依赖的地方直接调用这个静态方法,这么粗暴的解决,我佩服!如果现在有多个Activity,要求在每个Activity都是一个独立的单例呢?其实有更灵性的解决方法就是@Scope注解。
  先自定义一个@Scope注解:

@Scope
public @interface SignLocal {
}

  @Component注解的接口上添加自定义的注解:

@SignLocal
@Component(modules = ParkingModule.class)
public interface ParkingComponent {
    void inject(ParkingActivity activity);
}

  @Module类的@Provides方注解的方法上也添加上:

    @SignLocal
    @Provides
    public Bus provideBus(String driver) {
        return new Bus(driver);
    }

  此时再次编译运行,会惊人的发现,地址值相同了,也就是说两次注入的实例是同一个!其实无论是多次注入,或者是一次注入多个,他们都是相同的实例!
  另外,如果是构造方法提供依赖的方式,把Module提供Bus依赖的方法注释,在Bus类上注解@SignLocal,效果是一样的:

@SignLocal
public class Bus {
    private String driver;
    @Inject
    public Bus(String driver) {
        this.driver = driver;
    }
}

  @Scope注解此时实现了单例,使提供的依赖与容器的生命周期相同,容器缓存了依赖。
  @Scope具有相对性,如果在构造方法或者@Provides方法注解了,@Component接口一定要有注解;@Component接口注解了,不一定非得有构造方法或者@Provides方法被注解。理解起来类似于,抽象类里面不一定有抽象方法,但是有抽象方法必须是抽象类。
  @Scope也可以和@Qualifier一样有自己的成员变量,默认也是value,同样的数据类型也是基本数据类型、String、Enum、Class,包含其一维数组类型,只是相对来说使用得比较少。
  然后,这里引出一个问题,如果Module提供Bus依赖的方法没有加@SignLocal,在Bus类上注解@SignLocal会是什么情况:

    @Provides
    public Bus provideBus(String driver) {
        return new Bus(driver);
    }

@SignLocal
public class Bus {
    ...
}

  这种情况依赖是从@Provides提供,Bus类上的@SignLocal注解加不加都一样,结果是每次重新创建实例。@Scope注解在@Provides方法时,对使用@Provides提供的依赖生效;@Scope注解在类时,对使用@Inject注解的构造方法提供的依赖生效。
  再者,细心的可能会发现在Activity注入的时候用的是同一个ParkingComponent实例,如果此时改下代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        ParkingComponent component = DaggerParkingComponent.builder().parkingModule(new ParkingModule("楼下老李")).build();
        component.inject(this);
        ((TextView) findViewById(R.id.text)).setText("属性注入成功 = " + mBus.toString());
        ParkingComponent component1 = DaggerParkingComponent.builder().parkingModule(new ParkingModule("楼下老李")).build();
        component1.inject(this);
        ((TextView) findViewById(R.id.text2)).setText("属性注入成功 = " + mBus.toString());
    }

  两次的ParkingComponent实例是重新生成,此时再运行,&^%$#@!,@SignLocal也不管用了!其实,这才是正常的,提供依赖的容器都不一样了,提供的依赖又怎么可能一样!
  接着,我们便引出局部单例及全局单例的定义,我们已经知道,加了@Scope注解的单例只对于同一个容器而言,所以重点在于如何维护这个容器。
  例子二,模拟用户登录的场景来加深局部单例的概念,代码过多,这里不全部贴出来,只说明下整个过程,模拟登陆的LoginActivity登陆成功时会在Application维护一个UserComponent,在退出登陆的时候把UserComponent置空,此时UserComponent提供的用户信息User,生命周期只在登陆之后到注销之前,并且在这期间是单例,可以看对应Demo的例子二。
  例子三,模拟全局单例,只需要在Application维护一个@Component,此后注入的时候,都从Application获取注入,由于容器的全局唯一,每次注入的实例都是相同的,由于例子只是把ParkingComponent 初始化的方法移动到了Application,在文章不贴出来,可以看对应Demo的例子三。
  再说一点,例子三中用的注解是@Singleton,这是@Scope的默认实现类,实际上,这跟我们自定义的注解没区别,甚至再狠点定义完全没意义的字符同样也可以,就是可能会导致后面看代码的人想把你狠狠按在地上摩擦。其实我想说的是,所有自定义的@Scope都能实现上述功能,不一样的@Scope注解的最大作用其实就是为了增强可读性,类似于Singleton代表全局单例,ByActivity代表在Activity的生命周期内等,如此一看就知道这个@Component容器是用在哪个地方。
  最后总结,@Scope作用是控制所提供依赖的生命周期,使其与容器的生命周期相同,从而实现局部单例或全局单例。@Scope注解的名字是为了增强可读性,所以起名字的时候,一定要起得言简意赅。
  上一篇留下了个问题@Retention(RetentionPolicy.RUNTIME)在自定义注解的时候到底要不要加,没人来打我脸...暂时观念还是跟上一篇一样,编译期间就已经生成好关联了,加不加没啥区别,如果有误的话,欢迎留言指正。

  Demo源码截我 对应daggerThree包名
  Dagger2 GitHub地址
  Dagger2 官网地址
  所有的测试实例均基于2.15版本。
  下一篇,我们来研究Dependencies与SubComponent的使用。

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