美文网首页Android-Dagger2Android开发Android开发经验谈
Dagger2使用简析——@Scope、@Qualifier、@

Dagger2使用简析——@Scope、@Qualifier、@

作者: 白与兰与白兰地 | 来源:发表于2019-05-20 19:23 被阅读13次

    在了解了简单注入对象的使用后,我们将问题升级。我们平常开发中为了节省资源,在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的实现类

    相关文章

      网友评论

        本文标题:Dagger2使用简析——@Scope、@Qualifier、@

        本文链接:https://www.haomeiwen.com/subject/xpmfzqtx.html