Dagger2 的初步了解和使用

Dagger2?

Dagger 是 Java 平臺的依賴注入庫。在 J2EE 開發上流行甚廣的 Spring 就是一個依賴注入庫。此外還有 Google 的 Guice 和 Square 的 Dagger1。但它們都是是通過在運行時讀取注解來實現依賴注入的,依賴的生成和注入需要依靠 Java 的反射機制,這對于對性能非常敏感的 Android 來說是一個硬傷。

Dagger 同樣使用注解來實現依賴注入,但它利用 APT(Annotation Process Tool) 在編譯時生成輔助類,這些類繼承特定父類或實現特定接口,程序在運行時 Dagger 加載這些輔助類,調用相應接口完成依賴生成和注入。Dagger 對于程序的性能影響非常小,因此更加適用于 Android 應用的開發。

開始之前

在了解 Dagger2 之前,請務必先通曉兩個概念:

  1. 依賴注入(Dependency Injection : DI):面向對象編程的一種模式,最大的作用是解耦。
  2. Java 注解(Annotation):了解到哪些是 Java 原生支持的注解。

Dagger2 帶給我們的注解

下文嘗試盡量拋開特定的場景(比如 MVP 模式),以更具有普適性的代碼,從骨架開始,慢慢去豐滿血肉,更全面的去理解這里這些注解的使用。

@Inject

假設 MainActivity 依賴一個 Tester 的類,代碼應該如下:

public class Tester {
    @Inject
    public Tester() {
    }
}
public class MainActivity extends Activity {
    @Inject 
    Tester tester;
}

@Inject 有兩個作用:

  1. 標記 Tester 的構造方法,通知 Dagger2 在需要該類實例時可以通過該構造函數 new 出相關實例從而提供依賴。提供依賴的方式還有 @Provide ,下面細說。
  2. 標記 MainActivity 的 Tester 變量,通知 Dagger2 該變量實例需要由它來提供,也就是上述的需要 Dagger2 去 new 出 Tester 的實例 tester 。

我們另外還需要一個中間件去連接依賴提供方依賴使用方,這就是 @Component ,詳細的內容在下面介紹,先看下這個例子中的 TestComponent:

@Component
public interface TestComponent {
    void inject(CActivity cActivity);
}

在 MainActivity 的 onCreate() 加入:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    DaggerTestComponent.builder().build().inject(this);
    ...
}

DaggerTestComponent 是 Dagger2 幫我們生成的類,命名規則就是在我們定義的接口名稱前加上Dagger,實現了我們之前定義的 TestComponent 接口。執行完這一行代碼,tester 就已經不是 null 了。

@Provides

@Inject 標記構造方法來生成依賴對象的方式有它的局限性,如:

  1. 接口(Interface),沒有構造方法,自然無處標記。
  2. 第三方庫提供的類,不適合去直接修改源碼,標記構造方法。

對于上述的問題,就需要 @Provide 來提供依賴:

@Module
public class TestModule {
    @Provides
    Tester provideTester() {
        return new Tester();
    }
}

需要注意的是:

  1. @Provides 只能用于標記非構造方法,并且該方法必須在 @Moudle 內部。
  2. @Provides 修飾的方法的方法名建議以 provide 開頭。
@Component(modules = TestModule.class)
public interface TestComponent {
    void inject(CActivity cActivity);
}

這里跟之前使用 @Inject 提供依賴時的 Component 不同,標識了提供依賴的 TestModule。

public class MainActivity extends Activity {
    @Inject 
    Tester tester;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerTestComponent.builder().testModule(new TestModule()).build().inject(this);
    }
}

DaggerTestComponent 也同樣多了 testModule() 方法,參數就是我們自己定義的 TestModule。

@Module

@Module 一般用來標記類,該注解告知 Dagger2 可以到該類中尋找需要的依賴。上述的 @Provides 則標記提供依賴實例的方法。兩者都是一起使用的。

@Component

Component一般用來標注接口,如上文所說,作用在于作為依賴提供方依賴使用方溝通的橋梁。

更重要的一點在于:Component 可以組合不同的 Module 和 Component。

@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {}

多個 Module 和 Component 的情況:

@Component(
dependencies = {Component1.class,Component2.class,...}, 
modules = {Module1.class,Module1.class,...})
public interface ActivityComponent {}

敲黑板,劃重點:

  • Component 和它依賴的其他 Component 的 @Scope 作用域(@Scope下文會詳細介紹)不能相同。

  • 如果在依賴使用方需要依賴的對象并不是由當前直接的 Component 的 Module 提供的,而是由其所依賴的其他 Component 的 Module 提供的。那么就在被依賴的 Component 中就需要提供一個返回值為這個被依賴的 Compnent 的 Module 提供的依賴對象的方法,方法名可以隨意。(賊?繞,血崩!)

舉個栗子:

這里有一個 AModule 和 AComponent:

@Module
public class AModule {
    @Provides
    Tester provideTester() {
        return new Tester();
    }
}
@Component(modules = AModule.class)
public interface AComponent {}

還有一個依賴于 AComponent 的 BComponent:

@Component(dependencies = AComponent.class)
public interface BComponent {
    void inject(MainActivity mainActivity);
}

在 MainActivity 中就依賴 Tester 對象:

public class MainActivity extends Activity {
    @Inject 
    Tester tester;
}

這么寫的話,編譯器就不干了...我們需要在被依賴的 ACompnent 中添加返回值為 Tester 的方法,如下:

@Component(modules = AModule.class)
public interface AComponent {
    Tester getTester();
}

栗子舉完了,但是這里我仍有個疑惑,為何這里這么不智能?需要顯示去提供依賴?明明 @Subcomponent (下文詳述)就可以很智能的去父 Component 中查找缺失的依賴...

@Subcomponent

@Subcomponent 其功能效果類似 component 的 dependencies。但是使用 @Subcomponent 不需要在父 component 中顯式添加子 component 需要用到的對象,只需要添加返回子 Component 的方法即可,子 Component 能自動在父 Component 中查找缺失的依賴。

// 父Component
@PerApp
@Component(modules = AppModule.class)
public AppComponent{
    ActivityComponent getActivityComponent();  // 1.只需要在父Component添加返回子Component的方法即可
}

// 子Component
@PerAcitivity   // 2.注意子 Component 的 Scope 范圍小于父 Component 
@Subcomponent(modules = ActivityModule.class)   // 3.使用 @Subcomponent 注解
public ActivityComponent{
    void inject(MainActivity activity); 
}

public class App extends Application {

    AppComponent mAppComponent;
    
    @Inject
    ToastUtil toastUtil;
    
    @Override
    public void onCreate() {
        super.onCreate();

        mAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
    }

    public AppComponent getAppComponent(){
        return mAppComponent;
    }
}

public class SomeActivity extends Activity{
    public void onCreate(Bundle savedInstanceState){
        ...
        App.getAppComponent().getActivityComponent().inject(this);//4.調用getActivityComponent方法創建出子Component
    }    
}

@Subcomponent 和 Component 在使用最大的差異就在于:
當我們使用的 @Subcomponent,父 Component 可以完全不暴露自己,而只把子 Component 傳遞給其使用者。

@Qualifier

這段內容引自:Dagger 源碼解析

如果有兩類程序員,他們的能力值 power 分別是 5 和 1000,應該怎樣讓 Dagger 對他們做出區分呢?使用 @Qualifier 注解即可。

(1). 創建一個 @Qualifier 注解,用于區分兩類程序員:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Level {
  String value() default "";
}

(2). 為這兩類程序員分別設置 @Provides 函數,并使用 @Qualifier 注解對他們做出不同的標記:

@Provides @Level("low") Coder provideLowLevelCoder() {
    Coder coder = new Coder();
    coder.setName("戰五渣");
    coder.setPower(5);
    return coder;
}

@Provides @Level("high") Coder provideHighLevelCoder() {
    Coder coder = new Coder();
    coder.setName("大神");
    coder.setPower(1000);
    return coder;
}

(3). 在聲明 @Inject 對象的時候,加上對應的 @Qualifier 注解:

@Inject @Level("low") Coder lowLevelCoder;
@Inject @Level("high") Coder highLevelCoder;

此外,還有一個默認實現的注解 @Named,使用方法同上,源碼如下:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

@Scope

Dagger2 可以通過 @Scope 自定義注解限定注解作用域。
我們直接看 Dagger2 自帶的,通過 @Scope 實現的 @Singleton 注解,源碼如下:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton{}

用法如下:

@Singleton // @Inject 提供對象的單例模式
public class Tester {
    @Inject
    public Tester() {}
}
@Provides
@Singleton // @Provides 提供對象的單例模式
Tester provideTester() {
    return new Tester();
}
@Singleton // 標明該 Component 中有 Module 使用了 @Singleton
@Component(modules = TestModule.class)
public interface ActivityComponent {
    void inject();
}

如果要使用我們自己的注解,比如在 MVP 中非常常見的 @PreActivity,將上面的 @Singleton 替換成 @PreActivity 即可,使用上無區別:

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PreActivity {}

如何理解 @Scope

參看:Android:dagger2讓你愛不釋手-重點概念講解、融合篇

這里我們使用一個最簡單的 @Singleton 注解提供一個 AppComponent。編譯后,Dagger2 自動幫我們生成如下代碼:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerAppComponent implements AppComponent {
  private Provider<Context> provideContextProvider;
  private MembersInjector<App> appMembersInjector;

  private DaggerAppComponent(Builder builder) {  
    assert builder != null; // 判斷了只有第一次實例化這個Component時才會去執行下面的代碼
    initialize(builder);
  }

  public static Builder builder() {  
    return new Builder();
  }

  private void initialize(final Builder builder) {  
    this.provideContextProvider = ScopedProvider.create(AppModule_ProvideContextFactory.create(builder.appModule));
    this.appMembersInjector = App_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideToastUtilProvider);
  }

  @Override
  public Context context() {  
    return provideContextProvider.get();
  }

  @Override
  public void inject(App app) {  
    appMembersInjector.injectMembers(app);
  }

  public static final class Builder {
    private AppModule appModule;
  
    private Builder() {  
    }
  
    public AppComponent build() {  
      if (appModule == null) {
        throw new IllegalStateException("appModule must be set");
      }
      return new DaggerAppComponent(this);
    }
  
    public Builder appModule(AppModule appModule) {  
      if (appModule == null) {
        throw new NullPointerException("appModule");
      }
      this.appModule = appModule;
      return this;
    }
  }
}

傳遞進來的 appModule 首先交由 AppModule_ProvideContextFactory:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class AppModule_ProvideContextFactory implements Factory<Context> {
  private final AppModule module;

  public AppModule_ProvideContextFactory(AppModule module) {  
    assert module != null;
    this.module = module;
  }

  @Override
  public Context get() {  
    Context provided = module.provideContext(); // 這就是我們在 AppModule 中定義的方法,去提供 Context 對象的實例。
    if (provided == null) {
      throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
    }
    return provided;
  }

  public static Factory<Context> create(AppModule module) {  
    return new AppModule_ProvideContextFactory(module);
  }
}

這個類的功能其實就是維護了 Context 對象,其他類在想使用時就可以從這里拿。
繼續看之前,AppModule_ProvideContextFactory 通過工廠模式創建了自己的實例后就把自己傳遞給了 ScopedProvider:

public final class ScopedProvider<T> implements Provider<T> {
  private static final Object UNINITIALIZED = new Object();

  private final Factory<T> factory;
  private volatile Object instance = UNINITIALIZED;

  private ScopedProvider(Factory<T> factory) {
    assert factory != null;
    this.factory = factory;
  }

  @SuppressWarnings("unchecked") // cast only happens when result comes from the factory
  @Override
  public T get() {
    // double-check idiom from EJ2: Item 71
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          instance = result = factory.get();
        }
      }
    }
    return (T) result;
  }

  /** Returns a new scoped provider for the given factory. */
  public static <T> Provider<T> create(Factory<T> factory) {
    if (factory == null) {
      throw new NullPointerException();
    }
    return new ScopedProvider<T>(factory);
  }
}

對之前的 Context 對象,做一次雙重校驗鎖,目的是為了實現對象的線程安全。
在用到 Context 對象的地方,都是類似于 DaggerAppComponent 中的 provideContextProvider.get() 方法去獲取實例。

總結來說,@Scope本身并不控制對象的生命周期,其生命周期其實還是看生成的 Component 對象的生命周期。

后話

其實知曉 Dagger2 注解的使用,大致了解 Dagger2 的原理其實并不是難點或者說重點。
在實際工程中如何靈活去使用它,如何根據業務的需要切分 Module 和 Component 才是我們在之后需要時間不斷去打磨。

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

推薦閱讀更多精彩內容