美文网首页
Dagger2快速上手指南

Dagger2快速上手指南

作者: 小白兔兽型大发 | 来源:发表于2019-02-22 20:15 被阅读0次

    以迅雷不及掩耳盗铃之势


    dagger

    前言

      dagger翻译过来就是匕首的意思,是一个非常著名的依赖注入框架,由square出品,后来由伟大的google接手,推出第二代dagger2,然后这把匕首以完美适用mvp,高度解耦为大家熟知,不过也因为其学习成本过高,造成了很多人从入门到放弃,放弃到放弃,那这篇文章我们就来梳理一下dagger的使用方法,以便我们快速了解并熟悉这个框架.
      既然要写,网上零零总总的dagger文章也都看了,有一篇说的特别好(一看就是大牛写的,后来翻不到了),他说你搞不明白是因为你对java注解不了解,对依赖注入手段不熟悉,对apt这种扫描解析注解的方式不熟悉,说白了就是基础不牢,所以你要是懂那真是见了鬼了...完全没毛病!物质基础决定上层建筑,基础真的很重要,正所谓万变不离其宗,有一个好基础,框架上手快,代码精简有效,而且对于阅读源码帮助也非常大,不过,注解原理不太明白也不是完全不能学习dagger的.
      其实dagger这套东西就是用注解去解耦的(IOC),举个例子,如果用最通俗的语言来说,我之前写了一套代码,后来业务变了,我加个字段,正常我改一处就行了,但是我发现改一处不行,还要改100处,牵一发而动全身,本来5分钟解决的事情要弄1个小时,有人说程序员的懒惰推动了行业的进步(我说的),于是各种框架应运而生,这些框架的出现就是帮助我们以最短的时间,最小的代价达到目的,没错,一行代码也不愿多写...

    引用

       implementation 'com.google.dagger:dagger:2.21'
       annotationProcessor 'com.google.dagger:dagger-compiler:2.21'
    

      我们简单说一下第二个引用,传统的注解是运行时候利用反射去解析的,比较耗资源,而dagger跟butterknife及3.0之后的eventbus都是编译时注解,什么意思?就是我们写代码编译的时候,去扫描整个项目的dagger注解,然后生成java文件,帮助我们去生成实例,然后做这些事情的工具叫apt(Annotation Processing Tool)注解解析器,比如这个annotationProcessor.同理butterknife也是有的

      //butterknife
     implementation 'com.jakewharton:butterknife:8.8.1'
     annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    

    基本用法

    dagger2最基本的使用方法只需要4个注解:

    • @Inject: 声明被注入的依赖
    • @moudle:提供依赖
    • @Provides:具体提供依赖的方法
    • @Component:连接依赖及被注入方的纽带
      接下来我们具体操作一下,需求:想在MainActivity中创建一块糖的实例,当然不能用new的
    1. 创建module及provides方法,并用相应的注解标注
    @Module
    public class MainModule {
    
        @Provides
        Sugar providesSugar() {
            return new Sugar("红色", true);
        }
    }
    

    实体类为:

    public class Sugar {
        ...
        public Sugar(String color, boolean isSweet) {
            this.color = color;
            this.isSweet = isSweet;
        }
        ...
    }
    
    1. 创建Component并声明注入方法,然后rebuild一下
    @Component(modules = MainModule.class)
    public interface MainComponent {
        void inject(MainActivity mainActivity);
    }
    
    1. 在MainActivity中标注@Inject.并且注入
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MAINACTIVITY";
        @Inject
        Sugar sugar;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerMainComponent.create().inject(this);
            if (sugar != null && sugar.isSweet()) {
                Log.i(TAG, sugar.getColor() + "的糖有点甜");
            }
    
        }
    }
    //打印一下:
    01-23 16:38:25.512 13799-13799/com.gaox.dagger2test I/MAINACTIVITY: 红色的糖有点甜
    

      我们看到红色的糖作为依赖成功注入到了MainActivity,如果后续我还要给糖加形状,加类别,只需要修改module提供糖的方法就行了,哪怕就是把这块糖注入到1000个activity里,也只修改一处!然而上面代码onCreate方法中有一个陌生的DaggerMainComponent,这个是从哪来的?之前我们提到apt会扫描注解生成java文件,那在编译的时候这些文件又在哪里?
      都在这个目录下:

    ├── app/bulid
    │   ├── generated/source    
    │   ├── ├──apt/debug/包名
    │   ├── ├──├──DaggerMainComponent
    │   ├── ├──├──MainActivity_MembersInjector
    │   ├── ├──├──MainModule_ProvidesSugarFactory
    

    在debug下面自己的包名中发现了新出现了3个类,而DaggerMainComponent就在其中,接下来我我们分析一下,看看这3个类如何将依赖注入的?

            DaggerMainComponent.create().inject(this);
    

    从这行代码开始,展开DaggerMainComponent这个类

    // Generated by Dagger (https://google.github.io/dagger).
    package com.gaox.dagger2test;
    
    import dagger.internal.Preconditions;
    
    public final class DaggerMainComponent implements MainComponent {
      private final MainModule mainModule;
    
      private DaggerMainComponent(MainModule mainModuleParam) {
        this.mainModule = mainModuleParam;
      }
    
      public static Builder builder() {
        return new Builder();
      }
    
      public static MainComponent create() {
        return new Builder().build();
      }
    
      @Override
      public void inject(MainActivity mainActivity) {
        injectMainActivity(mainActivity);
      }
    
      private MainActivity injectMainActivity(MainActivity instance) {
        MainActivity_MembersInjector.injectSugar(
            instance, MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule));
        return instance;
      }
    
      public static final class Builder {
        private MainModule mainModule;
    
        private Builder() {}
    
        public Builder mainModule(MainModule mainModule) {
          this.mainModule = Preconditions.checkNotNull(mainModule);
          return this;
        }
    
        public MainComponent build() {
          if (mainModule == null) {
            this.mainModule = new MainModule();
          }
          return new DaggerMainComponent(mainModule);
        }
      }
    }
    
    1. create方法中通过典型的建造者模式new Builder().build(),将mainModule 实例化了,并且做为参数在实例化了DaggerMainComponent,等于将mainModule放到了DaggerMainComponent容器中!
    2. inject方法,通过观察我们发现DaggerMainComponent是实现了MainComponent接口,为其实例,也就是调取了DaggerMainComponent的inject方法,调用方法顺序为:
    inject(MainActivity mainActivity) -> 
    injectMainActivity(mainActivity) ->
    MainActivity_MembersInjector.injectSugar(instance,   
    MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule));
    
    • MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule))提供了sugar

    • injectSugar

    public static void injectSugar(MainActivity instance, Sugar sugar) {
        instance.sugar = sugar;
      }
    

    将sugar赋值给activity中的sugar,这样依赖注入就完毕了,而且通过观察DaggerMainComponent,当然我们还可以写成这样:

      DaggerMainComponent.builder()
                    .mainModule(new MainModule())
                    .build()
                    .inject(this);
    //这样的话我们的module是可以带参数的!
    

    以上就是最基本的使用方法,只要翻一翻生成的java文件就非常的清楚,报了错也可以很快定位,不会一脸懵逼,那除了这种方式,还有一种非常轻便的用法,使用起来更简单:
    简单来说就是去掉module,inject直接声明在依赖的构造函数上,比如:

    public class Sugar {
        private String color;
        private boolean isSweet;
        
        @Inject
        public Sugar(){
        }
    }
    //component去掉module
    @Component
    public interface MainComponent {
        void inject(MainActivity mainActivity);
    }
    //打印  
      if(sugar!=null){
                Log.i(TAG, sugar.toString());
            }
    01-24 10:16:58.784 8093-8093/com.gaox.dagger2test I/MAINACTIVITY: Sugar{color='null', isSweet=false}
    

    sugar已被实例化,只是没有赋值,说明也是成功的,我们之前的Sugar是在module中自己new出来的,由module提供给MainActivity,但是这种方式是什么地方实例化Sugar的呢?再来看 一下DaggerMainComponent中的injectMainActivity方法

     private MainActivity injectMainActivity(MainActivity instance) {
        MainActivity_MembersInjector.injectSugar(instance, new Sugar());
        return instance;
      }
    

    这里有几点要注意一下:

    1. 用inject标注的构造函数,不一定是空参的,有其他的对象作为参数也是可以的,像这样:
     @Inject
        public Sugar(Butter butter) {
            this.butter = butter;
        }
    //相对应的injectMainActivity:
     private MainActivity injectMainActivity(MainActivity instance) {
        MainActivity_MembersInjector.injectSugar(instance, getSugar());
        return instance;
      }
    
    private Sugar getSugar() {
        return new Sugar(new Butter());
      }
    

    思考:那如果我把参数由对象换成int,string,list啊等等行不行?
    那肯定不行啊,上面很明显,apt帮我们实例化对象,都不是我们自己new的,人家又不知道你要赋什么值?当然不行了,有需求的话可以用moudle带参数

    1. 如果同时在构造方法上声明了inject,又引用了moudle,分别对应了两个构造方法,那到底执行那种呢? 答案是moudle,就不举例了

    进一步使用

    • module中的provides方法带参数
      如果我们的provides方法中的实例需要一定的参数才可以初始化,比如:
    //需要butter
    new Sugar("红色", true, butter);
    //当然我们可以将butter,new在这里,但是如果有100个需要butter的,难道要new100次吗?
    

    那我们就可以让provides方法提供参数:

     @Provides
        Sugar providesSugar(Butter butter) {
            return new Sugar("红色", true, butter);
        }
    

    参数中的Butter有两种初始化的方法:

    1. Butter这个类用Inject注解去标注构造函数
      DaggerMainComponent中生成Sugar的时候会直接new出butter.
    private Sugar getSugar() {
        return MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule, new Butter());
      }
    
    1. 在本module提供一个全新的provides方法
     @Provides
        Butter providesButter() {
            return new Butter();
        }
    

    DaggerMainComponent中生成Sugar的时候会提供一个全新的Butter方法:

    private Sugar getSugar() {
        return MainModule_ProvidesSugarFactory.proxyProvidesSugar(
            mainModule, MainModule_ProvidesButterFactory.proxyProvidesButter(mainModule));
      }
    //proxyProvidesButter
     public static Butter proxyProvidesButter(MainModule instance) {
        return Preconditions.checkNotNull(
            instance.providesButter(), "Cannot return null from a non-@Nullable @Provides method");
      }
    //providesButter
     @Provides
        Butter providesButter() {
            return new Butter();
        }
    

    这样一来就很方便了,我们就可以统一管理实例,比如实际开发中通常会有一个HttpModule,用来提供网络配置的一些实例,如下:

    private Retrofit createRetrofit(Retrofit.Builder builder, OkHttpClient client, String url) {
            return builder
                    .baseUrl(url)
                    .client(client)
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        @Singleton
        @Provides
        Retrofit.Builder provideRetrofitBuilder() {
            return new Retrofit.Builder();
        }
    
        @Singleton
        @Provides
        OkHttpClient.Builder provideOkHttpBuilder() {
            return new OkHttpClient.Builder();
        }
        @Singleton
        @Provides
        OkHttpClient provideClient(OkHttpClient.Builder builder) {
    
            if (BuildConfig.DEBUG) {
                builder.addInterceptor(new LoggingInterceptor());
            }
          //...太长省略
    }
    
        @Singleton
        @Provides
        Retrofit provideApiServiceRetrofit(Retrofit.Builder builder, OkHttpClient client) {
            return createRetrofit(builder, client, APIService.TEST);
        }
    
    • @Name,@ Qualifier,@Scope
    1. @Name跟@Qualifier
      假设一个module有两个方法提供同一个依赖,那么不管我们用上面说的哪种方式都会报错,因为dagger不知道我们到底想用哪一种,所以我们需要进行区分,非常简单,我们只需要额外添加一个@named,比如:
        @Provides
        @Named("sugarWithButter")
        Sugar providesSugar(Butter butter) {
            return new Sugar("红色", true, butter);
        }
    
        @Provides
        @Named("sugarWithNone")
        Sugar providesSugar2() {
            return new Sugar();
        }
    

    同样inject也要加上

        @Inject
        @Named("sugarWithButter")
        Sugar sugar;
    
        @Inject
        @Named("sugarWithNone")
        Sugar sugar2;
    

    @Qualifier跟@Name其实是一样的,只不过我们需要通过@Qualifier去自定义一个注解,然后替换掉@Name就可以了,这样不用写字符串,避免出错,例如:

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SugarWithNone {
    }
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SugarWithButter {
    }
        //替换掉
        @Provides
        @SugarWithButter
        Sugar providesSugar(Butter butter) {
            return new Sugar("红色", true, butter);
        }
    
        @Provides
        @SugarWithNone
        Sugar providesSugar2() {
            return new Sugar();
        }
        //inject
        @Inject
        @SugarWithButter
        Sugar sugar;
    
        @Inject
        @SugarWithNone
        Sugar sugar2;
    
    1. @Scope
      @Scope这个注解实际上就是定义作用域,我们最常用的@Singleton就是通过scope定义的,如下:
    /**
     * Identifies a type that the injector only instantiates once. Not inherited.
     *
     * @see javax.inject.Scope @Scope
     */
    @Scope
    @Documented
    @Retention(RUNTIME)
    public @interface Singleton {}
    

    假如我们在同一个activity中通过@Inject,注入很多相同的依赖,比如:

        @Inject
        @SugarWithNone
        Sugar sugar2;
        @Inject
        @SugarWithNone
        Sugar sugar3;
    
       // com.gaox.dagger2test.simple.Sugar@b1753e2
       // com.gaox.dagger2test.simple.Sugar@749c373
    

    发现不是同一个对象,但是一旦我加上@Singleton

        @Provides
        @Singleton
        @SugarWithNone
        Sugar providesSugar2() {
            return new Sugar();
        }
    
    @Singleton
    @Component(modules = MainModule.class)
    public interface MainComponent {
        void inject(SimpleActivity simpleActivity);
    }
    
      //  com.gaox.dagger2test.simple.Sugar@b1753e2
      //  com.gaox.dagger2test.simple.Sugar@b1753e2
    

    打印内存地址一致,发现@Singleton这个注解可以让提供的依赖变成单例的,这里需要注意两点

    1. 由moudle提供的依赖,想让其成为单例,需要标注两处,一处是提供依赖的provides方法,一处是连接本module的component
    2. 如果是有构造方法直接inject提供依赖的,除了component之外,还需要在类名上标注@Singleton

    @Singleton这个注解是dagger2默认实现的,严格来讲,他只是负责在component连接区域负责提供单例模式,并且其生命周期保持一致,那如果我想自己去划范围,那就需要自定义@Scope,比如说我们在实际开发中可以自定义AppScope,ActivityScope,FragmentScope,那其提供的依赖只要是用这些注解标注,其生命周期就各自对应一一绑定了,并且为单例,举个例子,自定义如下:

    @Scope
    @Retention(RUNTIME)
    public @interface ActivityScope {
    }
    
    • Component依赖
      component依赖类似于我们平常写代码将公共部分进行抽取,这样会避免使用的时候重复的去写moudle及依赖方法,我们只需要将重复的依赖抽取到基类Module,并在基类component中暴露出方法就可以,并且还可以给其指定是否为单例,最典型的例子,定义一个appModule及appComponent用于提供全局的appContext,流程如下:
    1. 定义appMoudle, appComponent并暴露方法
    //appMoudle
    @Module
    public class AppModule {
        private App appContext;
        public AppModule(App appContext) {
            this.appContext = appContext;
        }
    
        @Provides
        @Singleton
        public App providesAppContext() {
            return appContext;
        }
    }
    //appComponent
    @Singleton
    @Component(modules = AppModule.class)
    public interface AppComponent {
        App getAppContext();
    }
    

    AppComponent暴露出一个getAppContext的方法来提供AppModule中的上下文

    1. 定义activityModule,及activityComponent
    //activityModule
    @Module
    public class ActivityModule {
        @Provides
        public RetrofitHelper providesRetrofitHeloper(App appContext) {
            return new RetrofitHelper(appContext);
        }
    }
    //activityComponent
    @ActivityScope
    @Component(dependencies = AppComponent.class, modules = ActivityModule.class)
    public interface ActivityComponent {
        void inject(DependencyActivity dependencyActivity);
    }
    

    这边要注意如果被依赖component的用到了scope,那依赖让也要定义(@ActivityScope)

    1. 在App中定义获取appComponent的方法
     public static AppComponent getAppComponent() {
            if (appComponent == null) {
                appComponent = DaggerAppComponent.builder()
                        .appModule(new AppModule(appContext))
                        .build();
            }
            return appComponent;
        }
    
    1. activity进行注入
     @Inject
     RetrofitHelper retrofitHelper;
    
    DaggerActivityComponent.builder()
                    .appComponent(App.getAppComponent())
                    .activityModule(new ActivityModule())
                    .build().inject(this);
    

    只要依赖了,就会有一个.xxxComponent的的方法去绑定xxxComponent,我们这边只需要将App中定义的方法获取,扔进来就可以了.打印一下:

    public class RetrofitHelper {
        public RetrofitHelper(App appContext) {
            Log.i("=======", "RetrofitHelper:" + appContext.toString());
        }
    }
    //RetrofitHelper:com.gaox.dagger2test.dependencies.App@fe34ad
    //retrofitHelper打印:
    //com.gaox.dagger2test.dependencies.RetrofitHelper@b1753e2
    

    我们发现,context首先实例化,然后做为参数将retrofitHelper成功实例化.
    不仅仅是这样 App提供的是全局的,我还可以再定义一个类用构造函数进行注入:

       public class ApiService {
        @Inject
        public ApiService(App appContext) {
    
        }
    }
    //打印ApiService
    //com.gaox.dagger2test.dependencies.ApiService@749c373
    

    实际开发中,在mvp模式里经常会用到这种方式在presenter中去提供网络请求的一个工具类,如下:

        @Inject
        public MainPresenter(RetrofitHelper mRetrofitHelper) {
            this.mRetrofitHelper = mRetrofitHelper;
        }
    

    而且,最简单粗暴,想用appContext,直接Inject,比如:

     @Inject
     App appContext;
    //打印一下,发现跟上面一个地址,为单例
    //com.gaox.dagger2test.dependencies.App@fe34ad
    

    想一想,开发中放到baseActivity中,app上下文随便拿还是很方便的

    • MVP+Dagger2
      MVP是什么模式在这边我们就不介绍了,我们要想在mvp这种模式中去使用dagger,无非就是将P注入到V,再把V注入到P作为回调,我们之前总结的用法是完全可以实现的,在这边我们写一个简单的架子,以供参考
    1. 首先我们需要定义一个module去提供view跟presenter,再定义一个component作为连接桥梁
    @Module
    public class MVPModule {
    
        private MVPContact.MvpView view;
    
        public MVPModule(MVPContact.MvpView view) {
            this.view = view;
        }
    
        @Provides
        public MVPContact.MvpView providesMVPView() {
            return view;
        }
    
    
        @Singleton
        @Provides
        public MVPresenter providesMVPresenter(MVPContact.MvpView mvpView){
            return  new MVPresenter(mvpView);
        }
    }
    @Singleton
    @Component(modules = MVPModule.class)
    public interface MVPComponent {
        void inject(MVPActivity mvpActivity);
    }
    

    其实通过看moudle就已经很清楚,我们利用了构造方法将当前的view传进来进行初始化,然后将view作为参数去初始化presenter,当然,这不是真正的v注到p,只是取了个巧,因为我认为再把

    DaggerxxPresenterComopnent.build().moudle.....省略
    

    写到presenter里有点不美好,所以就用构造方法传了..

    1. 接下来定义presenter 及 activity注入
    public class MVPresenter {
    
    
        private  MVPContact.MvpView view;
    
        public MVPresenter(MVPContact.MvpView view) {
            this.view =view;
        }
    
        public void injectPSuccess(){
            Log.i("=====","p被成功注入");
            view.injectVSuccess();
    
        }
    }
    //activity
       DaggerMVPComponent.builder()
                    .mVPModule(new MVPModule(this))
                    .build()
                    .inject(this);
    
            mvPresenter.injectPSuccess();
    
        }
    
        @Override
        public void injectVSuccess() {
            Log.i("=====","v被成功注入");
        }
    //打印
    //=====: p被成功注入
    //=====: v被成功注入
    

    总结一下:
    思路就是一个view需要一个moudle去提供p,而view又作为参数去生成p,
    进而互相调用.

    这个例子基本上就是mvp最简单的dagger使用了,不存在更简单的,实际开发中当然要根据业务需要进行进一步封装,之前说的component依赖肯定也要用上,用法了解了,都不困难.

    这篇文章也是从17年开始写,写了一半,静静的躺在笔记里,到现在一晃又两年了,附demo,也希望自己可以变成自己想要成为的人.

    参考资料
    todo-mvp-dagger
    dagger入门

    相关文章

      网友评论

          本文标题:Dagger2快速上手指南

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