翻譯Dagger 2在安卓中使用

官方文檔鏈接:https://google.github.io/dagger/android.html

1.前言


Dagger 2相比其它依賴注入框架的主要優勢之一,嚴格的代碼生成(沒有反射),意味著可以在安卓應用中使用。但是,在安卓中使用,仍然有些事項需注意。

2.設計思想


雖然安卓是用Java編寫的,但兩者就風格而言完全不一樣,因為得考慮移動平臺獨特的表現方式。許多常見的用于Java代碼的模式無法用于安卓代碼,甚至許多《Effective Java》書中的建議在安卓看來都是不恰當的。

為了達到既符合語言習慣又輕量的代碼目標,Dagger依靠混淆來接著處理編譯后的字節碼。Dagger通過使用不同的工具鏈產生在兩種環境下都有效執行的字節碼,使代碼在服務器和安卓上不管是看還是感覺都很自然。此外,Dagger有個明確的目標,確保生成的Java代碼總能支持混淆優化(假設在安卓上使用混淆)。

當然,不是所有問題都能用那樣的方式解決,但這是能提供安卓特有兼容的主要機制。

3.基本使用


使用Dagger編寫安卓應用的主要困難之一,許多安卓框架類由系統自身初始化,像Activity和Fragment。但只有在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.android包中的類提供了簡化這種模式的方法):

  • 樣板代碼使以后重構變得困難。隨著越來越多的開發者復制粘貼這段代碼,越來越少的人知道它實際作用。
  • 更嚴重的是,它需要請求注入的對象(FrombulationActivity)知道注入器。即使這是通過接口代替實體類,但仍打破了依賴注入的核心原則:一個類不應該知道任何關于如何被注入的。

4.給Activity注入對象


  • 為Application級的Component添加AndroidInjectionModule來確保Dagger必要的所有基礎類型依賴是有效的。

  • 從創建@Subcomponent注解的接口繼承AndroidInjector<YourActivity>@Subcomponent.Builder注解的內部類繼承AndroidInjector.Builder<YourActivity>

    @Subcomponent(modules = ...)
    public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
      @Subcomponent.Builder
      public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
    }
    
  • 定義Subcomponent之后,通過定義個Module依賴Subcomponent及其Builder,并將它添加到Application級的Component中,來添加Subcomponent到Component層次結構中:

    @Module(subcomponents = YourActivitySubcomponent.class)
    abstract class YourActivityModule {
      @Binds
      @IntoMap
      @ActivityKey(YourActivity.class)
      abstract AndroidInjector.Factory<? extends Activity>
          bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
    }
    
    @Component(modules = {..., YourActivityModule.class})
    interface YourApplicationComponent {}
    

    如果Subcomponent及其Builder沒有其它的方法,可以使用@ContributesAndroidInject注解幫助生成樣板代碼。在Module中添加抽象方法返回需要的Activity,并用@ContributesAndroidInject注解,且指定Subcomponent依賴的模塊,來取代上述的2、3步。如果Subcomponent需要作用域,可以給方法使用作用域注解。

    @ActivityScope
    @ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })
    abstract YourActivity contributeYourActivityInjector();
    
  • 接著,使Application實現HasActivityInjector,且用@Inject注解一個DispatchingAndroidInjector<Activity>對象去讓activityInjector()方法返回:

    public class YourApplication extends Application implements HasActivityInjector {
      @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
    
      @Override
      public void onCreate() {
        super.onCreate();
        DaggerYourApplicationComponent.create()
            .inject(this);
      }
    
      @Override
      public AndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
      }
    }
    
  • 最后,在需要的Activity的onCreate()方法中,于super.onCreate()方法之前調用AndroidInjection.inject(this)方法:

    public class YourActivity extends Activity {
      public void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
      }
    }
    

工作原理?(此處可以參考另一篇博客,有更詳細的分析)

通過AndroidInjection.inject()方法從Application中獲取DispatchingAndroidInjector<Activity>對象,同時將Activity傳入inject(Activity)方法中。DispatchingAndroidInjector<Activity>對象通過Activity類查詢AndroidInjector.Factory(就是YourActivitySubcomponent.Builder),創建ActivityInjector(就是YourActivitySubcomponent),并將Activity傳入它的inject(Activity)方法中。

5.給Fragment注入對象


給Fragment注入和給Activity注入一樣簡單。以同樣的方式定義Subcomponent,將參數中的Activity替換為Fragment,把@ActivityKey替換為@FragmentKey,以及HasActivityInjector替換為HasFragmentInjector。注入過程發生的地方,也由Activity的onCreate()方法替換為Fragment的onAttach()方法。

不像為Activity綁定Module,可以選擇在哪為Fragment綁定Module。可以讓某Fragment級的Component作為另一個Fragment級(也可以是Activity級或者Application級)Component的Subcomponent,這都取決于Fragment需要的其它依賴關系。當決定Fragment級Component的位置之后,使相關Fragment實現HasFragmentInjector。舉個例子,假設Fragment需要成為YourActivitySubcomponent的依賴,代碼將如下所示:

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 { ... }

6.基本類型


因為DispatchingAndroidInjector在運行時通過類查詢相關的AndroidInjector.Factory,可以定義基本類型實現HasActivityInjector/HasFragmentInjector/etc,同時調用AndroidInjection.inject()方法,那它們的子類只需要添加相應的@Subcomponent注解的依賴。如果沒有復雜的類層級結構,可使用Dagger提供的基類,例如DaggerActivity和DaggerFragment。Dagger還提供了DaggerApplication,只要繼承它且重寫applicationInjector()方法,返回應該注入Application中Component?;绢愋瓦€有下面這些:

  • DaggerService和DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

只有當BroadcastReceiver在清單文件中被注冊,DaggerBroadcastReceiver才可以使用。若BroadcastReceiver在代碼中注冊,建議使用構造函數注入。

7.支持的庫


對于使用安卓支持庫的用戶,有相似的類型存在于dagger.android.support包中。若使用支持庫中的Fragment,依賴關系為AndroidInjector.Factory<? extends android.support.v4.app.Fragment>;若使用支持庫中的AppCompatActivity,應該繼續實現AndroidInjector.Factory<? extends Activity>而不是<? extends AppCompatActivity>(或者FragmentActivity)。

8.Dagger的安卓庫


build.gradle中添加以下內容:

dependencies {
  compile 'com.google.dagger:dagger-android:2.x'
  compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
  annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
}

9.什么時候注入


盡可能優先使用構造函數注入,因為javac將確保屬性在被設置前沒有被引用,以避免空指針異常,所以越早注入越好。為了這個原因,DaggerActivity在onCreate()方法中,于super.onCreate()方法前調用AndroidInjection.inject();DaggerFragment在onAttach()方法中同樣如此,還能避免Fragment重連后對象不一致。

對于Activity而言,在super.onCreate()方法之前調用AndroidInjection.inject()方法是很重要的,因為當配置更改時,會調用super去連接之前Activity關聯的Fragment,并依次注入。為了保證Fragment成功注入,Activity必須已經注入。若使用ErrorProne工具,可在編譯時發現調用順序上的錯誤。

10.AndroidInjector.Factory作用域


AndroidInjector.Factory有意成為無狀態(無屬性)的接口,那樣就不需要管理被注入對象狀態。DispatchingAndroidInjector通過Provider(注解的方法)獲取AndroidInjector.Factory對象,那樣就不需要明確持有Factory實例。因為AndroidInjector.Builder的實現由Dagger生成,持有待被注入的Activity/Fragment/etc實例。若給提供注入對象的方法加上作用域,將會導致編譯時錯誤。如果也認同AndroidInjector.Factory不該持有被注入對象實例,需要使用@SuppressWarnings("dagger.android.ScopedInjectoryFactory")注解Module中的方法來忽略這個問題。

11.總結


Dagger 2在安卓中使用的關鍵點就是不讓被注入的類知道注入的細節。部分地方由于不理解,只能照著語義翻譯,若有人發現翻譯錯誤的,還望在評論中指出,謝了!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。