Dagger2 的初步了解和使用

dgcu7456 7年前發布 | 11K 次閱讀 Dagger 測試技術 Java開發

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 模式),以更具有普適性的代碼,從骨架開始,慢慢去豐滿血肉,更全面的去理解這里這些注解的使用。

<h3>@Inject</h3>

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

public class Tester {
    @Inject
    public Tester() {
    }
}
public class MainActivity extends Activity {
    @Inject 
    Tester tester;
}
<p>@Inject 有兩個作用:</p>
  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 了。

<h3>@Provides</h3> <p>@Inject 標記構造方法來生成依賴對象的方式有它的局限性,如:</p>
  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);
}

}</code></pre>

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

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

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 中查找缺失的依賴...

<h3>@Subcomponent</h3> <p>@Subcomponent 其功能效果類似 component 的 dependencies。但是使用 @Subcomponent 不需要在父 component 中顯式添加子 component 需要用到的對象,只需要添加返回子 Component 的方法即可,子 Component 能自動在父 Component 中查找缺失的依賴。</p>
// 父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 }
}</code></pre> <p>@Subcomponent 和 Component 在使用最大的差異就在于:</p>

當我們使用的 @Subcomponent,父 Component 可以完全不暴露自己,而只把子 Component 傳遞給其使用者。

<h3>@Qualifier</h3>

如果有兩類程序員,他們的能力值 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; }</code></pre>

(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 "";

}</code></pre> <h3>@Scope</h3>

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

這里我們使用一個最簡單的 @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;
}

} }</code></pre>

傳遞進來的 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); } }</code></pre>

這個類的功能其實就是維護了 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); } }</code></pre>

對之前的 Context 對象,做一次雙重校驗鎖,目的是為了實現對象的線程安全。

在用到 Context 對象的地方,都是類似于 DaggerAppComponent 中的 provideContextProvider.get() 方法去獲取實例。

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

后話

其實知曉 Dagger2 注解的使用,大致了解 Dagger2 的原理其實并不是難點或者說重點。

在實際工程中如何靈活去使用它,如何根據業務的需要切分 Module 和 Component 才是我們在之后需要時間不斷去打磨。

 

來自:https://segmentfault.com/a/1190000008677663

 

 本文由用戶 dgcu7456 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!