Dagger2深入理解

原文出处:alighters 。

最近,看到一些小伙伴想要入门Dagger2,加之最近刚经历了Dagger2的水深火热,在这里针对Dagger2中不同的注解方式,会生成怎样的代码,结合其生成的不同代码,来帮助大家做一些深入的理解。

概念

首先,Dagger2是一个DI的解决方案,跟之前接触过的Spring相比,它最主要的好处是通过apt插件在编译阶段时,用来生成注入的代码;而Spring是需要在运行时,通过XML或者注解来进行代码注入的。所以,相比来说,在性能上,Dagger2是优于Spring,但带来的是编译阶段时间的延长。这样的话,每当我们修改或者添加这些注解代码的时候,就需要我们重新Build一下(即由apt插件来生成我们所需要使用的代码),build时间的延长,感觉这对Android开发程序员来说,应该习以为常了吧。(掩面而泣。。。)

另外,不得不提的就是apt插件的成熟。apt插件通过生成代码的方式会使得我们则针对特定规则的代码,通过添加注解,使用apt在编译阶段生成代码,减少我们的代码书写量。在gayhub上,已经有很多成熟的库,在使用apt来生成代码,像bundler这个库,就是通过封装Intent参数跟界面组件来绑定,这样我们就可不必通过getIntent来一个个获取参数。另外还提供了使用saveState和restoreState,使得我们在界面组件异常退出的时候,不必再使用savedInstance来进行数据的保存与获取。还有就是支持Parcelable数据以及自定义数据parser,另作者不由不喜欢啊。

使用

根项目添加apt的版本依赖

dependencies {
    ...
   classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    ...
}

项目中配置apt插件的使用,以及Dagger2的版本

apply plugin: 'com.neenbedankt.android-apt'
dependencies {
   compile 'com.google.dagger:dagger:2.0.2'
   compile 'com.google.dagger:dagger-compiler:2.0.2'
   compile 'org.glassfish:javax.annotation:10.0-b28'
}

重要概念

这里简单提一下它们的主要作用,dagger2两个很重要的东西,不过介绍的文章很多,需要的话,可自行查找一下。

  • Component

    用来为“被注入方“提供其所需要的注入类。当在子Scoped的Component中,也要被注入相同的类的时候,则需要在当前的Component添加相应的返回对应的类的方法。

  • Module

    用来提供上层所需要的依赖类。这里所带来的好处就是,所有的依赖类都是通过Module来提供出去,当我们的依赖发生改变时,我们只需要在这里改变提供一个新的对象类即可,而不影响我们上层使用的代码。这里,举一个最简单的例子就是,当我们调用服务器提供的API的时候,我们为上层提供了一个Repository命名的接口,并对其返回的是调用API的类;这里,当API还未实现,我们仅仅对其提供一个MockAPI即可,上层的调用者根本不需要关心我的数据提供从哪里来,是否真实。

依赖实现辅助类

Provider

Provider定义了一个来提供泛型T的方式,是个很简单的接口。

public interface Provider<T> {
   /**
    * Provides a fully-constructed and injected instance of {@code T}.
    *
    * @throws RuntimeException if the injector encounters an error while
    *  providing an instance. For example, if an injectable member on
    *  {@code T} throws an exception, the injector may wrap the exception
    *  and throw it to the caller of {@code get()}. Callers should not try
    *  to handle such exceptions as the behavior may vary across injector
    *  implementations and even different configurations of the same injector.
    */
   T get();
}

ScopedProvider

而ScopedProvider在Provider的基础上,加上了Scoped的概念,主要通过指定类型的Factory来创建出来对应类型的ScopedProvider,在get方法上,主要通过double-checked来确保获取实例T的唯一性。

/**
 * A {@link Provider} implementation that memoizes the result of a {@link Factory} instance.
 *
 * @author Gregory Kick
 * @since 2.0
 */
public final class ScopedProvider<T> implements Provider<T> {
   private static final Object UNINITIALIZED = new Object();
   private final Factory<T> factory;
   private volatile Object instance = UNINITIALIZED;
   private ScopedProvider(Factory<T> factory) {
      assert factory != null;
      this.factory = factory;
   }
   @SuppressWarnings("unchecked") // cast only happens when result comes from the factory
      @Override
      public T get() {
         // double-check idiom from EJ2: Item 71
         Object result = instance;
         if (result == UNINITIALIZED) {
            synchronized (this) {
               result = instance;
               if (result == UNINITIALIZED) {
                  instance = result = factory.get();
               }
            }
         }
         return (T) result;
      }
   /** Returns a new scoped provider for the given factory. */
   public static <T> Provider<T> create(Factory<T> factory) {
      if (factory == null) {
         throw new NullPointerException();
      }
      return new ScopedProvider<T>(factory);
   }
}

Factory

Factory仅仅继承了一个Provider,并是一个空的实现。

public interface Factory<T> extends Provider<T> {}

MembersInjector

从类的注释中,可以看出它的主要作用是给类的属性字段,或者方法参数来提供注入,并忽略是否在含有构造器的注入,都会生成MembersInjector的类。

/**
 * Injects dependencies into the fields and methods on instances of type {@code T}. Ignores the
 * presence or absence of an injectable constructor.
 *
 * @param <T> type to inject members of
 *
 * @author Bob Lee
 * @author Jesse Wilson
 * @since 2.0 (since 1.0 without the provision that {@link #injectMembers} cannot accept
 *      {@code null})
 */
public interface MembersInjector<T> {
   /**
    * Injects dependencies into the fields and methods of {@code instance}. Ignores the presence or
    * absence of an injectable constructor.
    *
    * <p>Whenever the object graph creates an instance, it performs this injection automatically
    * (after first performing constructor injection), so if you're able to let the object graph
    * create all your objects for you, you'll never need to use this method.
    *
    * @param instance into which members are to be injected
    * @throws NullPointerException if {@code instance} is {@code null}
    */
   void injectMembers(T instance);
}

注入方式

这里通过列举几种不同的注入方式,探究其的实现,了解它们的不同使用场合

构造器注入

这里先看一个简单的类

public class TestData {
   @Inject
   public TestData() {
   }
}

通过在构造器上添加@Inject注解,则会编译生成一个实现了Factory接口的单例类,用来生成TestData类。生成的代码如下:

@Generated("dagger.internal.codegen.ComponentProcessor")
public enum TestData_Factory implements Factory<TestData> {
   INSTANCE;
   @Override
   public TestData get() {
      return new TestData()
   }
   public static Factory<TestData> create() {
      return INSTANCE;
   }
}

可以看出通过构造器注入的生成的Factory类,是一个通过enum来实现的一个单例工厂类,是不是有个新技能Get。

Module注入

实现一个Module来提供TestData的依赖,代码如下:

@Module
public class TestModule {
   @Singleton
   @Provides
   public TestData provideTestData(){
      return new TestData();
   }
}

再查看一下,通过apt生成之后的代码:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class TestModule_ProvideTestDataFactory implements Factory<TestData> {
   private final TestModule module;
   public TestModule_ProvideTestDataFactory(TestModule module) {
      assert module != null;
      this.module = module;
   }
   @Override
      public TestData get() {
         TestData provided = module.provideTestData();
         if (provided == null) {
            throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
         }
         return provided;
      }
   public static Factory<TestData> create(TestModule module) {
      return new TestModule_ProvideTestDataFactory(module);
   }
}

可以看出,针对Module注解,dagger2会根据咱们定义了Provides的注解方法,会生成相应以Module为开头的Factory类,而这个Factory会以Module作为参数,通过调用Module中的代码,来实现注入类的提供。

属性注入,方法注入

这里,以我们常用的Activity为例,毕竟Activity的使用可是不允许我们通过构造器来生成一个Activity的,此时就是MembersInjector的用武之地了。 来看个Activity的代码先。

public class MainActivity extends AppCompatActivity {
   @Inject
   TestData mData;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
   }
}

当然,也可以使用方法来进行注入,像下面这样:

@Inject
public void setTestData(TestData testData){
}

会生成一个MainActivity_MembersInjector的类,如下:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
   private final MembersInjector<AppCompatActivity> supertypeInjector;
   private final Provider<TestData> mDataProvider;
   public MainActivity_MembersInjector(MembersInjector<AppCompatActivity> supertypeInjector, Provider<TestData> mDataProvider) {
      assert supertypeInjector != null;
      this.supertypeInjector = supertypeInjector;
      assert mDataProvider != null;
      this.mDataProvider = mDataProvider;
   }
   @Override
      public void injectMembers(MainActivity instance) {
         if (instance == null) {
            throw new NullPointerException("Cannot inject members into a null reference");
         }
         supertypeInjector.injectMembers(instance);
         instance.mData = mDataProvider.get();
      }
   public static MembersInjector<MainActivity> create(MembersInjector<AppCompatActivity> supertypeInjector, Provider<TestData> mDataProvider) {
      return new MainActivity_MembersInjector(supertypeInjector, mDataProvider);
   }
}

从上方可以看出,MainActivity_MembersInjector类通过实现MembersInjector类,通过injectMembers方法,来给MainActivity的实例,采用Provider获取实例的方式,来给我们之前定义的Inject参数或者属性,来赋值相应的注入类。 另外,我们看到,在injectMembers的方法中,也会调用父类的supertypepInjector的类,来调用父类的参数注入。

Component完成注入

上面,我们提到了Activity所需要的类注入,只能通过方法参数,或者字段属性两个方式,(构造器是行不通的)。其MembersInjector的类页编写好了,我们该怎么调用呢?继续上面的例子,我们来给MainActivity来提供注入方式,神奇的Component就登上舞台了。

@Singleton
@Component(modules = { TestModule.class })
public interface ApplicationComponent {
   void inject(MainActivity mainActivity);
}

这里,component一般在使用的时候,都是需要跟Scope相绑定的,不然会报错。看它的生成代码:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerApplicationComponent implements ApplicationComponent {
   private Provider<TestData> provideTestDataProvider;
   private MembersInjector<MainActivity> mainActivityMembersInjector;
   private DaggerApplicationComponent(Builder builder) {
      assert builder != null;
      initialize(builder);
   }
   public static Builder builder() {
      return new Builder();
   }
   public static ApplicationComponent create() {
      return builder().build();
   }
   private void initialize(final Builder builder) {
      this.provideTestDataProvider = ScopedProvider.create(TestModule_ProvideTestDataFactory.create(builder.testModule));
      this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideTestDataProvider);
   }
   @Override
      public void inject(MainActivity mainActivity) {
         mainActivityMembersInjector.injectMembers(mainActivity);
      }
   public static final class Builder {
      private TestModule testModule;
      private Builder() {
      }
      public ApplicationComponent build() {
         if (testModule == null) {
            this.testModule = new TestModule();
         }
         return new DaggerApplicationComponent(this);
      }
      public Builder testModule(TestModule testModule) {
         if (testModule == null) {
            throw new NullPointerException("testModule");
         }
         this.testModule = testModule;
         return this;
      }
   }
}

我们定义好的Component接口,都会生成一个以Dagger开头的一个实现类。在其中,就可以看到它在实现咱们定义的void inject(MainActivity activity)方法,就是通过使用MainActivity_MembersInjector类,来完成这一步注入的,有木有颇感神奇啊。这就是apt插件的神奇之处,定义好了规则,按照规则来生成代码即可。

这里,紧接着提及一下Scope的的用法。当我们为Component定义了Scope之后,并且在module的方法上,添加了Scope注解,这样Dagger2在Component中生成相应的Provider的时候,就会在前面添加ScopedProvider,来对我们所需要的Provider来提供单例模式的访问。具体可以看到的代码就是上面初始化过程中,生成provideTestDataProvider的字段。

总结

掌握了上面提及的这些内容,则Dagger2的不同注入方法,生成怎样的代码,我们就上手Dagger2的使用,若是在使用中还遇到问题的话,可加入下方的二维码群,来进行交流讨论。另外,感谢明道小伙伴们(月半兄jagerlchad)对Dagger2的讨论。

blob.png