Dagger2单例(四)

Dagger2单例(四)

我们就继续用前面的例子开始讲了,如果我使用了两个对象:

class CoffeeMaker {
    @Inject
    Heater heater;
    @Inject
    Heater heater2;

    CoffeeMaker() {
        CoffeeComponent component = DaggerCoffeeComponent.create();
        component.inject(this);
    }

    public void brew() {
        Log.e("@@@", "heater :" + heater.toString());
        Log.e("@@@", "heater2 :" + heater2.toString());
    }
}

我们的执行结果是:

heater :com.charon.stplayer.test.ElectricHeater@2fea2fd
heater2 :com.charon.stplayer.test.ElectricHeater@8c2abf2

很显然这是两个不同的对象。 我现在想让该Heater是单例呢?

  • 首先,在CoffeeModule里的provideHeater()方法前面添加Singleton
@Module
public class CoffeeModule {
    @Singleton
    @Provides
    Heater provideHeater() {
        return new ElectricHeater();
    }

    ....
}    
  • 然后再CoffeeComponent里的接口上面添加@Singleton注释
@Singleton
@Component(modules = CoffeeModule.class)
public interface CoffeeComponent {
    void inject(CoffeeMaker maker);

    Heater provideHeater();
    Pump providePump();
    Ice provideIce();
    IceBox provideIceBox();
    @Type("normal")
    Milk provideNormalMilk();
    @Type("shuiguo")
    Milk provideShuiGuoMilk();
    String provideString();
}

注意Modle是在方法上面添加Singleton,而Component是在接口上面添加Singleton
好了,再执行以下:

heater :com.charon.stplayer.test.ElectricHeater@2fea2fd
heater2 :com.charon.stplayer.test.ElectricHeater@2fea2fd

看,它是同一个对象了。
Dagger2里面还有有一个注解@Scope也是用来实现单例的,其实它和Singleton差不多,Singleton是它的实现。用法也基本一样,只是换一个名字。

可重用的作用域绑定(Reusable scope)

如果你想控制@Inject构造器类被实例化或provider方法被调用的的次数,且不用保证某个componentsubcomponent在生命周期中使用同一个实例,就可以使用@Reusable作用域了。@Reusable作用域绑定,不像其他作用域绑定一样需要关联一个component,它不会关联任何component,相反真正要用这个绑定的component都会缓存这个返回值或对象实例。
这就意味着,如果你用@Reusablecomponent指定module,且只有一个subcomponent会用到这个绑定,那么只有这个subcomponent会缓存这个绑定对象。如果两个subcomponent祖先没有共享一个绑定,那么每个subcomponent会各自缓存自己的对象。如果一个component的祖先已经缓存了绑定对象,那么subcomponent会重用这个对象。
由于无法保证component只会调用一次绑定,@Reusable绑定可能会返回不确定的对象,这个时候想引用同一个对象是很危险的。所以当你不关心对象会被实例化多少次使用@Reusable是安全的。

@Reusable // It doesn't matter how many scoopers we use, but don't waste them.
class CoffeeScooper {
  @Inject CoffeeScooper() {}
}

@Module
class CashRegisterModule {
  @Provides
  @Reusable // DON'T DO THIS! You do care which register you put your cash in.
            // Use a specific scope instead.
  static CashRegister badIdeaCashRegister() {
    return new CashRegister();
  }
}

@Reusable // DON'T DO THIS! You really do want a new filter each time, so this
          // should be unscoped.
class CoffeeFilter {
  @Inject CoffeeFilter() {}
}

@Scope

Dagger2中,可以通过自定义Scope来实现局部单例:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface HeaterScope {
}

然后在Module中和Singleton类似去使用它,来指定它的作用范围:

@Module
public class CoffeeModule {
    @HeaterScope
    @Provides
    Heater provideHeater() {
        return new ElectricHeater();
    }
    ....
}    

然后在Component中和Singleton类似去使用它,来指定它的作用范围:

@HeaterScope
@Component(modules = CoffeeModule.class)
public interface CoffeeComponent {
    void inject(CoffeeMaker maker);

    Heater provideHeater();
    Pump providePump();
    Ice provideIce();
    IceBox provideIceBox();
    @Type("normal")
    Milk provideNormalMilk();
    @Type("shuiguo")
    Milk provideShuiGuoMilk();
    String provideString();
}

@Singleton@Scope有什么区别呢? 我们看一下@Singleton的源码:

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

查看源码可以发现@Singleton是使用注解@Scope修饰的, 而@Scope是用来声明作用域的,Dagger2的机制:在同一作用域下provides提供的依赖对象会保持单例, 脱离这一作用域, 单例作用就会失效, 可以说@Singleton@Scope注解的一个标准, 我们还可以去自定义一些Scope注解来指定具体的作用域,这里就先不介绍了。

好了,到这里基本讲的差不多了,那我们就开始开发了,为了简单,我们就还是用咖啡的例子,在Android开发中,有两个Activity,我想在这两个Activity中都使用Heater对象,我们前面已经把它设置
成单例了。但是想要在两个Activity中都使用,我们首先要修改一下CoffeeComponentinject方法。

@Singleton
@Component(modules = CoffeeModule.class)
public interface CoffeeComponent {
    void inject(MainActivity maker);
    void inject(FirstActivity maker);

    Heater provideHeater();
    Pump providePump();
    Ice provideIce();
    IceBox provideIceBox();
    @Type("normal")
    Milk provideNormalMilk();
    @Type("shuiguo")
    Milk provideShuiGuoMilk();
    String provideString();
}

好了接下来分别在MainActivityFirstActivity中去使用:

import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {
    @Inject
    Heater heater1;
    @Inject
    Heater heater2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        CoffeeComponent component = DaggerCoffeeComponent.create();
        component.inject(this);
        Log.e("@@@", "MainActivity heater :" + heater1.toString());
        Log.e("@@@", "MainActivity heater2 :" + heater2.toString());
        startActivity(new Intent(MainActivity.this, FirstActivity.class));
        finish();
    }
}

FirstActivity:

public class FirstActivity extends AppCompatActivity {
    @Inject
    Heater heater1;
    @Inject
    Heater heater2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        CoffeeComponent component = DaggerCoffeeComponent.create();
        component.inject(this);

        Log.e("@@@", "FirstActivity heater :" + heater1.toString());
        Log.e("@@@", "FirstActivity heater2 :" + heater2.toString());
    }
}

走你 ~

04-21 11:57:31.514 12146-12146/com.charon.daggerdemo E/@@@: MainActivity heater :com.charon.daggerdemo.test.ElectricHeater@91bece5
    MainActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@91bece5
04-21 11:57:31.734 12146-12146/com.charon.daggerdemo E/@@@: FirstActivity heater :com.charon.daggerdemo.test.ElectricHeater@765de0d
    FirstActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@765de0d

我屮艸芔茻!!!每个Activity中的对象确实是一样的,但是两个不同的Activity中是不一样的。单例个锤子啊单例。这也是单例,只不过是局部单例。
使用@Singeton注解或者定制的@Scope的, 只在同一个activity或者fragment的一个生命周期中保持单例. 而平时我们希望一些对象能够在整个应用的生命周期中只存在一个, 也就是说实现全局意义上真正的单例该怎么做呢?

上面的DaggerCoffeeComponent在两个Activity中被各自被实例化一次, 导致产生了两个不同的对象, 所以我们需要做到让DaggerCoffeeComponent能够实现单例,在不同的Activity都是同一个对象,这样就可以保证全局单例了,Android中整个App生命周期中都只有一个Appclication实例,所以可以在Application中获得一个唯一的Component实例:

主要分为以下基本部分:

  • 创建一个BaseModule,并且创建单例的provideHeater方法

    @Module
    public class BaseModule {
        @Singleton
        @Provides
        public Heater provideHeater() {
            return new ElectricHeater();
        }
    }
  • 接下来就是创建BaseComponent类,并且要将该接口声明单例

    @Singleton
    @Component(modules = BaseModule.class)
    public interface BaseComponent {
        // 之前我们说过Component中的provideXXX是可以不写的,但是如果你想让别的Component依赖该Component,就必须写,不写的话意味着没有向外界暴露该依赖
        Heater provideHeater();
    }

    到这里,可能就会有疑问了,你忘了写inject方法了。这里BaseComponent是不需要写inject方法的,因为他没有要注入的类,我们下面会有具体的Component类来写inject,方法。这是base就不需要了
    注意:BaseComponent中不再需要写inject方法, 因为这个component是用来让别的component来依赖的, 只需要告诉别的component他可以提供哪些类型的依赖即可, 这个例子中 我们提供一个全局Heater的依赖

  • 接下类是定义具体依赖于BaseComponent的子Component,之前我们写Component都是用@Component(modules = BaseModule.class)来指定具体要去查找类的Module,但是这里不一样了,
    这里该Component是依赖于BaseComponent,所以这里要使用dependencies关键字了。

    @Singleton
    @Component(dependencies = BaseComponent.class)
    public interface HeaterComponent {
        // 因为这里HeaterComponent依赖了BaseComponent,所以这里就有proviceHetear方法
        void inject(MainActivity maker);
        void inject(FirstActivity maker);
    }

    好了,这里我们重新build一次,如果没有意外的话,就会生成DaggerHeaterComponent类了,我们去使用它就可以了。
    但是,但是,出意外了…..

    错误: This @Singleton component cannot depend on scoped components:
    @Singleton com.charon.daggerdemo.test.BaseComponent

    答题意思是说,使用@Singleton修饰的component不能依赖另一个局部的component,也就是@Singleton com.charon.daggerdemo.test.BaseComponent
    也就是说如果依赖的component中也使用了@singleton时, 被依赖的地方就不能再使用@Singleton了。
    BaseComponent我明明是
    用了@Singleton,而这个错误信息却说是一个scoped component,这是什么鬼? 这个其实前面我们也说过了,这里再看一下@Singleton的源码:

    @Scope
    @Documented
    @Retention(RUNTIME)
    public @interface Singleton {}

    那这里不能用@Singleton该怎么处理呢? 这里要用到自定义@Scope了。
    自定义一个ActivityScope:

    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ActivityScope {
    }

    然后修改我们上面的HeaterComponent类,把之前使用的@Singleton修改成我们刚才自定义的@ActivityScope:

    @ActivityScope
    @Component(dependencies = BaseComponent.class)
    public interface HeaterComponent {
        void inject(MainActivity maker);
        void inject(FirstActivity maker);
    }

    好了,再build一下,

  • 自定义Application

public class BaseApplication extends Application {
    private static BaseApplication mApplication;
    private BaseComponent baseComponent;
    public static BaseApplication getInstance() {
        return mApplication;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        mApplication = this;
        // 下面这两种方式都是可以的
//        baseComponent = DaggerBaseComponent.create();
        baseComponent = DaggerBaseComponent.builder().baseModule(new BaseModule()).build();
    }

    public BaseComponent getBaseComponent() {
        return baseComponent;
    }
}
  • 好了,去MainActivityFirstActivity中修改下:

image
和前面一样,直接调用DaggerHeaterComponent你会发现没有create方法了,只有一个builder方法,这种依赖的方式,必须要使用buildrer来进行。

public class MainActivity extends AppCompatActivity {
    @Inject
    Heater heater1;
    @Inject
    Heater heater2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // .baseComponent就是将Application中的BAseComponent传递到子component中,而在application中一创建就调用了DaggerBaseComponent.builder().build();
        HeaterComponent component = DaggerHeaterComponent.builder().baseComponent(
                BaseApplication.getInstance().getBaseComponent()).build();
        component.inject(this);
        Log.e("@@@", "MainActivity heater :" + heater1.toString());
        Log.e("@@@", "MainActivity heater2 :" + heater2.toString());
        startActivity(new Intent(MainActivity.this, FirstActivity.class));
        finish();
    }
}
public class FirstActivity extends AppCompatActivity {
    @Inject
    Heater heater1;
    @Inject
    Heater heater2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        HeaterComponent component = DaggerHeaterComponent.builder().baseComponent(
                BaseApplication.getInstance().getBaseComponent()).build();
        component.inject(this);

        Log.e("@@@", "FirstActivity heater :" + heater1.toString());
        Log.e("@@@", "FirstActivity heater2 :" + heater2.toString());
    }
}

运行下:

04-21 13:28:46.944 15130-15130/com.charon.daggerdemo E/@@@: MainActivity heater :com.charon.daggerdemo.test.ElectricHeater@91bece5
    MainActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@91bece5
04-21 13:28:47.024 15130-15130/com.charon.daggerdemo E/@@@: FirstActivity heater :com.charon.daggerdemo.test.ElectricHeater@91bece5
    FirstActivity heater2 :com.charon.daggerdemo.test.ElectricHeater@91bece5

看,现在是全局单例了

上面讲到了Component依赖,这里仔细讲讲,在上面的例子Activity中:

// .baseComponent就是将Application中的BAseComponent传递到子component中,而在application中一创建就调用了DaggerBaseComponent.builder().build();
HeaterComponent component = DaggerHeaterComponent.builder().baseComponent(
        BaseApplication.getInstance().getBaseComponent()).build();
component.inject(this);

这里因为baseComponent的生命周期比较特殊,需要和Application绑定,所以BaseComponentbuild工作放到Application中了。如果对于普通的component依赖
这里其实是两部分,假设目前我们有这种情况,已经有了一个A:

public class A {
    public A() {
        Log.e("@@@", "crate a");
    }
}

@Module
public class AModule {
    @Provides
    public A proviceA() {
        return new A();
    }
}

@Component(modules = AModule.class)
public interface AComponent {
    A proviceA();
    void inject(FirstActivity activity);
}

同样还有B:

public class B {
    public B() {
        Log.e("@@@", "crate b");
    }
}

@Module
public class BModule {
    @Provides
    public B proviceB() {
        return new B();
    }
}

@Component(modules = BModule.class)
public interface BComponent {
    B proviceB();
    void inject(MainActivity activity);
}

我们在MainActivity中去注入B:

public class MainActivity extends AppCompatActivity {
    @Inject
    B b;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        BComponent bComponent = DaggerBComponent.create();
        bComponent.inject(this);
    }
}

执行结果:

04-21 18:45:38.494 18851-18851/com.charon.daggerdemo E/@@@: crate b

现在我想在B中注入A怎么办?我想也去用A,但是A已经有了,我不至于要重新再去写一遍:

@Component(modules = BModule.class, dependencies = AComponent.class) // 只需要在这里把AComponent引入,其他不用动
public interface BComponent {
    B proviceB();
    void inject(MainActivity activity);
}

到这里,我们的BComponent继承了AComponent,它也有了AComponent中的方法:

public class MainActivity extends AppCompatActivity {
    @Inject
    B b;
    @Inject
    A a;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 注意要先build获取AComponent
        AComponent aComponent = DaggerAComponent.builder().aModule(new AModule()).build();
        // 要通过aComponent()方法将AComponent设置到BComponent中
        BComponent bComponent = DaggerBComponent.builder().bModule(new BModule()).aComponent(aComponent).build();
        bComponent.inject(this);
    }
}

执行结果:

04-21 19:04:59.209 20445-20445/? E/@@@: crate b
04-21 19:04:59.210 20445-20445/? E/@@@: crate a

可释放的引用(Releasable references)

如果一个绑定使用scope注解,这就意味着component对象会持有这个作用域对象的引用直到component自己被GC回收。在像Android这样的内存敏感环境中,如果你想在内存紧张时让GC回收当前未使用的作用域对象,只需要使用@CanReleaseReferences定义一个scope即可:

@Documented
@Retention(RUNTIME)
@CanReleaseReferences
@Scope
public @interface MyScope {}

可以为scope注入一个ReleasableReferenceManager对象,在内存紧张时调用它的releaseStrongReferences()方法以便让component持有该对象的弱引用而不是强引用。

@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferences myScopeReferences;

void lowMemory() {
  myScopeReferences.releaseStrongReferences();
}

当内存压力减少时可以调用restoreStrongReferences()方法恢复缓存对象的强引用。

void highMemory() {
  myScopeReferences.restoreStrongReferences();
}

如果一个Component的功能不能满足你的需求,你需要对它进行拓展,这里有两种方式:

  • 使用@Component(dependencies=××.classs)
  • 使用@Subcomponent

@SubComponent

Subcomponent用于拓展原有component。同时这将产生一种副作用——子component同时具备两种不同生命周期的scope。子Component具备了父Component拥有的Scope,也具备了自己的Scope

Subcomponent其功能效果优点类似componentdependencies。但是使用@Subcomponent不需要在父component中显式添加子component需要用到的对象,只需要添加返回子Component的方法即可,子Component能自动在父Component中查找缺失的依赖。

我们还是用上面AB的例子,假设A是父类:

  • 首先,修改AComponent类,在里面提供BComponent
//@Singleton
@Component(modules = AModule.class)
public interface AComponent {
    // 一定要在A中提供获取BComponent的方法
    BComponent bcomponent();
    A provideA();
    void inject(FirstActivity activity);
}
  • 其次,修改BComponent,将其修改成SubComponent的方式,并且去掉前面的dependencies
//@ActivityScope  // 如果有的话只能比父类的范文小
@Subcomponent(modules = BModule.class) 
public interface BComponent {
    B provideB();
    void inject(MainActivity activity);
}
  • 使用
public class MainActivity extends AppCompatActivity {
    @Inject
    B b;
    @Inject
    A a;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AComponent aComponent = DaggerAComponent.builder().aModule(new AModule()).build();
        // 通过AComponent里面的方法去拿BComponent,然后调用inject
        aComponent.bcomponent().inject(this);
//        BComponent bComponent = DaggerBComponent.builder().bModule(new BModule()).aComponent(aComponent).build();
//        bComponent.inject(this);
    }
}

对比中我们发现dependenciesComponent强调的是在子类Component依赖于某个Component(子类为主角),而Subcomponent中强调的则是在父类Component中提供某个子类的Component(父类为主角)。

但是这两种到底有什么区别呢?

Component.dependiences方式

  • 可以很清晰的看到具体的依赖关系
  • 会生成独立的DaggerXXXXComponent
  • 依赖Component仅继承被依赖Component中显示提供的依赖,如果不提供,则无法使用@Inject注入被依赖的Component中的对象

SubComponent方式

  • 不需要在被依赖的Component显示提供依赖
  • 不需要使用更多的DaggerXXXXComponent对象来创建依赖,仅需要在被依赖Component中增加获取XXXComponent的方法
  • 但是会依赖不明确,不容易明确的看到具体的依赖关系
  • 多人开发,被依赖Component容易造成冲突
  • 务必在被依赖Component中创建XXXComponent的获取方法,并在被依赖ComponentDaggerXXXComponent的实例化对象中调用该方法

最后总结一下几个注解:

  • @Module: 修饰类,用来提供依赖,里面会提供具体生成依赖类对象的方法,供使用者通过
  • @Provides:用于注解被@Module修饰的类中的方法, 当需要该类的依赖时会去找返回值为该类的方法
  • @Component:注解一个接口或者抽象类,为injectmodule之间建立联系,可以理解为不赚差价的中间商,在编译时会产生对应的类的实例, 为依赖方和被依赖方提供桥梁,把相关的依赖注入到其中.
  • @inject:依赖注入类中的依赖变量声明,让Dagger2为其提供依赖或者用在被依赖的类的构造方法上申明, 通过标记构造函数让Dagger2来使用, 可以在需要这个类实例的时候来找到这个构造函数并把相关实例new出来
  • @Named:用于区分我们获取依赖的方法
    Dagger2寻找依赖是根据我们提供方法的返回值进行匹配的, 当我们遇到不同提供依赖的方法但是返回值类型是相同的应该怎么办呢?Dagger2提供了Named注解,用于区分相同返回值类型的不同方法.
  • @Qualifier:与Named一致,Named是继承Qulifier的,相当于Dagger2为我们提供的标准化的区分器,
  • Singleton:声明单例模式,Dagger2的机制是需要某个对象是回去找对应提供的实例化方法,多次寻找就会创建多个对象,当我们需要让对象保持单例模式时, 要使用@Singleton修饰提供依赖的方法
  • @Scope:查看源码可以发现@Singleton是使用注解@Scope修饰的,而@Scope是用来声明作用域的,Dagger2的机制:在同一作用域下,provides提供的依赖对象会保持单例,脱离这一作用域, 单例作用就会失效,可以说@Singleton@Scope注解的一个标准,我们还可以去自定义一些Scope注解.

更多文章请查看AndroidNote

你的star是我的动力!!!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值