Java
Android
Kotlin
Dagger2

Dagger2のAndroidサポートが何をしているのか理解する

概要

Dagger2では2.10からdagger.androidが入り、2.11でそれがさらに使いやすくなりました。
ここでは生成されたコードを見て、何をしているのかの説明を書いておきます。
ついでにKotlinでのandroidサポートを使った導入の仕方も記述しておきます。

導入

Gradle

build.gradle
buildscript {
    ext.dagger_version = "2.12"
    ・・・
app/build.gradle
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

・・・
dependencies {
・・・
    kapt "com.google.dagger:dagger-compiler:$dagger_version"
    kapt "com.google.dagger:dagger-android-processor:$dagger_version"
    implementation "com.google.dagger:dagger:$dagger_version"
    implementation "com.google.dagger:dagger-android:$dagger_version"
・・・

Module

以下が、Android Supportで追加されたModuleの書き方になります。

AndroidModule.kt
@Module
abstract class AndroidModule {

    @ContributesAndroidInjector
    abstract fun contributeMainActivity(): MainActivity
}

@ContributesAndroidInjector が2.11からできたAPIです。
これにより、2.10で自分で記述していたコードを生成してくれます。
自動生成されたソースは後ほど説明します。

Component

AppComponent.kt
@Singleton
@Component(modules = arrayOf(AndroidInjectionModule::class, AndroidModule::class))
interface AppComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: App): Builder
        fun build(): AppComponent
    }
    fun inject(app: App)
}

AndroidInjectionModule::class がミソです。2.10から追加されました。
@BindsInstanceは、2.9から追加されました。自分でコンポーネントを構築するときに、これを指定したメソッドを呼び出すことで、引数をコンポーネントにInjectできるようになります。

どういうことかというと、@BindsInstance fun application(application: App): Builderを書かないと例えば、以下のようにContextをInjectしようとしてもコンパイルエラーになります。

@Module
class AppModule {
    @Provides
    fun provideContext(application: App) = application.applicationContext
}

class MainDao @Inject constructor() {
    @Inject
    lateinit var context: Context

@BindsInstance fun application(application: App): Builderを記述し、Applicationクラスで設定することで、ModuleでDIされた状態で利用できるようになるということです。

Application

AppComponentから自動生成されたクラスDaggerAppComponentを使ってコンポーネント構築します。

App.kt
class App : Application(), HasActivityInjector {

    @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    override fun activityInjector() = dispatchingAndroidInjector

    override fun onCreate() {
        super.onCreate()

        val objectGraph = DaggerAppComponent
                .builder()
                .application(this)
                .build()
        objectGraph.inject(this)
    }
}

AppComponentで指定した@BindsInstanceのメソッドを使って、AppクラスをModuleで利用できるようにしています。(application(this)の部分です。)
また、dagger.android.HasActivityInjectorインターフェイスが重要です。これは次のActivityのソースとともに説明します。

Activity

Androidサポートのおかげでdagger.android.AndroidInjection.inject(this)だけを書けば、DIされるようになりました。

MainActivity.kt
lass MainActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
    ・・・      

ついでにその他のクラスも載せておきます。

MainViewModel.kt
class MainViewModel @Inject constructor() {
    @Inject
    lateinit var domain: MainDomain 
    ・・・ 

dagger.android.AndroidInjection.inject(this) が具体的に何をしているかというと、Applicationクラス(ここの場合はApp.ktクラス)でdagger.android.HasActivityInjectorが実装されていれば、dagger.android.DispatchingAndroidInjectorを使って引数のActivity(ここではMainActivity)をComponet(これは依存を解決するためのクラス。別名ObjectGraph。)に入れています。(@ContributesAndroidInjectorを書いていないとここでassertエラーになります。)
そして、Activityで@InjectしているフィールドにInjectしてくれます。

もう少し具体的にいうとdagger.android.DispatchingAndroidInjectorが、MainActivityをキーにModuleで定義した@ContributesAndroidInjectorによって自動生成されたコードMainActivitySubcomponentBuilder、さらに、Builderがインスタンスする同じく自動生成されたクラスMainActivitySubcomponentImpldagger.android.AndroidInjectorの実装クラス)を生成し、このクラスがそれぞれのクラスをInjectをします。

説明した対応のコードの一部が以下です。
ActivityのAndroidInjection.inject(this)が呼ばれた先に以下のmaybeInjectメソッドがあります。

dagger.android.DispatchingAndroidInjector.java
 public boolean maybeInject(T instance) { // ① instance=MainActivity
    Provider<AndroidInjector.Factory<? extends T>> factoryProvider =
        injectorFactories.get(instance.getClass()); 
    if (factoryProvider == null) {
      return false;
    }

    @SuppressWarnings("unchecked")
    AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get(); // ② factory=MainActivitySubcomponentBuilder

    try {
      AndroidInjector<T> injector =
          checkNotNull(
              factory.create(instance), // ③ new MainActivitySubcomponentImpl()             
              "%s.create(I) should not return null.",
              factory.getClass().getCanonicalName());

      injector.inject(instance); // ④

①の行の引数のT instanceは、MainActivityです。
②の行のAndroidInjector.Factory<T> factoryには、MainActivitySubcomponentBuilderが入ります。
③の行のfactory.create(instance)でMainActivitySubcomponentImplがインスタンスされます。
④の行のinjector.inject(instance);では、自動生成されたDaggerAppComponentが保持するMembersInjector<MainActivity>(これも自動生成されたMainActivity_MembersInjectorが実装クラス)を使って、MainActivityにDIすべきクラスを設定しています。

自動生成されるコード

@ContributesAndroidInjector によって生成されたコードは以下です。
バージョン2.10では、これを自分で記述する必要がありました。

AndroidModule_ContributeMainActivity.java
@Module(subcomponents = AndroidModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class AndroidModule_ContributeMainActivity {
  private AndroidModule_ContributeMainActivity() {}

  @Binds
  @IntoMap
  @ActivityKey(MainActivity.class)
  abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
      MainActivitySubcomponent.Builder builder);

  @Subcomponent
  public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainActivity> {} // implemenets AndroidInjector.Factory
  }
}

@Binds は、超簡単に説明すると以下の定型コードを書かないで済むようにするアノテーションです。

@Provides
AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(){
    return new MainActivitySubcomponent.Builder()
}

実際は、MainActivitySubcomponent.Builder もabstractなので必要なメソッドを実装しないといけませんが、そういうことです。

@IntoMap@ActivityKey はセットで利用するものです。ここで指定したものが、dagger.android.DispatchingAndroidInjectorで保持されるMapになります。(このMapが最終的に重要になります)

@ActivityKey で指定した値(この場合は、MainActivity.class)がキーになります。値は、メソッドの戻り値(この場合は、MainActivitySubcomponent.Builderになります。ちなみにこのBuilderはAndroidInjector.Factoryの実装になります。

以下がAppComponentから自動生成された対象の部分です。

DaggerAppComponent.java
 ・・・
 private Provider<AndroidInjector.Factory<? extends Activity>> bindAndroidInjectorFactoryProvider;
 ・・・
     @Override
    public AppComponent build() {
      if (application == null) {
        throw new IllegalStateException(App.class.getCanonicalName() + " must be set");
      }
      return new DaggerAppComponent(this);
    }
 ・・・ 
  private DaggerAppComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.mainActivitySubcomponentBuilderProvider =
        new dagger.internal.Factory<
            AndroidModule_ContributeMainActivity.MainActivitySubcomponent.Builder>() {
          @Override
          public AndroidModule_ContributeMainActivity.MainActivitySubcomponent.Builder get() {
            return new MainActivitySubcomponentBuilder();
          }
        }; // 参

    this.bindAndroidInjectorFactoryProvider = (Provider) mainActivitySubcomponentBuilderProvider; // 弐

    this.mapOfClassOfAndProviderOfFactoryOfProvider =
        MapProviderFactory
            .<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>builder(1)
            .put(MainActivity.class, bindAndroidInjectorFactoryProvider)
            .build(); // 壱 bindAndroidInjectorFactoryProvider=dagger.internal.Factory() implements Providerの匿名クラス。上記弐,参、参照

    this.dispatchingAndroidInjectorProvider =
        DispatchingAndroidInjector_Factory.create(mapOfClassOfAndProviderOfFactoryOfProvider);

    this.appMembersInjector = App_MembersInjector.create(dispatchingAndroidInjectorProvider);
  }
  ・・・

重要と説明した「MainActivity.classがキーで、値がMainActivitySubcomponent.Builderのmap」部分を具体的に見ていきます。
壱のmapの値に指定しているbindAndroidInjectorFactoryProviderは
弐を見るとmainActivitySubcomponentBuilderProviderになります。
さらにmainActivitySubcomponentBuilderProviderは、参でdagger.internal.Factoryの匿名クラスになっています。
dagger.internal.Factoryjavax.inject.Providerを継承していて、getで返すインスタンスは、MainActivitySubcomponentBuilderクラスです。
MainActivitySubcomponentBuilderの親クラスがMainActivitySubcomponent.Builderになります。

ちなみにDaggerでは、javax.inject.Provider#get()で取得できるクラスがInject可能であれば、ProviderをDI対象できます。
つまり、以下の定義は不要ということです。

   @Provides
   fun provideMainActivitySubcomponentBuilderProvider() = object: Provider<MainActivitySubcomponentBuilder> {
        override fun get(): MainActivitySubcomponentBuilder {
            return MainActivitySubcomponentBuilder()
        }
    }

なので、キーがMainActivityで、値がProvider<AndroidInjector.Factory<? extends Activity>>が成立するのです。

続きです。DaggerAppComponent.javaの続きです。

DaggerAppComponent.java
  private final class MainActivitySubcomponentBuilder
      extends AndroidModule_ContributeMainActivity.MainActivitySubcomponent.Builder {
    private MainActivity seedInstance;

    @Override
    public AndroidModule_ContributeMainActivity.MainActivitySubcomponent build() {
      if (seedInstance == null) {
        throw new IllegalStateException(MainActivity.class.getCanonicalName() + " must be set");
      }
      return new MainActivitySubcomponentImpl(this); // 「ア」
    }

    @Override
    public void seedInstance(MainActivity arg0) {
      this.seedInstance = Preconditions.checkNotNull(arg0);
    }
  }

  private final class MainActivitySubcomponentImpl
      implements AndroidModule_ContributeMainActivity.MainActivitySubcomponent {
    private MembersInjector<MainViewModel> mainViewModelMembersInjector;

    private Provider<MainViewModel> mainViewModelProvider;

    private MembersInjector<MainActivity> mainActivityMembersInjector;

    private MainActivitySubcomponentImpl(MainActivitySubcomponentBuilder builder) {
      assert builder != null;
      initialize(builder);
    }

    @SuppressWarnings("unchecked")
    private void initialize(final MainActivitySubcomponentBuilder builder) { // 「イ」

      this.mainViewModelMembersInjector =
          MainViewModel_MembersInjector.create(MainDomain_Factory.create()); 

      this.mainViewModelProvider = MainViewModel_Factory.create(mainViewModelMembersInjector);

      this.mainActivityMembersInjector = MainActivity_MembersInjector.create(mainViewModelProvider);
    }

    @Override
    public void inject(MainActivity arg0) {
      mainActivityMembersInjector.injectMembers(arg0); // 「ウ」
    }
  }

MainActivitySubcomponent.Builderは「ア」でMainActivitySubcomponentImplをインスタンスしています。
これを実際にインスタンスしているのは、dagger.android.DispatchingAndroidInjector.java で説明した③の部分factory.create(instance) で、これは結局は、Activity#onCreate() の最初で呼ぶAndroidInjection.inject(this) から呼ばれることになります。

で、MainActivitySubcomponentImpl がインスタンスされると「イ」のメソッドで@Inject でDIしたいクラスのProviderを保持している~Injectorクラスを自動生成されたFactoryクラスでインスタンスして「ウ」で、渡ってきたActivityの@Inject を記述したフィールドに設定しています。

「ウ」で利用されているMainActivity_MembersInjectorは以下です。

MainActivity_MembersInjector.java
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final Provider<MainViewModel> viewModelProvider;

  public MainActivity_MembersInjector(Provider<MainViewModel> viewModelProvider) {
    assert viewModelProvider != null;
    this.viewModelProvider = viewModelProvider;
  }

  public static MembersInjector<MainActivity> create(Provider<MainViewModel> viewModelProvider) {
    return new MainActivity_MembersInjector(viewModelProvider);
  }

  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.viewModel = viewModelProvider.get();
  }
}

上記、viewModelProviderのインスタンスは以下です。

MainViewModel_Factory.java
public final class MainViewModel_Factory implements Factory<MainViewModel> {
  private final MembersInjector<MainViewModel> mainViewModelMembersInjector;

  public MainViewModel_Factory(MembersInjector<MainViewModel> mainViewModelMembersInjector) {
    assert mainViewModelMembersInjector != null;
    this.mainViewModelMembersInjector = mainViewModelMembersInjector;
  }

  @Override
  public MainViewModel get() {
    return MembersInjectors.injectMembers(mainViewModelMembersInjector, new MainViewModel());
  }

  public static Factory<MainViewModel> create(
      MembersInjector<MainViewModel> mainViewModelMembersInjector) {
    return new MainViewModel_Factory(mainViewModelMembersInjector);
  }
}

@Singleton

次のようにすると@Singletonになります。

@Module
class AppModule {
    @Singleton
    @Provides
    fun provideMainDomain() = MainDomain()
}
@Singleton
class MainDomain @Inject constructor()
@Singleton
@Component(modules = arrayOf(AndroidInjectionModule::class, AndroidModule::class, AppModule::class))
interface AppComponent 
・・・

使う側のコードはいつも通りです。

class MainViewModel @Inject constructor() {

    @Inject
    lateinit var domain: MainDomain
    ・・・

こうするとどうなるかを見てみましょう

まずは、付けなかった場合です。

DaggerAppComponent.java
・・・
  private final class MainActivitySubcomponentImpl
      implements AndroidModule_ContributeMainActivity.MainActivitySubcomponent {
・・・      
    private void initialize(final MainActivitySubcomponentBuilder builder) { 

      this.mainViewModelMembersInjector =
          MainViewModel_MembersInjector.create(MainDomain_Factory.create());  // ここが違う
・・・          

MainDomainのProviderクラスが以下です。このクラスは@Singletonを付けた場合も存在します。

MainDomain_Factory.java
public final class MainDomain_Factory implements Factory<MainDomain> {
  private static final MainDomain_Factory INSTANCE = new MainDomain_Factory();

  @Override
  public MainDomain get() {
    return new MainDomain();
  }

  public static Factory<MainDomain> create() {
    return INSTANCE;
  }
}

MainViewModel_MembersInjector#create に渡す引数が、MainDomain_Factoryで、それはget()メソッドでMainDomainのインスタンス化して返します。Factory=Providerで、getが呼ばれるたびにインスタンスされます。

次は、@Singleton を付けた場合です。
MainViewModel_MembersInjector#create に渡すProviderが違います。

DaggerAppComponent.java
      this.mainViewModelMembersInjector =
          MainViewModel_MembersInjector.create(DaggerAppComponent.this.provideMainDomainProvider); // ここが違う
  public static AppComponent.Builder builder() {
    return new Builder();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {
    ・・・
    this.provideMainDomainProvider =
     DoubleCheck.provider(AppModule_ProvideMainDomainFactory.create(builder.appModule));

このDoubleCheck.provider() メソッドでSingleton対象のMainDomainをinstance フィールドで保持しておき、MainDomainProviderが呼ばれた時にそれを返すという仕組みです。

DoubleCheck.java
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
  private static final Object UNINITIALIZED = new Object();

  private volatile Provider<T> provider;
  private volatile Object instance = UNINITIALIZED;

  private DoubleCheck(Provider<T> provider) {
    assert provider != null;
    this.provider = provider;
  }

  @Override
  public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get(); // provider=AppModule_ProvideMainDomainFactory。ここでMainDomainがインスタンスされる
          Object currentInstance = instance;
          if (currentInstance != UNINITIALIZED && currentInstance != result) {
            throw new IllegalStateException("Scoped provider was invoked recursively returning "
                + "different results: " + currentInstance + " & " + result + ". This is likely "
                + "due to a circular dependency.");
          }
          instance = result; // ここでMainDomainがキャッシャされるので呼ばれるたびに同じインスタンスが返る
          provider = null;
        }
      }
    }
    return (T) result;
  }

  public static <T> Provider<T> provider(Provider<T> delegate) {
    checkNotNull(delegate);
    if (delegate instanceof DoubleCheck) {
      return delegate;
    }
    return new DoubleCheck<T>(delegate); // App.ktから呼ばれる
  }

ちなみにキャッシュしているDoubleCheckをインスタンスするのはApp.ktから呼ばれます。
activityなどは、AndroidInjection.inject(this) の内部でprovideMainDomainProviderを使ってキャッシュからインスタンスを取得することになります。