Dagger2在Android平臺上的新姿勢

前言

最近看Google新的框架sample時android-architecture-components發現了dagger2在Android平臺上新的寫法,很簡潔,值得學習。特意看了看dagger2的官方文檔Android部分,講得很詳細也很到位。因為是新姿勢,中文的學習資料還比較少,所以我決定翻譯一下官方文檔。以下是主要是對dagger2 Android部分官方文檔的翻譯,并增加了一些內容幫助理解。文檔其它部分的已經有人翻譯過了dagger2官方文檔中文。如果你還不了解dagger2,或者不太熟悉dagger2中的subcomponent和multibindings的話(以下內容需要熟悉這些內容才能看懂),可以先看一下dagger2官方文檔中文。dagger2的學習曲線還是很陡峭的,官方文檔永遠是最好的學習資料。

Android Architecture Components是2017年Google I/O 大會上新推出的應用框架,主要用于解決UI組件的生命周期和數據持久化問題,幫助我們輕易地處理配置變化(像屏幕旋轉)時數據的存儲問題。構建感知生命周期的Observer,防止內存泄漏。非常值得學習。
Architecture Components官方文檔
中文翻譯

我已經放棄使用dagger.android了,具體原因可以查看當定義Dagger2 Scope時,你在定義什么?

開始

相較于其它的依賴注入框架而言,Dagger2其中一個主要的優勢是,它不使用反射來生成其實現。這意味著它可以被用在Android應用上(意思就是不影響APP的性能)。然而,把Dagger2應用在Android平臺上,還是有一些點需要注意。

dagger.android

在Android平臺上使用Dagger的一個主要的不同是,很多類的實例化依賴于操作系統本身,像是Activity和Fragment,但是Dagger最理想的工作方式是它能夠構造所有需要注入的類的實例。所以,你必須在它們(Activity、Fragment等)的生命周期中進行成員的注入。很多類看上去跟下面類似:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //先寫如下代碼, 否則frombulator可能為null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ...其它代碼
  }
}

這么做有以下問題:

  1. 復制粘貼同樣的代碼使得以后想要重構變得困難。越來越多的這樣復制粘貼的代碼,開發者反而對這段代碼的作用了解的更少。
  2. 更加重要的是,它需要被注入類(FrombulationActivity)知道它的注入類。即使這是通過接口實現的,而不是具體的類。但是,這仍然破壞了依賴注入的的核心原則:一個類不應該對它是如何被注入的有任何的了解。

補充說明:以上這樣的代碼其實在Android平臺上并不常見,更常見的是類似如下的代碼:

@Singleton
@Component(modules= AppModule.class)
public interface AppComponent {
    Application getApplication();
    //其它需要暴露出來的類,供dependencies使用
}

@ActivityScope
@Component(dependencies = AppComponent::class, modules= ActivityModule.class)
interface ActivityComponent {
    void inject(YourActivity activity)
}

public class YourActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    DaggerActivityComponent.builder()
        .appComponent(YourApp.getAppComponent())
        .activityModule(new ActivityModule(this))
        .build()
        .inject(this);
    // ...其它代碼
  }
}

也就是通過component之間依賴(dependencies)的方式來管理不同component之間的關系;而Google文檔上的例子是通過子組件(subcomponent)的方式來管理component間的關系,并且如果想使用本文所介紹的新的姿勢,就必須使用子組件的方式,拋棄原來依賴的方式。就管理component之間的關系而言,這兩種方式都是可以的,我也一直都是使用依賴的方式來構建不同的component,這么做相較于子組件的方式而言,最大的優勢就是簡單,直接依賴另外一個component,被依賴的component暴露出相應的類即可。而子組件的方式寫起來比較麻煩。關鍵是還得額外學習subcomponent的是怎么回事,光整明白component就夠累的了,還要啥subcomponent。 But,解鎖了這篇文章介紹的dagger2和Android結合的新姿勢,老司機就需要考慮這么一個問題了,需不需要換個姿勢呢?!我個人覺得,還是有必要的,原因如下:

  1. 符合依賴注入的的核心原則:一個類不應該對它是如何被注入的有任何的了解。這個核心原則體現在我們的代碼上就是,在Activity、Fragment等類需要注入對象時,可以直接使用@Inject注解一個對象即可,不需要生成component(subcomponent)了,使用更加的方便。
  2. 我們都是有追求的老司機,借此學習一下subcomponent的使用有益無害。
  3. 代碼量更少。沒有最懶的程序猿,只有更懶得程序猿。

Activity的注入

  1. 在你的Application Component中加入AndroidInjectionModule模塊,以提供所有基本類型的綁定。
@Singleton
@Component(modules= {
AndroidInjectionModule.class, 
...
})
public interface AppComponent {
    ...
}

AndroidInjectionModule并沒有什么特別的,只是一個普通的module,該module提供了5個Map,Map的key是Android四大組件和Fragment的class對象,Map的value是相應的注入器的工廠方法。

@Module
public abstract class AndroidInjectionModule {  
    @Multibinds
    abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
      activityInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      fragmentInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends Service>, AndroidInjector.Factory<? extends Service>>
      serviceInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends BroadcastReceiver>, AndroidInjector.Factory<? extends BroadcastReceiver>>
      broadcastReceiverInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends ContentProvider>, AndroidInjector.Factory<? extends ContentProvider>>
      contentProviderInjectorFactories(); 
    
    private AndroidInjectionModule() {}
}
  1. 聲明你的subcomponent并且實現接口AndroidInjector<YourActivity>,該subcomponent需要有一個被@Subcomponent.Builder注解的并擴展自AndroidInjector.Builder<YourActivity>的構造器:
@Subcomponent(modules = ...)
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
}
  1. 聲明過subcomponent之后,把它通過如下方式加入到主component體系中:定義一個提供該subcomponent builder的module,并且把該module加入到你的AppComponent中。
@Module(subcomponents = YourActivitySubcomponent.class)
abstract class YourActivityModule {
  @Binds
  @IntoMap
  @ActivityKey(YourActivity.class)
  abstract AndroidInjector.Factory<? extends Activity>
      bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
}

@Singleton
@Component(modules = {
AndroidInjectionModule.class,
YourActivityModule.class,...
})
interface AppComponent {}

注意:如果你的subcomponent和它的builder除了第2步中提及的方法或者超類沒有其它的內容,你可以用 @ContributesAndroidInjector生成2、3步中的一切。現在不需要步驟2和3,你只需聲明一個abstract module,返回你所需的activity(用 @ContributesAndroidInjector注解),可以聲明subcomponent需要的其它的module。如果這個subcomponent需要scope注解,也可以聲明:

@Module
public abstract class ActivityBulidersModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = {/*subcomponent需要的module*/})
    abstract YourActivity contributeYourActivity();
}

@Singleton
@Component(modules = {
AndroidInjectionModule.class,
ActivityBulidersModule.class,...
})
interface AppComponent {}

@ContributesAndroidInjector注解是dagger-android-2.11中提供的,它會生成如下代碼:

@Module(subcomponents = ActivityBulidersModule_YourActivityInjector.YourActivitySubcomponent.class)
public abstract class ActivityBulidersModule_YourActivityInjector {
  private ActivityBulidersModule_YourActivityInjector() {}

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

    @ActivityScope
    @Subcomponent(modules = {/*subcomponent需要的module*/})
    public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
        @Subcomponent.Builder
        abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
    }
}

是不是跟我們自己寫的一毛一樣,@ContributesAndroidInjector只是幫我們自動生成了一些代碼,并沒有什么特別的,但前提是第2步沒有其它方法或者超類型

  1. 下一步,讓你的Application實現HasActivityInjector并且@Inject DispatchingAndroidInjector<Activity>而后從方法activityInjector()(接口HasActivityInjector中的方法)返回:
public class YourApplication extends Application implements HasActivityInjector {
  @Inject 
  DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

  @Override
  public void onCreate() {
    super.onCreate();
    DaggerAppComponent.create()
        .inject(this);
  }

  @Override
  public AndroidInjector<Activity> activityInjector() {
    return dispatchingActivityInjector;
  }
}
  1. 最后,在 Activity.onCreate() 方法中在super.onCreate()之前調用AndroidInjection.inject(this)
public class YourActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
  }
}
  1. 恭喜你!完成了!

如何工作的?

AndroidInjection.inject() 從Application中獲取了一個 DispatchingAndroidInjector<Activity>,并把activity實例傳入方法 inject(Activity)中。 DispatchingAndroidInjector 根據activity的class來查找 AndroidInjector.Factory(即 YourActivitySubcomponent.Builder),創建 AndroidInjector (即YourActivitySubcomponent), 然后把你的activity實例傳入方法 inject(YourActivity)中。

更多關于實現的原理請查看我的另外一篇文章Dagger2在Android平臺上的新魔法

Fragment注入

注入Fragment就跟注入Activity一樣。以相同的方式定義subcomponent,把Activity類型替換為Fragment,@ActivityKey替換為@FragmentKey,HasActivityInjector替換為HasFragmentInjector。
和Activity在onCreate()中注入不同,Fragment的注入在方法onAttach()中。

和為Activity添加module不同,在Fragment中你可以選擇在哪添加module。你可以把你的Fragment的subcomponent聲明為另一個Fragment component的子component,或者是Activity component的子component,或者是Application component的子component——這取決于你想在哪注入你的subcomponent。在決定了Fragment subcomponent是哪個component的子component之后,讓相應的類型實現接口HasFragmentInjector。

原文說的很繞,其實上面一段話的核心意思就是,Fragment的subcomponent可以是Fragment、Activity、Application component(或subcomponent)的subcomponent,只要其對應的類型(Fragment、Activity、Application)實現了HasFragmentInjector接口即可。作為對比,Activity則只能是Application component的subcomponent,所以只能是Application實現HasActivityInjector接口。

例如,你的Fragment subcomponent是YourActivitySubcomponent的子component。你的代碼類似于此:

public class YourActivity extends Activity
    implements HasFragmentInjector {
  @Inject 
  DispatchingAndroidInjector<Fragment> fragmentInjector;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    // ...
  }

  @Override
  public AndroidInjector<Fragment> fragmentInjector() {
    return fragmentInjector;
  }
}

public class YourFragment extends Fragment {
  @Inject SomeDependency someDep;

  @Override
  public void onAttach(Activity activity) {
    AndroidInjection.inject(this);
    super.onAttach(activity);
    // ...
  }
}

@Subcomponent(modules = ...)
public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourFragment> {}
}

@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
  @Binds
  @IntoMap
  @FragmentKey(YourFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment>
      bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
}

@Subcomponent(modules = { YourFragmentModule.class, ... }
public interface YourActivityOrYourApplicationComponent { ... }

同樣我們可以簡寫:

@Module
public abstract class FragmentBuildersModule {
    @ContributesAndroidInjector
    abstract YourFragment contributeYourFragment();
}

Support libraries

對于使用Android support library的用戶,有dagger.android.support提供了支持。注意用支持庫中的Fragment,應該綁定AndroidInjector.Factory<? extends android.support.v4.app.Fragment>,但是仍應該實現AndroidInjector.Factory<? extends Activity> 而不是 <? extends AppCompatActivity> (或者 FragmentActivity)。dagger.android.support中有AndroidSupportInjectionModule,提供了對android.support.v4.app.Fragment的支持:

@Module(includes = AndroidInjectionModule.class)
public abstract class AndroidSupportInjectionModule {
  @Multibinds
  abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      supportFragmentInjectorFactories();

  private AndroidSupportInjectionModule() {}
}

如何獲取

在你的build.gradle中增加

dependencies {
  compile 'com.google.dagger:dagger-android:2.x'
  compile 'com.google.dagger:dagger-android-support:2.x' // 如果你使用support libraries
  annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
}

參考:
Dagger&Android

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容