使用 Kotlin 構建 MVVM 應用程式—提高篇:Dagger-Android

ditclear發表於2019-02-26

使用 Kotlin 構建 MVVM 應用程式—提高篇:Dagger-Android

寫在前面

本篇是對於使用Kotlin構建MVVM應用程式—第四部分:依賴注入 Dagger2 的補充。

依賴注入 Dagger2 這篇文章中,我們瞭解了 Dagger2 是如何進行依賴注入的。

可以簡單的將Dagger2理解成android應用的依賴管理工具。既然Dagger2已經可以滿足我們日常的開發需要了,那麼Dagger-Android又是拿來幹什麼的呢?

為什麼要有Dagger-Android?

對於這個問題,google在Dagger-Android的文件上有解釋:

我們普通的dagger程式碼如下所示:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}
複製程式碼

這會帶來一些問題:

  1. 只是複製貼上上面的程式碼會讓以後的重構比較困難,還會讓一些開發者不知道Dagger到底是如何進行注入的(然後就更不易理解了)
  2. 更重要的原因是:它要求注射型別(FrombulationActivity)知道其注射器。 即使這是通過介面而不是具體型別完成的,它打破了依賴注入的核心原則:一個類不應該知道如何實現依賴注入。

為了解決上述的問題,Dagger-Android應運而生。

Dagger-Android

首先我們需要在app/build.gradle加入相應的依賴

	//dagger2  di
    implementation 'com.google.dagger:dagger:2.16'
    kapt 'com.google.dagger:dagger-compiler:2.16'
	//dagger-android
    implementation 'com.google.dagger:dagger-android:2.16'
    implementation 'com.google.dagger:dagger-android-support:2.16' 
    kapt 'com.google.dagger:dagger-android-processor:2.16'
複製程式碼

注入方法建議看文件更好,這裡簡單描述一下:

還是以MVVM-android為例。

  1. 新增ActivityModule.kt
@Module
abstract class ActivityModule {

    @ContributesAndroidInjector
    abstract fun contributePaoActivity(): PaoActivity

}
複製程式碼

2 . 修改AppComponent.kt

@Singleton
@Component(modules = arrayOf(
        AndroidInjectionModule::class,
        AppModule::class,
        ActivityModule::class)
)
interface AppComponent {

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

    fun inject(application: PaoApp)
}
複製程式碼

相比Dagger2,modules多了AndroidInjectionModule和ActivityModule兩個類。

3 . rebuild一下專案,然後新增PaoApp.kt同時實現HasActivityInjector介面

class PaoApp : Application(),HasActivityInjector{

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.builder().application(app).build().inject(app)
    }

    override fun activityInjector() = dispatchingAndroidInjector

}
複製程式碼

4 . 最後在Activity的onCreate()方法之前呼叫 AndroidInjection.inject(this)進行注入

class PaoActivity : RxAppCompatActivity() {
    @Inject
    lateinit var mViewModel : PaoViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        //////di
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
    }
}
複製程式碼

到此,一個簡單的依賴注入就好了。

當然簡單是絕不簡單的。這些是在寫什麼?完全雲裡霧裡。剛從Dagger轉換成Dagger-Android的直接就勸退了,還不如直接使用Dagger2的好。

確實,對於日常功能迭代的開發團隊來說,普通的dagger更易理解,所以Dagger-Android也算是一個可選項,可以作為一個提高,而且google的很多示例裡dagger的用法都是Dagger-Android,所以還是有必要懂它的原理。

原理剖析

第四部分中,我們也瞭解了普通的Dagger是如何進行依賴注入的,這裡我們再來回顧一次

由AppModule提供所需的依賴
由AppCompent提供注入的途徑
由@Inject標識需要注入的物件
呼叫
DaggerAppComponent.builder()
   .appModule(AppModule(applicationContext)).build()
   .inject(this)
完成依賴注入
複製程式碼

這裡的邏輯比較好理解一些,就是普通的

paoActivity.mViewModel = appComponent.paoViewModel

那Dagger-Android相比之下,又是怎麼樣的邏輯呢?

相比之前,Dagger-Android將Activity/Fragment所需的compoent都放到了一個map物件裡,這個map物件由App的dispatchingAndroidInjector物件持有。其中key值為activity/fragment的class,value為提供相應Component的Provider物件。

Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>>

複製程式碼

當我們在Activity中呼叫 AndroidInjection.inject(this)時,又在做什麼呢?

public static void inject(Activity activity) {
    checkNotNull(activity, "activity");
    Application application = activity.getApplication();
    if (!(application instanceof HasActivityInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              application.getClass().getCanonicalName(),
              HasActivityInjector.class.getCanonicalName()));
    }
	//找到dispatchingAndroidInjector物件
    AndroidInjector<Activity> activityInjector =
        ((HasActivityInjector) application).activityInjector();
    checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());
	//進行注入
    activityInjector.inject(activity);
  }
複製程式碼

activityInjector.inject(activity)裡面的邏輯又可以描述為

一個全域性的單例map物件,通過key值為activity.class即
map.get(activity.class) 找到activity與之對應的Provider<AndroidInjector.Factory<? extends T>>
。。。
。。。
然後經過層層的深入
找到對應的component
最後依然還是呼叫activity.viewmodel = component.viewmodel
複製程式碼

和普通的dagger對比一下區別就在於:

Dagger-Android在剛開始的時候通過註解處理器分析@Component、@Module、@ContributesAndroidInjector 等等註解,幫我們在App啟動的時候建立了一個全域性的單例map,並新增相關的對映。

可以在生成的DaggerAppComponet.kt檔案中找到

private Map<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>
    getMapOfClassOfAndProviderOfFactoryOf() {
  return Collections
      .<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>
          singletonMap(PaoActivity.class, (Provider) paoActivitySubcomponentBuilderProvider);
}
複製程式碼

當注入的時候就間接的通過這個map找到對應activity需要的Component,完成注入。

接下來我們就具體來看看 activityInjector.inject(activity)是如何完成注入的。

注入過程

先來斷點除錯一下,看看activityInjector是什麼?

activityInjector

可以看到activityInjector的真身是DispatchingAndroidInjector,實際呼叫的是DispatchingAndroidInjector的inject()方法,接著看看inject()方法

inject

呼叫的是maybeInject(instance),繼續深入

maybeInject

到這裡就很清晰了,正如前文所說的那樣,通過一個單例map根據key值為instance.class

找到相應的factoryProvider,通過get()方法獲取到AndroidInjector.Factory<T>物件

而它的真身是DaggerAppComponent的內部類PaoActivitySubcomponentBuilder

private final class PaoActivitySubcomponentBuilder
    extends ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.Builder {
    //....
    }
複製程式碼

PaoActivitySubcomponentBuilder 繼承了ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.Builder,再來看看ActivityModule_ContributePaoActivity

@Module(subcomponents = ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.class)
public abstract class ActivityModule_ContributePaoActivity {
  private ActivityModule_ContributePaoActivity() {}

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

  @Subcomponent
  public interface PaoActivitySubcomponent extends AndroidInjector<PaoActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<PaoActivity> {}
  }
}
複製程式碼

這些程式碼是新增對映的關鍵程式碼了,Dagger-Android通過處理@ContributesAndroidInjector自動生成的,按照Dagger-Android文件上的說法是可以自己編寫,@ContributesAndroidInjector只是簡化了這步操作。

可以看到@Binds、@IntoMap、@ActivityKey 這幾個註解,就如前文所說的那樣將其儲存到單例的map物件之中,key值便是PaoActivity.class

這些都是題外話,再回頭繼續斷點除錯,可以看到factory.create(instance)

create

呼叫了seedInstance()方法,這是一個抽象方法,由前文的PaoActivitySubcomponentBuilder實現。

private final class PaoActivitySubcomponentBuilder
      extends ActivityModule_ContributePaoActivity.PaoActivitySubcomponent.Builder {
    private PaoActivity seedInstance;
@Override
public void seedInstance(PaoActivity arg0) {
  this.seedInstance = Preconditions.checkNotNull(arg0);
}
}
複製程式碼

就是一個簡單的賦值操作。然後返回型別為AndroidInjector<T>injector,斷點可以看到它的真身是PaoActivitySubcomponentImpl

maybeInject

繼續往下看,來到 injector.inject(instance);

到了這一步,就跟以前的Dagger沒任何區別了。

 private final class PaoActivitySubcomponentImpl
      implements ActivityModule_ContributePaoActivity.PaoActivitySubcomponent {
    private PaoActivitySubcomponentImpl(PaoActivitySubcomponentBuilder builder) {}

    private PaoRepo getPaoRepo() {
      return new PaoRepo(
          DaggerAppComponent.this.providePaoServiceProvider.get(),
          DaggerAppComponent.this.providePaoDaoProvider.get());
    }

    private PaoViewModel getPaoViewModel() {
      return new PaoViewModel(getPaoRepo());
    }

    @Override
    public void inject(PaoActivity arg0) {
      injectPaoActivity(arg0);
    }

    private PaoActivity injectPaoActivity(PaoActivity instance) {
      PaoActivity_MembersInjector.injectMViewModel(instance, getPaoViewModel());
      return instance;
    }
  }
}
複製程式碼

到此,經過分析Dagger-Android的注入過程,我們瞭解了他們的工作原理。fragment的注入是同樣的思路。 最後總結歸納一下:

  • 普通的賦值:viewmodel = ViewModel(Repo())
  • Dagger的注入: instance.viewmodel = component.viewmodel
  • Dagger-Android的注入:instance.viewmodel = map.get(instance.class).getComponent().viewmodel

就一個思路的轉變,google的大神真是有心了,搞這麼多事。 github:github.com/ditclear/MV…

寫在最後

Dagger-Android相比於普通的Dagger確實稍微繞了一些,多了一些設計模式和麵向介面,光看原始碼的話很容易繞暈,特別是在不懂得google大神們的思路的時候。

如果說Dagger的複雜度是5,那麼Dagger-Android的複雜程度就是7。

如果能明悟的話,邏輯也是很簡單的,然後多進行斷點除錯。就像解演算法題一樣,Dagger和Dagger-Android可以算是兩種思路吧。

如同寫在前面的話裡提到的,Dagger適合於那些初中級開發者的團隊,比較容易理解。Dagger-Android則更適合實力都較強的開發團隊。

相關文章