Dagger2新版注解及源码解析

一、序

接:Dagger2.1不是Dagger2

demo地址:https://github.com/mrqatom/DaggerInjection

通过学习,我们知道了新版Dagger的用法,可是作为有追求的骚年,不能仅仅成为API的搬运工,必须要了解一下其中具体的用法以及实现方式。首先我们来看看几个注解的具体作用。

二、@Component.Builder

我们在AppComponent里有如下代码

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }
image.gif

我们大概也猜出他的作用,就是自定义构造component

以前我们的用法像这样:

 @Module
 public class AppModule {

    Application application;

    public AppModule(Application application) {
       this.application = application;
    }

    @Provides
    Application providesApplication() {
       return application;
    }
    @Provides
    public SharedPreferences providePreferences() {
        return application.getSharedPreferences(DATA_STORE,
                              Context.MODE_PRIVATE);
    }

 }

//使用时
DaggerAppComponent appComponent = DaggerAppComponent.builder()
         .appModule(new AppModule(this)) //this : application 
         .build();
image.gif

现在Dagger允许我们自定义Builder:

@Component(modules = {AppModule.class})
public interface AppComponent {

   void inject(MainActivity mainActivity);

   @Component.Builder
   interface Builder {
        AppComponent build();
        Builder appModule(AppModule appModule);
    }
}
image.gif

就像我们以前的用法一样。这看起来多此一举,不过,配合@BindsInstance就会发生不一样的化学反应,下面我们来看看:

@Module
 public class AppModule {

     @Provides
     @Singleton
     public SharedPreferences providePreferences(
                                    Application application) {
         return application.getSharedPreferences(
                                    "store", Context.MODE_PRIVATE);
     }
 }

@Component(modules = {AppModule.class})
public interface AppComponent {
   void inject(MainActivity mainActivity);
   SharedPreferences getSharedPrefs();
   @Component.Builder
   interface Builder {

      AppComponent build();
      @BindsInstance Builder application(Application application);      
  }

//使用时
DaggerAppComponent appComponent = DaggerAppComponent.builder()
           .application(this)
           .build();
image.gif

我们的代码发生了如下改变:

1、AppModule不再需要Application为参数的构造函数,可以直接使用Application

2、component里加入application并加上了@BindsInstance注解,而且无需传入Module

3、使用时无需传入Module而仅仅需要application即可

这个注解有效的简化了Module的初始化并减少了与module的耦合,所以再回头看看demo里的AppComponent是不是更清晰了些呢?

三、@IntoMap

这个注解看名字就可以猜测,用来把什么东西加入map里。

是的,就是把依赖加入map里,先来看看官方简单的例子:

@Module
class MyModule {
  @Provides @IntoMap
  @StringKey("foo")
  static Long provideFooValue() {
    return 100L;
  }

  @Provides @IntoMap
  @ClassKey(Thing.class)
  static String provideThingValue() {
    return "value for Thing";
  }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<String, Long> longsByString();
  Map<Class<?>, String> stringsByClass();
}

@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.longsByString().get("foo")).isEqualTo(100L);
  assertThat(myComponent.stringsByClass().get(Thing.class))
      .isEqualTo("value for Thing");
}
image.gif

我们来梳理一下重点:

1、@intoMap需要和@provides及@binds之类的注解配合使用,这很好理解,就是把依赖in to map而已

2、需要和@mapKey配合使用,即map的key,比如StringKey、ClassKey、ActivityKey...balabala...

3、使用时就和普通map一样

四、@Binds

@Binds和@provides一样,都是提供依赖的作用,也可以说是优化版,我们先来看看@provides的用法:

@Provides
public LoginContract.Presenter 
  provideLoginPresenter(LoginPresenter loginPresenter) {
    return loginPresenter;
}
image.gif

这应该是Dagger使用者比较常见的格式了,相比之下@Binds方式就显得更简洁了:

@Binds
public abstract LoginContract.Presenter
  provideLoginPresenter(LoginPresenter loginPresenter);
image.gif

以下是重点:

1、binds方式是抽象方法,无需方法体

2、只能有一个参数且return类型与其相同

3、使用@Binds后该module变为抽象(废话

4、@binds与@provides不得在同一个Module中使用,必须分开写在两个Module里,可以使用include使其关联就像这样

@Module(includes = Declarations.class)
public class MainActivityModule {
    @Provides
    MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
        return new MainPresenter(mainView, apiService);
    }
}

@Module
public abstract class Declarations {
    @Binds
    abstract MainView provideMainView(MainActivity mainActivity);
}
image.gif

5、如果你一定要将他们写在一个module里,也有方案,把@provides方法变为static即可:

@Module
public abstract class MainActivityModule {
    @Binds
    abstract MainView provideMainView(MainActivity mainActivity);

    @Provides
    static MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
        return new MainPresenter(mainView, apiService);
    }
}
image.gif

五、源码解析

Dagger2是利用APT自动化形成,这里我就不详述了不然一篇文章根本不够,大家可以自行谷歌关键字:‘APT’、‘编译期注解’

我不会一段一段代码的贴,而是讲述大概流程以及一些思想,避免“休闲式学习”,所以大家需要自己打开源码浏览,如果仅看文章大概率会懵逼:),先来看一段Dagger的初始代码

public class MyApplication extends Application implements HasActivityInjector {
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().application(this).build().inject(this);
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }
}
image.gif

继承HasActivityInjector重写activityInjector,定义DispatchingAndroidInjector加上@Inject这里先提一下,后面有用。

主要是 DaggerAppComponent.builder().application(this).build().inject(this) 这句代码,这是一句很明显的建造者模式的代码,是由我们自定义的@Component.Builder形成的,当我们调用.build时会对DaggerAppComponent进行初始化即调用initialize方法,代码如下:

  private void initialize(final Builder builder) {
    this.mainActivityComponentBuilderProvider =
        new Provider<MainActivityComponent.Builder>() {
          @Override
          public MainActivityComponent.Builder get() {
            return new MainActivityComponentBuilder();
          }
        };
  }
image.gif

初始化时创建了ActivityBuilder中由@Binds注解的方法return的类,并用Provider封装了一层,其实也就是我们的@Subcomponent.Builder修饰的类而已,这里我们仅有一个MainActivity

这个类实际上只是一个创建者,调用build方法创建真正的实现类:MainActivityComponentImpl,在这个类里会提供我们Module里用provides注解的依赖,我们对比一下:

    //Module中
    @Provides
    MainView provideMainView(MainActivity mainActivity){
        return mainActivity;
    }
    @Provides
    MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
        return new MainPresenter(mainView, apiService);
    }

    //源码中
    private MainView getMainView() {
      return MainActivityModule_ProvideMainViewFactory.proxyProvideMainView(
          mainActivityModule, seedInstance);
    }

    private MainPresenter getMainPresenter() {
      return MainActivityModule_ProvideMainPresenterFactory.proxyProvideMainPresenter(
          mainActivityModule, getMainView(), new ApiService());
    }

image.gif

proxyProvideMainPresenter其实就是调用module的provideMainPresenter来生成一个Mainpresenter

当MainActivityComponentImpl的inject方法被调用时,以上方法也同时被调用,像这样:

    @Override
    public void inject(MainActivity arg0) {
      injectMainActivity(arg0);
    }

    private MainActivity injectMainActivity(MainActivity instance) {
      //....省略
      MainActivity_MembersInjector.injectPresenter(instance, getMainPresenter());
      return instance;
    }
image.gif

MainActivity_MembersInjector.injectPresenter会将生成的Mainpresenter注入到MainActivity里,也就完成了依赖注入。

至于inject方法什么时候被调用,以及MainActivity如何传入的留待后文分析,我们先回到初始化的地方。

DaggerAppComponent构建完成之后,又调用了他的Inject方法,这个方法了只做了一件事,就是初始化MyApplication中的

DispatchingAndroidInjector,而他里面就保存了我们上面提到的MainActivityComponentBuilder,如果有多个Activity的话,就会把他们保存到一个map中,key为activity名字,这就是@IntoMap的作用了。

所以DispatchingAndroidInjector其实提供了一个仓库的作用,仓库里保存了我们在ActivityBuilder里@binds修饰的类,其实也就是我们所有的Activity

还记得我们在MyApplication中重写的方法吗?

@Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }
image.gif

通过这个方法就可以获取到该“仓库”,然后一步步获取到真正的实现类完成依赖注入,具体我们继续看。

那我们Activity如何获取到该’仓库‘呢?在每一个Activity中都需要调用一句代码:

  AndroidInjection.inject(this);

  //实现
  public static void inject(Activity activity) {
    checkNotNull(activity, "activity");
    //拿到application
    Application application = activity.getApplication();
    //判断application是否继承了HasActivityInjector
    if (!(application instanceof HasActivityInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              application.getClass().getCanonicalName(),
              HasActivityInjector.class.getCanonicalName()));
    }
    //通过刚刚讲到的方法拿到“仓库”
    AndroidInjector<Activity> activityInjector =
        ((HasActivityInjector) application).activityInjector();
    checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());
    //调用“仓库”的inject方法,并传入了相应的Activity
    activityInjector.inject(activity);
  }

image.gif

“仓库”的inject方法会在map里找到我们之前所说的真正实现类完成所有依赖注入工作。

因为“仓库”保存了我们所有Activity,所以只要在Activity里调用上述代码就能完成该Activity的依赖注入。

至于Fragment的注入是如何实现由读者自行阅读,就是在Activity依赖注入实现类里又创建了一个Fragment的ComponentBuilder而已,类似于递归的思想。

至于@ContributesAndroidInjector的实现方式就是和基本版相同,只是自动生成了component而已,帮我们简化了操作。

总结

看完大家可能觉得Dagger源码解析部分很少,事实上Dagger的源码确实不算难,适合刚刚学习看源码的童鞋,克服源码的恐惧就是现在了。至于编译期注解方面的知识,建议大家去看Arouter源码上手。

Dagger的思想是非常优秀的,加上现在更新的越来越简洁,非常推荐大家尝试使用。

课外拓展:研究@Singleton以及@scope的源码实现

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