Android Dagger2使用小结

作者: lovejjfg | 来源:发表于2016-12-25 17:15 被阅读1740次

    都说不用 dagger2mvp 模式都是耍流氓,但是,但是我之前一直都在耍流氓?所以这两天入门了一下 dagger2

    参考的项目时googlesamples/android-architecturedagger 分支。还有就是 dagger 里面自带的example。

    dagger的定义

    A fast dependency injector for Android and Java.
    Android和Java的依赖快速注入器。

    定义是如此的简洁,那么到底是撒意思呢?应该如何理解?我现在的理解就是它可以快速自动的构建出我们所需要的依赖对象,这里的依赖对象可以理解为某一个成员变量。例如在 MVP 中,VP 层就是互相关联的, V 要依赖对应的 P,而 P 也要依赖对应的 Vdagger 能解决的就是这种依赖关系,通过注入的方式,将双方的耦合再次降低,在实际的使用中体现为一个注解想要的对象就创建好了,咱们不用再去管理所依赖对象的创建等情况了。

    dagger 入门

    注入,肯定涉及到 将什么 注入 到哪里 的三个问题,那接下来就围绕着三个方面展开咯!

    自定义 Module 注入

    Module 中使用 @Provides 提供相关的注入对象是第一种实现方式。具体做法是定义一个类,加上 @Moudule 的注解。然后在里面使用 @Provides 定义返回注入对象的方法。

    @Module
    public final class ApplicationModule {
    
        private final Context mContext;
    
        ApplicationModule(Context context) {
            mContext = context;
        }
    
        @Provides
        Context provideContext() {
            return mContext;
        }
    
        @Provides
        DailyApiService provideDailyApiService() {
            return BaseDataManager.getDailyApiService();
        }
        @Provides
        int provideAge() {
            return 500;
        }
    }
    
    Provider.png

    关于 Module 中的方法,按照官方解释咱们需要有以下的注意事项,每个方法应该是无参的,返回的就是需要注入的类型的。

    构造方法注入

    如果我们可以访问对应的构造方法,那儿这里也提供了另外一种注入的方式,就是在构造方法是使用 @Inject 的注解了。

    Inject相关.png

    这个注解可以定义在构造方法上,方法上,字段上,但是这里有一个优先顺序,就是最先执行构造方法上的,其次是相关字段的,最后就是相关方法的。字段的权限不能是私有的。如果有多个构造方法,@Inject只能指定其中的一个。

    @Inject
    public Person(String birthday,  String name) {
        this.birthday = birthday;
        this.name = name;
    }
    

    到这里基本上差不多说完了第一部分 将什么 的问题。

    注入 到哪里

    对于 注入 到哪里 这两个问题,这里首先要引入 @Component 的注解了,举个场景,例如我们在 Activity 里经常会使用到全局的 Application ,在 Application 的初始化中也可能初始化好某些类( ApiService ),那么这个关系就是 Activity 依赖 Application , Application 依赖 ApiService

    首先我们定义一个 AppComponent ,这是一个接口类,dagger 会自动生成具体实现类。 @Component 定义了需要注入的 Modules 对象 还有可以指定 所依赖的其他 Component 组件。对于 @Component 的使用,它应该定义在接口或者抽象类中,并且里面至少要有一个方法,至于每个方法的命名,没有特别的指定符合相关命名规范就好了。

    @Singleton
    @Component(modules = { ApplicationModule.class})
    public interface AppComponent {
    
        Context context();  
    
        DailyApiService getdailyApiService();  
    
        int getAge();
    }
    

    通过以上步骤,就已经定义好了 Component 和需要注入的 Module 了,然后在 APP 中来试着注入我们定义的 age 字段看看。这里第一个问题是咱们怎么获取对应的 AppComponent 实现类呢?定义好了 ComponentModule 之后记得 rebuild 一下,然后 dagger 会自动生成对应的 Component 实现类,名字的话就是 Dagger+ComponentName (如果你是内部类的话似乎还有外部的 ClassName ),然后调用其的 builder() 方法,传入依赖的 Module 或者 Component ,最后就创建好了对应的实现类了。

    但是到这里,你会发现 log 显示的 age 根本就不是我们指定的 500,为什么呢?怎么就没有注入成功呢?!因为我们还没有执行真正的注入这个动作啊,还缺了最后一个方法,执行我们的注入,上面只是把对应的Component 创建出来了而已,并没有注入呢!那么最后这一步怎么定义呢?

    Inject_Method.png

    根据文档,我们需要在 AppComponent 接口中再定义一个方法,这个方法只能有一个参数,这个参数就是指定我们之前所说的哪里,这个方法可以返回 void 或者该类型,咱们通常的写法都是返回 void 的。在接口中添加该方法,修改了 Component 记得要 rebuild 哟。

    void inject(App app);
    

    App 中最后调用 mAppComponent.inject(this) ,再次运行,这次就可以看到已经注入成功了,到这里,dagger 的入门就搞定了。用一个比喻就是 Component 相当于一个注射器,是个容器,还有一个针头,里面装的就是使用 @Provides 或者 @Inject 定义的需要注入的对象,通过inject(Type type) 的方法指定需要注入到指定的对象中,这样就完成了整个注入过程。

    dagger进阶

    相同类型多次注入

    @Qualifier
    如果我们在 Module 中有重复的类型返回,例如我定义两个 int 类型的providesModule 中的话,编译直接会报错:

    xxx is bound multiple times:

    那如果我们真的需要注入同一类型多次呢,这个问题总会有解决方案的吧?要是真的这么坑估计也没人用 dagger 了吧!哈哈。下面说明这个问题应该如何解决。

    dagger 中具有相同类型返回的情况时,可以使用@Qualifier 的注解来区分,而 dagger 已经为我们提供了一个其子类 @Named("xx") 的注解。

    例如在上面的 Module 中可以如果需要返回两个 int 类型的话需要这么声明:

    @Singleton
    @Provides
    @Named("number")
    int provideNumber() {
        return 5;
    }
    
    @Provides
    @Named("age")
    int provideAge() {
        return 500;
    }
    

    并且在调用的位置,也需要使用同样的 @Named("age") 的来指定你需要注入的是哪个具体的对象。

    懒加载

    在上面的比喻中,一针扎进去,是撒都给你打进去了,那么如果有些我想要在调用的时候才加载呢?这里 dagger 提供了 Lazy<T> 的方式来注入。

    对应的获取就是:

    @Inject
    Lazy<String> str;//延迟加载
    

    你没有看错,就是这么简单,获取时使用 Lazy 包装一下就行了,在真正需要加载的时候调用 str.get()来加载。

    获取某一个注入对象多次

    比如我们需要一次性创建出10个 Person 对象,这个怎么处理呢?这里就需要使用 Provider<Person> 的类来指定了。获取方式也是 get() 就好了,每次都会创建出行的对象。

    Component 的依赖

    如果我们定义了某一个 ActivityComponent ,并且它依赖咱们的 AppComponent 里面的 APIService 的话就要这样定义了:

    @ActivityScope
    @Component(dependencies = AppComponent.class, modules = ListStoryPresenterModule.class)
    public interface ListStoryComponent {
      void inject(ListStory view);
    }
    

    AppComponent 中需要将获取 APIService 的方法暴露出来,不然还是无法注入成功的。

    那如果我觉得暴露这些方法太麻烦了,那需要怎么办呢?最简单就是使用 @SubComponent ,在所属的父 Component 中定义一个 SubComponent,该 SubComponent 中将会包含父 Component 的所有方法。父 Component 不显示声明都可以。

    @ActivityScope
    @Component(dependencies = AppComponent.class, modules = ListStoryPresenterModule.class)
    public interface ListStoryComponent {
    
        String getName(String name);
    
        ListPresenter.View getView();
    
        SubComponent getSubComponent(SubModule module);
    
    }
    
    @Subcomponent(modules = SubModule.class)
    public interface SubComponent {
    
        void inject(ListStory context);
    }
    

    子类注入

    void inject(Activity view) 注入的方法是可以接受该类型的子类的,但是在子类中定义的 @Inject 是无效的,比如说上面说的那个 AppComponent 的例子中如果最后申明的注入方法是:

    void inject(Application app);
    

    你会发现,age 的值一样不会注入成功的。再举官方的实例:我们定义了 void inject(Self self) 的方法,只有A 和 B 能注入,其子类中的C是无法注入的。

     class Parent {
        @Inject A a;
     }
    
     class Self extends Parent {
        @Inject B b;
     }
    
     class Child extends Self {
        @Inject C c;
     }
    

    那么需要怎么解决这个问题呢?目前我的感觉只能在对应的 Component 中定义一系列需要实现的子类的注入方法咯。

    void inject(App app);
    
    void inject(SubActivityA activity);
    void inject(SubActivityB activity);
    

    作用域

    最后还有一个问题就是 Component 的作用域,@Scope 就是其中来限定相关作用域,例如说我们需要初始化一些全局的工具类,像 GsonSharedPreferences 这些,那我们就需要一个单例就行了,那么 dagger 如何保证注入的对象就是单例的呢?那就需要使用这个@Scope的注解了。然后应该在基类里面初始化一个全局通用的 Component ,然后在每个子类里面一次调用注入的方法。另外这个注解 ComponentModule 是要成对使用的,不然会报错的。然后在具体的子类中实现initInject()的方法。 对了,@Scope 已经实现了的是 @Singleton,我们可以直接使用 。

    public abstract class BaseActivity extends AppCompatActivity {
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            AppComponent appComponent = ((App) getApplication()).getAppComponent();
            initInject(appComponent);
        }
    
        public abstract void initInject(AppComponent appComponent);
    }
    

    小结

    使用 dagger 的好处就是在有依赖对象的时候,进一步不用去考虑所依赖对象的创建什么的,对我们就是隐藏了构造对应对象的过程,省去了创建的相关代码。当然我也只是这两天刚刚上手使用 dagger,所以不免有相关优势没有挖掘出来,或者自己有相关的理解偏差。另外建议大家多看看 dagger的相关注解,除了是英文这个缺点,真的很详尽的。

    相关文章

      网友评论

        本文标题:Android Dagger2使用小结

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