美文网首页
Dagger2新版注解及源码解析

Dagger2新版注解及源码解析

作者: very_mrq | 来源:发表于2018-08-30 10:40 被阅读363次

    一、序

    接: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的源码实现

    相关文章

      网友评论

          本文标题:Dagger2新版注解及源码解析

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