Dagger2使用简析——@Scope、@Qualifier、@binds、dependencies、Lazy

在了解了简单注入对象的使用后,我们将问题升级。我们平常开发中为了节省资源,在APP的生命周期内很多对象都是作为单例存在的,因此现在我们尝试解决三个问题

  • 将一个对象注入到Application中,并且保证它在整个APP的生命周期内是单例的
  • 这个对象应该符合依赖倒置原则,我们使用其抽象类来作为引用
  • 之后为了方便在MainActivity中使用它,我们还应该能够将它注入到MainActivity中

1. 如何解决一个对象在某个生命周期内是单例的

现在我们向App中注入一个FactoryA的实例对象,并使得其在App的生命周期内是单例的,代码如下:

public class App extends Application {

    @Inject
    FactoryA mFactory;

//    @Inject
//    FactoryA mFactory2;

    @Override
    public void onCreate() {
        super.onCreate();

        mAppComponent = DaggerAppComponent
                .builder()
                .build()
                .inject(this);

        mFactory.showMe();
//        mFactory2.showMe();
    }
}
@Module
public abstract class AppModule {
    @Singleton
    @Provides
    public static FactoryA providesFactoryA() {
        return new FactoryA();
    }
}
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(App app);
}

如果你对简单注入已经熟练掌握的话,你应该发现这里使得对象成为单例只需要同时在@Provides注解的方法component接口上添加@Singleton这个注解即可
我们看一下@Singleton这个注解:

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

事实上千万不要误将@Singleton单例挂钩,真正起作用的是@Scope这个注解,我们可以用任意被@Scope所注解的注解来替代@Singleton,它告诉Dagger2在自动生成的代码中,使用DoubleCheck缓存获取到的对象实例来保证在Component组件所依附的生命周期内对象的单例性,

2. 使用抽象类作为引用时如何注入

在大部分情况下,FactoryA应该作为Factory抽象类的具体实现存在,同时Factory还可能有其它实现类,即有如下代码:

public abstract class Factory {}
public class FactoryA extends Factory {}
public class FactoryB extends Factory {}

此时我们需要对module作出如下修改

@Module
public abstract class AppModule {
    @Singleton
    @Binds
    public abstract Factory bindsFactory(FactoryB factoryB);

    @Singleton
    @Provides
    public static FactoryA providesFactoryA() {
        return new FactoryA();
    }

    @Singleton
    @Provides
    public static FactoryB providesFactoryB() {
        return new FactoryB();
    }
}

从这里可以看出

  • @Binds注解用于module中的抽象方法,这个方法的参数应该是具体实现类,返回值应该是抽象类,它告诉Dagger2在自动生成的代码中注入抽象类引用对象时,应该使用哪一个具体实现类作为实例被获取
  • 在使用抽象类作为module时,获取实例对象的方法只能使用static静态方法

2.1 解决@Provides注解的方法返回类型一样的问题

在上面的示例中,可能有同学已经尝试将providesFactoryAprovidesFactoryB的方法返回修改为Factory,然而单纯这样修改并不能通过编译,因为Dagger2仅通过返回类型无法判断调用哪一个方法获取所需的实例,此时需要@Named登场了:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

@Name可以注解在参数、成员变量、方法上,事实上真正起作用的是@Qualifier,我们将AppModule改写成如下代码:

@Module
public abstract class AppModule {
    @Singleton
    @Binds
    public abstract Factory bindsFactory(@Named("factoryA") Factory factory);

    @Singleton
    @Provides
    @Named("factoryA")
    public static Factory providesFactoryA(@Named("machine1") Machine machine) {
        return new FactoryA(machine);
    }

    @Singleton
    @Provides
    @Named("factoryB")
    public static Factory providesFactoryB(@Named("machine2") Machine machine) {
        return new FactoryB(machine);
    }
}

不同value的@Named注解标记后,Dagger2就可以正确的区分出对应的实例,除此之外我们可以通过自定义不同的被@Qualifier注解的注解来区分,具体实现请自行实践

3. 将App中的单例对象同样注入到MainActivity中

要解决这个问题,我们可以使用@Component的第二个属性dependencies来依赖AppComponent

@ActivityScope
@Component(modules = {MainModule.class}, dependencies = {AppComponent.class})
public interface MainComponent {
    void inject(MainActivity activity);
}

这里依赖之后会产生一个小问题,就是存在依赖关系的组件的作用域标记不能相同,因为在AppComponent中我们标记了@Singleton,因此在这里我们自定义了新的@Scope,即@ActivityScope

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

这里如果我们要使获取到的ProductMainComponent实例的生命周期内保持单例,我们就需要给提供实例的方法注解上同样的@ActivityScope

@Module
public class MainModule {
    @ActivityScope
    @Provides
    public static Product provideProduct(Worker worker) {
        return new Product(worker);
    }
}

在完成上述改变之后,我们需要在构建MainComponent实例时传入AppComponent的实例

public class MainActivity extends AppCompatActivity {
    @Inject
    Factory mFactory;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainComponent mainComponent = DaggerMainComponent
                .builder()
                //这里传入appComponent实例,我们可以通过application获取到
                .appComponent(((App) getApplication()).getAppComponent())
                .build()
                .inject(this);
    }
}

最后一步,我们需要AppComponent接口添加一个方法,用来提供其所持有的单例对象

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(App app);

    Factory factory();
}

4. 懒加载机制——Lazy

因为在实际场景中,有的对象并不是无时不刻的被使用到,我们需要它在真正被使用时才被实例化,那么你可以使用Lazy,这里以Product为例,代码修改如下即可:

@Inject
Lazy<Product> mProduct;

Product product = mProduct.get();

5. 小结

成功解决问题之后(建议在每种情况下都看一下自动生成的代码,代码本身并不复杂,有利于理解Dagger2在背后是如何关联这些内容的),我们应该对三个构成部分有一定认识:

  • Module类:提供注入对象的实例,如果可以使用@Inject注解构造函数来提供实例甚至可以不需要这一部分
  • Component接口:解决直接依赖关系的中间桥梁,Dagger2会生成其实现类从而将获取到的实例赋值到目标容器中,可以认为它具有与依赖的目标容器相同的生命周期
  • 目标容器:持有注入对象的引用,依赖于Component的实现类
    原文作者:白与兰与白兰地
    原文地址: https://www.jianshu.com/p/ea5ede0d8b41
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞