寫在前面
本篇是對於使用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
}
}
複製程式碼
這會帶來一些問題:
- 只是複製貼上上面的程式碼會讓以後的重構比較困難,還會讓一些開發者不知道Dagger到底是如何進行注入的(然後就更不易理解了)
- 更重要的原因是:它要求注射型別(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為例。
- 新增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
的真身是DispatchingAndroidInjector
,實際呼叫的是DispatchingAndroidInjector
的inject()方法,接著看看inject()
方法
呼叫的是maybeInject(instance)
,繼續深入
到這裡就很清晰了,正如前文所說的那樣,通過一個單例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)
。
呼叫了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
。
繼續往下看,來到 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則更適合實力都較強的開發團隊。