35
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-10

概要

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を使ってキャッシュからインスタンスを取得することになります。

35
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?