Dagger2快速上手指南

以迅雷不及掩耳盗铃之势


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入门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348