使用Kotlin構(gòu)建MVVM應(yīng)用程序—提高篇:Dagger-Android

寫在前面

提高篇的目的是想著寫一些較深入的知識闰集,包含一些源碼分析沽讹、架構(gòu)設(shè)想腳手架搭建的東西武鲁。
面向的人群是中高級的開發(fā)者或者愿意深入了解如何快速構(gòu)建Kotlin&&MVVM應(yīng)用的人群爽雄。

Dagger-Android

原本的打算是將其作為使用Kotlin構(gòu)建MVVM應(yīng)用程序系列的第五部分內(nèi)容。
但因?yàn)镈agger本身就有一定的入門門檻沐鼠,Dagger-Android的門檻就更高了挚瘟。對于初中級開發(fā)者而言,Dagger-Android太容易入門到放棄饲梭,對于這部分人群不是很適合乘盖,因此將其放入提高篇較為合適。
又因?yàn)镈agger-Android門檻較高憔涉,對于初學(xué)者來說不適用订框,就如同初出江湖的小菜鳥就想著去練習(xí)遠(yuǎn)高于自己根基的武功,對自己沒有太多好處兜叨,多積累基礎(chǔ)穿扳,設(shè)計模式才是正途,能力到了自然就悟了国旷。
又由于曲高和寡矛物,懂的人自然懂。為此跪但,我開通了使用Kotlin構(gòu)建MVVM應(yīng)用程序的小專欄履羞,提高篇的完整內(nèi)容會放在這里,愿意去了解的了解一哈屡久。
而這里也就大致的總結(jié)一下它的思路忆首。

為什么要有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. 只是復(fù)制粘貼上面的代碼會讓以后的重構(gòu)比較困難被环,還會讓一些開發(fā)者不知道Dagger到底是如何進(jìn)行注入的(然后就更不易理解了)
  2. 更重要的原因是:它要求注射類型(FrombulationActivity)知道其注射器糙及。 即使這是通過接口而不是具體類型完成的,它打破了依賴注入的核心原則:一個類不應(yīng)該知道如何實(shí)現(xiàn)依賴注入蛤售。

為了解決上述的問題妒潭,Dagger-Android應(yīng)運(yùn)而生揣钦。
這是它的起因冯凹,那么和傳統(tǒng)的Dagger區(qū)別又在哪里呢宇姚?

區(qū)別在哪?

就在解法不同罷了阱持,如果你認(rèn)為Dagger-Android是普通Dagger的延伸衷咽,要按照Dagger的邏輯去理解Dagger-Android镶骗,那么一開始就錯了躲雅。
假設(shè)把依賴注入看成是一道算法題或者數(shù)學(xué)的最后一道大題相赁。
Dagger和Dagger-Android都是正確的答案,只是兩個不同的解法而已裆赵。
相比之下跺嗽,Dagger-Android在程序啟動的時候通過處理注解页藻,初始化了一個全局的單例map

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

key值為activity/fragment的class桨嫁,而value為提供相應(yīng)Component的Provider對象璃吧。在開始的時候就為我們映射好了废境。
當(dāng)我們調(diào)用AndroidInjection.inject(this)時,也就是一個簡單的map.get(instance.class).getComponent().inject(this)
相比之下,多了一個從map去獲取component的中間過程毡咏,但是卻解決了上訴的第二個問題 :一個類不應(yīng)該知道如何實(shí)現(xiàn)依賴注入呕缭。

思路的差別就在這里修己,明白這一點(diǎn)睬愤,通過斷點(diǎn)一步步的源碼分析尤辱,Dagger-Android也沒有多么高不可攀啥刻。

快速開始

首先我們需要在app/build.gradle加入相應(yīng)的依賴

    //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兩個類荸镊。

  1. rebuild一下項(xiàng)目,然后新增PaoApp.kt同時實(shí)現(xiàn)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

}
  1. 最后在Activity的onCreate()方法之前調(diào)用 AndroidInjection.inject(this)進(jìn)行注入
class PaoActivity : RxAppCompatActivity() {
    @Inject
    lateinit var mViewModel : PaoViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        //////di
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
    }
}

到此张惹,一個簡單的依賴注入就好了宛逗。
當(dāng)然簡單是絕不簡單的雷激。這些是在寫什么?完全云里霧里进栽。剛從Dagger轉(zhuǎn)換成Dagger-Android的直接就勸退了快毛,還不如直接使用Dagger2的好唠帝。

確實(shí)襟衰,對于日常功能迭代的開發(fā)團(tuán)隊(duì)來說瀑晒,普通的dagger更易理解徘意,所以Dagger-Android也算是一個可選項(xiàng)椎咧,可以作為一個提高勤讽,而且google的很多示例里dagger的用法都是Dagger-Android,所以還是有必要懂它的原理向臀。

原理剖析

第四部分中券膀,我們也了解了普通的Dagger是如何進(jìn)行依賴注入的三娩,這里我們再來回顧一次

由AppModule提供所需的依賴
由AppCompent提供注入的途徑
由@Inject標(biāo)識需要注入的對象
調(diào)用
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/frament的class已卷,value為提供相應(yīng)Component的Provider對象侧蘸。

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

當(dāng)我們在Activity中調(diào)用 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());
    //進(jìn)行注入
    activityInjector.inject(activity);
  }

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

一個全局的單例map對象析桥,通過key值為activity.class即
map.get(activity.class) 找到activity與之對應(yīng)的Provider<AndroidInjector.Factory<? extends T>>
泡仗。猜憎。截亦。
柬讨。踩官。颖系。
然后經(jīng)過層層的深入
找到對應(yīng)的component
最后依然還是調(diào)用activity.viewmodel = component.viewmodel

和普通的dagger對比一下區(qū)別就在于:

Dagger-Android在剛開始的時候通過注解處理器分析@Component、@Module信粮、@ContributesAndroidInjector 等等注解强缘,幫我們在App啟動的時候建立了一個全局的單例map欺旧,并添加相關(guān)的映射辞友。

可以在生成的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);
}

當(dāng)注入的時候就間接的通過這個map找到對應(yīng)activity需要的Component,完成注入鲫尊。

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

注入過程

先來斷點(diǎn)調(diào)試一下豪嚎,看看activityInjector是什么侈询?

activityInjector

)

可以看到activityInjector的真身是DispatchingAndroidInjector扔字,實(shí)際調(diào)用的是DispatchingAndroidInjector的inject()方法囊嘉,接著看看inject()方法

inject

調(diào)用的是maybeInject(instance),繼續(xù)深入

maybeInject

到這里就很清晰了革为,正如前文所說的那樣扭粱,通過一個單例map根據(jù)key值為instance.class

找到相應(yīng)的factoryProvider,通過get()方法獲取到AndroidInjector.Factory<T>對象

而它的真身是DaggerAppComponent的內(nèi)部類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> {}
  }
}

這些代碼是添加映射的關(guān)鍵代碼了,Dagger-Android通過處理@ContributesAndroidInjector自動生成的,按照Dagger-Android文檔上的說法是可以自己編寫虐块,@ContributesAndroidInjector只是簡化了這步操作嘉蕾。

可以看到@Binds儡率、@IntoMap、@ActivityKey 這幾個注解,就如前文所說的那樣將其保存到單例的map對象之中浪汪,key值便是PaoActivity.class凯旋。

這些都是題外話钠署,再回頭繼續(xù)斷點(diǎn)調(diào)試,可以看到factory.create(instance)

create

調(diào)用了seedInstance()方法傍菇,這是一個抽象方法淮悼,由前文的PaoActivitySubcomponentBuilder實(shí)現(xiàn)。

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肪凛,斷點(diǎn)可以看到它的真身是PaoActivitySubcomponentImpl爹袁。

maybeInject

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

到了這一步,就跟以前的Dagger沒任何區(qū)別了浦妄。

 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;
    }
  }
}

到此,經(jīng)過分析Dagger-Android的注入過程耳胎,我們了解了他們的工作原理。
最后總結(jié)歸納一下:

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

就一個思路的轉(zhuǎn)變,google的大神真是有心了搔体,搞這么多事劝术。

寫在最后

Dagger-Android相比于普通的Dagger確實(shí)稍微繞了一些绳泉,多了一些設(shè)計模式和面向接口,光看源碼的話很容易繞暈,特別是在不懂得google大神們的思路的時候月腋。

如果說Dagger的復(fù)雜度是5,那么Dagger-Android的復(fù)雜程度就是7。

如果能明悟的話所禀,邏輯也是很簡單的操禀,然后多進(jìn)行斷點(diǎn)調(diào)試。就像解算法題一樣,Dagger和Dagger-Android可以算是兩種思路吧溪猿。

如同寫在前面的話里提到的,Dagger適合于那些初中級開發(fā)者的團(tuán)隊(duì)纫塌,比較容易理解诊县。Dagger-Android則更適合實(shí)力都較強(qiáng)的開發(fā)團(tuán)隊(duì)。)

github:https://github.com/ditclear/MVVM-Android/tree/dagger-android

參考文檔:
Dagger-Android
告別Dagger2模板代碼:DaggerAndroid原理解析 (推薦)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市昔瞧,隨后出現(xiàn)的幾起案子瞧哟,更是在濱河造成了極大的恐慌枪向,老刑警劉巖深员,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異燎悍,居然都是意外死亡奏路,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炬灭,“玉大人醋粟,你說我怎么就攤上這事≈毓椋” “怎么了米愿?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鼻吮。 經(jīng)常有香客問我育苟,道長,這世上最難降的妖魔是什么椎木? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任违柏,我火速辦了婚禮,結(jié)果婚禮上香椎,老公的妹妹穿的比我還像新娘漱竖。我一直安慰自己,他們只是感情好畜伐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布馍惹。 她就那樣靜靜地躺著,像睡著了一般玛界。 火紅的嫁衣襯著肌膚如雪万矾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天慎框,我揣著相機(jī)與錄音良狈,去河邊找鬼。 笑死笨枯,一個胖子當(dāng)著我的面吹牛薪丁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猎醇,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼窥突,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了硫嘶?” 一聲冷哼從身側(cè)響起阻问,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沦疾,沒想到半個月后称近,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第队,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年刨秆,在試婚紗的時候發(fā)現(xiàn)自己被綠了凳谦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡衡未,死狀恐怖尸执,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缓醋,我是刑警寧澤如失,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站送粱,受9級特大地震影響褪贵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抗俄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一脆丁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧动雹,春花似錦槽卫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姊氓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喷好,已是汗流浹背翔横。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梗搅,地道東北人禾唁。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像无切,于是被迫代替她去往敵國和親荡短。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容