×

Android Dagger2 从零单排(三) @Scope

96
MrTangFB
2018.05.11 18:17* 字数 1838

  转发请注明出处: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的使用。

Android Dagger2
Web note ad 1