依賴注入框架Dagger2

什么的依賴注入

在軟件工程中,依賴注入是實現控制反轉的方式之一。百度百科中對于控制反轉的解釋如下:控制反轉(Inversion of Control,英文縮寫為IoC)把創建對象的權利交給框架。我們可以舉例來說明一下,在開發中我們經常有類組合的情況,例如Student類中引用了School類,用于描述學生的學校信息,那么這就是典型的組合,再具體點的例子:假設我們的網絡請求類RetrofitManager中需要有一個OkHttpClick對象。

public class RetrofitManager {

    OkHttpClient client;

    public RetrofitManager() {
        client = new OkHttpClient.Builder()
                .connectTimeout(1000L, TimeUnit.SECONDS).build();
    }
}

我們可以看到上面的類中直接在構造器中將client對象進行初始化,那么此時就產生了較高的耦合現象。這種在一個類中直接創建另一個類的對象的代碼,和硬編碼(hard-coded strings)以及硬編碼的數字(magic numbers)一樣,是一種導致耦合的壞味道,我們可以把這種壞味道稱為硬初始化(hard init),違背了單一職責原則與開閉原則,同時也會嚴重影響到單元測試與代碼的可讀性。

試想一下,領導現在要求你修改網絡請求的相關參數或者讓你添加固定的請求頭又或者要添加相關的網絡攔截器等等。這時候我們就必須對RetrofitManager中的client初始化進行修改。當然,這個例子還好,可能就只有這一個地方對吧。但是如果是其他情況,修改的地方有可能是成千上萬。因此,我們需要使用依賴注入來解決這個問題。

實現依賴注入的三種方式

這里會介紹幾種實現依賴注入的簡單方式,而復雜的依賴注入框架當然思想都是一樣的。就是避免在調用者中直接實例化(new)被調用者對象,而是通過其他方式進行引入。

1.構造器注入

直接通過構造器參數的形式將被調用者的對象傳入

public class RetrofitManager {

    OkHttpClient client;

    public RetrofitManager(OkHttpClient client) {
        this.client = client;
    }
}

2.setter注入

通過提供setter方式讓外界將對象注入

public class RetrofitManager {

    OkHttpClient client;

    public void setClient(OkHttpClient client) {
        this.client = client;
    }
}

2.接口注入

定義注入接口,讓調用者都實現改接口

public interface Inject {

    void inject(OkHttpClient client);
}

public class RetrofitManager implements Inject{

    OkHttpClient client;

    @Override
    public void inject(OkHttpClient client) {
        this.client = client;
    }
}

OK,簡單了解了依賴注入與耦合問題之后,我們再來看Dagger2

Dagger2是什么

Dagger2

Dagger2一個Android和java快速依賴注入框架,Dagger2是Dagger的升級版,目前由Google進行維護。Dagger2中杜絕了反射的使用,而是采用編譯時注解的方式在編譯時候實現代碼的自動生成。

注解的分類

Annotation(注解)是Java提供的一種對元程序中元素關聯信息和元數據(metadata)的途徑和方法。Annatation(注解)是一個接口,程序可以通過反射來獲取指定程序中元素的Annotation對象,然后通過該Annotation對象來獲取注解中的元數據信息。

通常自定義注解時我們會通過@Retention元注解(元注解是專門用來定義注解的注解)來標記該注解的保留周期:.SOURCE:在源文件中有效(即源文件保留)、CLASS:在class文件中有效(即class保留)、RUNTIME:在運行時有效(即運行時保留)。SOURCE,在編譯階段丟棄。這些注解在編譯結束之后就不再有任何意義,所以它們不會寫入字節碼。@Override, @SuppressWarnings都屬于這類注解。CLASS,在類加載的時候丟棄。在字節碼文件的處理中有用。注解默認使用這種方式。RUNTIME,始終不會丟棄,運行期也保留該注解,因此可以使用反射機制讀取該注解的信息。

扯一些題外話,早期的一些控件注入框架普遍采用RUNTIME(運行時)形式的注解通過反射機制在運行時動態完成控件的注入工作。甚至是一些依賴注入框架也采用這種實現形式。大家都知道,反射對性能是有影響的,所有現在流行的框架一般都是采用CLASS(編譯時)形式的注解配合javapoet或者javassist等字節碼操作工具實現編譯時修改代碼或者生成類文件的方式去實現相關功能。

Dagger2的引入

有兩種方式可以完成Dagger2的依賴,第一種方式是使用android-apt,第二種方式是使用annotationProcessor。前者是開發者開發的apt框架,后者是谷歌Android Gradle插件支持的apt方式。Dagger2最新文檔上可以看到Android端推薦使用的是第二種方式。具體細節可查看博客:http://blog.csdn.net/xx326664162/article/details/68490059

我們采用第二種方式對Dagger2進行依賴,直接在項目gradle中添加以下兩行

annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
compile 'com.squareup.okhttp3:okhttp:3.10.0'

Inject + Component

1.定義類ClassA,為它的無參構造器加上注解@Inject,表明該類支持快速依賴注入

public class ClassA {

    @Inject
    public ClassA() {
    }
}

2.定義接口MainActivityComponent,為該接口添加注解@Component,定義抽象方法,其他返回值為void,參數為需要注入對象的類(必須的需要注入對象的真實所在類,比如:MainActivity,而不能是父類Activity),方法名無限制,但是一般規范為inject或者injectXXX。Component可以理解為classA對象(被調用者)與MainActivity(調用者)之間的橋梁。

@Component
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

3.對項目重新Build,框架會根據MainActivityComponent自動生成DaggeMainActivityComponent類(命名規則為Dagger+Component名稱),這也驗證了在上面說到的自動生成代碼的這茬。在MainActivity中聲明ClassA成員對象,并用@Inject注解標記,然后使用DaggeMainActivityComponent完成對象注入。

public class MainActivity extends AppCompatActivity {

    @Inject
    ClassA classA; // 不能是private,后面會提到

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerMainActivityComponent.builder().build().inject(this);
        System.out.println("MainActivity.onCreate" + classA.toString());
    }
}

com.exp.dagger2 I/System.out: MainActivity.onCreatecom.exp.dagger2.ClassA@2711f49

這個例子很簡單,那么我們現在來看一下Dagger2生成的類在哪?類文件的內容是什么?是怎么完成對象注入的。我們將目錄結構切換到project模式下進入到 \app\build\generated\source\apt\debug 下可以發現自動生成的三個類:ClassA_Factory、DaggerMainActivityComponent、MainActivity_MembersInjector。

ClassA_Factory是對應ClassA類中構造器上的@Inject注解,只要類的構造器標記@Inject,那么Dagger2框架會自動為其生成對應的工廠類,用于提供實例化對象,那肯定是通過某種途徑提供給需要的地方(暫時我們只能認為是MainActivity)

// Generated by dagger.internal.codegen.ComponentProcessor (https://google.github.io/dagger).
package com.exp.dagger2;

import dagger.internal.Factory;

public final class ClassA_Factory implements Factory<ClassA> {
  private static final ClassA_Factory INSTANCE = new ClassA_Factory();

  @Override
  public ClassA get() {
    return new ClassA();
  }

  public static Factory<ClassA> create() {
    return INSTANCE;
  }

DaggerMainActivityComponent很顯然對應的是MainActivityComponent,DaggerMainActivityComponent是由Dagger2生成的一個MainActivityComponent接口的實現類。

public final class DaggerMainActivityComponent implements MainActivityComponent {
  private MembersInjector<MainActivity> mainActivityMembersInjector;

  private DaggerMainActivityComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

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

  public static MainActivityComponent create() {
    return new Builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(ClassA_Factory.create());
  }

  @Override
  public void inject(MainActivity activity) {
    mainActivityMembersInjector.injectMembers(activity);
  }

  public static final class Builder {
    private Builder() {}

    public MainActivityComponent build() {
      return new DaggerMainActivityComponent(this);
    }
  }
}

從代碼中可以看出DaggerMainActivityComponent構造器的唯一調用是在Builder中,通過builder設計模式來完成Component的實例化。Builder類中的build生成了Component對象,而我們是通過inject方法完成對象注入,而我們可以看到mainActivityMembersInjector對象中實際調用的是MainActivity_MembersInjector的injectMembers方法

public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final Provider<ClassA> classAProvider;

  public MainActivity_MembersInjector(Provider<ClassA> classAProvider) {
    assert classAProvider != null;
    this.classAProvider = classAProvider;
  }

  public static MembersInjector<MainActivity> create(Provider<ClassA> classAProvider) {
    return new MainActivity_MembersInjector(classAProvider);
  }

  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.classA = classAProvider.get();
  }

  public static void injectClassA(MainActivity instance, Provider<ClassA> classAProvider) {
    instance.classA = classAProvider.get();
  }
}

在實例化MainActivity_MembersInjector傳入了ClassA_Factory對象,在injectMembers方法中直接通過instance.classA進行對象賦值(因此注入目標成員的修飾符不能是private,否則此處無法直接引用),而classAProvider.get()就是ClassA_Factory中返回的new ClassA()。

整個簡單的流程就分析完了,現在再來總結下。首先我們的ClassA類構造器添加了@Inject,Dagger2為其生成工廠類。接著我們定義了Component接口,聲明抽象注入方法,Dagger2為其生成了對應的實現了并在方法實現中調用MainActivity_MembersInjector的inject方法,而MainActivity_MembersInjector中封裝了通過ClassA_Factory實現classA對象的注入。

簡單流程淺析

Inject + Module + Component

上面的ClassA是我們自己定義的類文件,現在有個問題。假設我們需要為某個類注入一個OkHttpClient對象。好的,那么咱們去OkHttpClient類中找個地方加@Inject注解,呃呃呃…不對,通過外部gradle命令依賴的它很高冷,咱們不能修改它。這個時候我們需要用到Module,就是說這個時候可以用Module去代替上面的@Inject(這么說不恰當)

怎么理解Module呢,我們可以想一下上個例子,構造器是干什么的?完成對象實例化的,提供對象的。沒錯,Module我們可以這么理解,它是用來向Component提供依賴對象的。實際操作一下~

1.創建一個類MainModule,并用注解@Module標記。類中定義提供OkHttpClient對象的方法并用@Provides標記

@Module
public class MainModule {

    @Provides
    public OkHttpClient provideOkHttpClient(){
        return new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).build();
    }
}

2.修改MainActivityComponent,為其使用@Component注解添加module引用。表明這個Component可能需要用到MainModule中提供的對象

@Component(modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

3.MainActivity增加OkHttpClient對象,并標記@Inject

    @Inject
    OkHttpClient client;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerMainActivityComponent.builder().build().inject(this);
        System.out.println("MainActivity.onCreate" + client);
    }

com.exp.dagger2 I/System.out: MainActivity.onCreateokhttp3.OkHttpClient@d28c04e

這個過程與上個例子的區別主要在于MainModule,咱們這次再看生成的類是什么樣的。我們主要講解差異點

自動生成的類文件

多了MainModule_ProvideOkHttpClientFactory類,該類其實與ClassA_Factory本質是一樣的。只不過ClassA_Factory只是單純的提供ClassA對象,而MainModule_ProvideOkHttpClientFactory中是用于提供OkHttpClient對象的,假設MainModule還有其他Provides,那么會生成對應數量的MainModule_ProvideXXXFactory。MainModule_ProvideOkHttpClientFactory會提供n個get方法(方法的重載),不同的get方法會返回不同的對象,這些方法的生成規則是根據MainModule中那么使用@Provides注解標記的方法,此例只有一個。

public final class MainModule_ProvideOkHttpClientFactory implements Factory<OkHttpClient> {
  private final MainModule module;

  public MainModule_ProvideOkHttpClientFactory(MainModule module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public OkHttpClient get() {
    return Preconditions.checkNotNull(
        module.provideOkHttpClient(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<OkHttpClient> create(MainModule module) {
    return new MainModule_ProvideOkHttpClientFactory(module);
  }
}

DaggerMainActivityComponent在創建mainActivityMembersInjector時候增加了一個provideOkHttpClientProvider參數,因為MainActivity_MembersInjector的作用是要完成MainActivity的對象注入,而MainActivity此時需要注入的有兩個對象,第一個是ClassA,第二個是OkHttpClient。他們需要分別從ClassA_Factory與MainModule_ProvideOkHttpClientFactory獲取

  // DaggerMainActivityComponent#initialize
  private void initialize(final Builder builder) {

    this.provideOkHttpClientProvider =
        MainModule_ProvideOkHttpClientFactory.create(builder.mainModule);

    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(ClassA_Factory.create(), provideOkHttpClientProvider);
  }
  // MainActivity_MembersInjector#injectMembers
    @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.classA = classAProvider.get();
    instance.client = clientProvider.get();
  }

如果我們把ClassA的@Inject去掉,在MainModule中添加ClassA對象的提供方法,可以?答案:是的;可以自己試試

Module帶參數

通常情況下大多數類的實例化可能都需要傳入參數,那么我們來看看帶參數情況下應該怎么寫

我們將OkHttpClient網絡請求的超時時限作為參數傳入

@Module
public class MainModule {

    int timeOut;

    public MainModule(int timeOut) {
        this.timeOut = timeOut;
    }

    @Provides
    public OkHttpClient provideOkHttpClient(){
        return new OkHttpClient.Builder().connectTimeout(timeOut, TimeUnit.SECONDS).build();
    }

    @Provides
    public ClassA providesClassA(){
        return new ClassA();
    }
}

然后在MainActivity中我們通過設置MainModule對象完成參數設置

DaggerMainActivityComponent.builder().mainModule(new MainModule(10)).build().inject(this);

這里有個需要注意的地方,之前的例子中都沒有參數,那么Component生成的類中會提供create方法直接創建Component

DaggerMainActivityComponent.create().build().inject(this);

而在有參數的情況下,不再提供create方法,而是只有build方法,因為此時需要傳入MainModule對象

當然我們更建議以下寫法:通過provide方法提供參數,一方面是解耦,一方面是代碼可讀性
Module中,不能出現參數和返回參數一致的情況,否則會導致死循環

@Module
public class MainModule {

    int timeOut;

    public MainModule(int timeOut) {
        this.timeOut = timeOut;
    }

    @Provides
    public int provideTimeOut(){
        return timeOut;
    }

    @Singleton
    @Provides
    public OkHttpClient provideOkHttpClient(int timeOut){
        return new OkHttpClient.Builder().connectTimeout(timeOut,TimeUnit.SECONDS).build();
    }

    @Provides
    public ClassA providesClassA(){
        System.out.println("MainModule.providesClassA");
        return new ClassA();
    }
}

Scope作用域

通過Singleton注解來看Scope,我們在頁面上注入了兩個OkHttpClient對象,我們來看看結果

    @Inject
    OkHttpClient client1;
    @Inject
    OkHttpClient client2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerMainActivityComponent.builder().mainModule(new MainModule(10)).build().inject(this);
        System.out.println("MainActivity.onCreate client1 --- " + client1);
        System.out.println("MainActivity.onCreate client2 --- " + client2);
    }

System.out: MainActivity.onCreate client1 — okhttp3.OkHttpClient@d28c04e
System.out: MainActivity.onCreate client2 — okhttp3.OkHttpClient@f45d56f

可以看到兩個是不同的對象,那假設我們需要讓他保持單例呢?可以使用@Single注解。我們需要關注的是如果我們希望把這個Person變成單例,這個注解需要在哪些地方使用。主要兩個地方,如下:

一是:提供Person對象的地方,有可能是類構造器,有可能是Module中某個標記了@Provids注解的方法。但是由于@Inject與@Singleton不能同時使用,因此只能是在Module中某個標記了@Provids注解的方法。

@Module
public class MainModule {

    int timeOut;

    public MainModule(int timeOut) {
        this.timeOut = timeOut;
    }

    @Singleton
    @Provides
    public OkHttpClient provideOkHttpClient(){
        return new OkHttpClient.Builder().connectTimeout(timeOut, TimeUnit.SECONDS).build();
    }

    @Provides
    public ClassA providesClassA(){
        System.out.println("MainModule.providesClassA");
        return new ClassA();
    }
}

二是:作為提供對象與對象調用者之間的橋梁-Component

@Singleton
@Component(modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

System.out: MainActivity.onCreate client1 — okhttp3.OkHttpClient@2711f49
System.out: MainActivity.onCreate client2 — okhttp3.OkHttpClient@2711f49

我們來看看生成的文件差異,主要是在DaggerMainActivityComponent#initialize方法中

  private void initialize(final Builder builder) {

    this.provideOkHttpClientProvider =
        DoubleCheck.provider(MainModule_ProvideOkHttpClientFactory.create(builder.mainModule));

    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(provideOkHttpClientProvider);
  }

在獲取provideOkHttpClientProvider對象時,額外增加了DoubleCheck.provider(…)

  /** Returns a {@link Provider} that caches the value from the given delegate provider. */
  public static <T> Provider<T> provider(Provider<T> delegate) {
    checkNotNull(delegate);
    if (delegate instanceof DoubleCheck) {
      /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped
       * binding, we shouldn't cache the value again. */
      return delegate;
    }
    return new DoubleCheck<T>(delegate);
  }

意思就是說如果咱們使用了scoped類型的注解,我們將返回之前緩存的Provider對象。我們再看看Singleton注解的定義

/**
 * Identifies a type that the injector only instantiates once. Not inherited.
 *
 * @see javax.inject.Scope @Scope
 */
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

非常明顯,類注釋已經告訴我們這個注解的作用就是用來實現單例的。這邊要注意到該注解使用了元注解@Scope,通過深入研究可以發現該Singleton注解的作用也僅僅是個標記作用而已。也就是說我們可以自定義一個注解來代替@Singleton`

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

還有需要注意的一點是Scope的作用周期取決于Component的周期長短,假設我們定義了AppComponent用于全局Application相關成員對象的注入,那么周期就是整個應用程序周期。而我們上面用到的MainActivityComponent對應的周期則是Activity的聲明周期。這邊有些同學會有疑惑的地方,如果說AppComponent中我們注入了一個單例OkHttpClient,我們后面又在別的地方通過另一個Component注入一個OkHttpClient,那么這兩個OkHttpClient對象是否為同一個呢?答案是否定的,因為Scope與Component是對應關系。盡管都設定了Scope,但是由于Component,他們之間是相關獨立的。

Qualifier方法區分

當我們的Module中存在兩個相同返回值的方法時,我們可以使用@Name進行區分,對應在注入時也需要指明。這里的Qualifier與Scope完全類似,大家可以對比一下。因此,我們也可以自定義注解去代替@Name

// AppModule
@Module
public class AppMudole {

    @Named("two")
    @Provides
    String providesString1(){
        return "test_one";
    }

    @Named("three")
    @Provides
    String providesString2(){
        return "test_two";
    }

    @Provides
    CacheManager providesCacheManager(){
        return new CacheManager();
    }
}

// MainActivity
@Named("two")
@Inject
String test1;
@Named("three")
@Inject
String test2;

自定義注解的話可以這樣子


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

}

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

}

@Module
public class AppMudole {

    @QualifierTwo
    @Provides
    String providesString1(){
        return "test_one";
    }

    @QualifierThree
    @Provides
    String providesString2(){
        return "test_two";
    }

    @Provides
    CacheManager providesCacheManager(){
        return new CacheManager();
    }
}

@QualifierTwo
@Inject
String test1;
@QualifierThree
@Inject
String test2;

Component間的依賴

很多時候可能某個東西是很多地方都需要使用到的,這個時候我們就可以通過Component之間的依賴實現

方式1:dependence


// 修改ClassA構造
public class ClassA {

    CacheManager cacheManager;

    public ClassA(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }
}

@Module
public class AppMudole {

    Context context;

    public AppMudole(Context context) {
        this.context = context;
    }

    @Provides
    Context providesContext(){
        return context;
    }

    @Provides
    CacheManager providesCacheManager(){
        return new CacheManager();
    }
}

@Component(modules = AppMudole.class)
public interface AppComponent {

    CacheManager providesCacheManager();
}

@Singleton()
@Component(dependencies = AppComponent.class,modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

定義一個AppModule提供對象,定義AppComponent作為全局基礎組件,提供應用中可能常用到的對象。在基礎組件中我們一般就不會提供注入方法,因為該Component一般作為其他Component的依賴,而不會具體在某個地方使用。

然后我們在注入時候需要將AppComponent對象傳遞給MainActivityComponent

        AppComponent appComponent = DaggerAppComponent
                .builder().appMudole(new AppMudole(this)).build();
        DaggerMainActivityComponent.builder().appComponent(appComponent)
                .mainModule(new MainModule(10))
                .build().inject(this);

這個時候咱們來看看apt生成的代碼


  // DaggerMainActivityComponent#initialize
  private void initialize(final Builder builder) {

    this.providesCacheManagerProvider =
        new com_exp_dagger2_AppComponent_providesCacheManager(builder.appComponent);

    this.providesClassAProvider =
        MainModule_ProvidesClassAFactory.create(builder.mainModule, providesCacheManagerProvider);

    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(providesClassAProvider);
  }

  // DaggerMainActivityComponent$com_exp_dagger2_AppComponent_providesCacheManager
  private static class com_exp_dagger2_AppComponent_providesCacheManager
      implements Provider<CacheManager> {
    private final AppComponent appComponent;

    com_exp_dagger2_AppComponent_providesCacheManager(AppComponent appComponent) {
      this.appComponent = appComponent;
    }

    @Override
    public CacheManager get() {
      return Preconditions.checkNotNull(
          appComponent.providesCacheManager(),
          "Cannot return null from a non-@Nullable component method");
    }
  }

DaggerMainActivityComponent中對于Component依賴中提供的對象獲取仍然是通過Provider對象的形式實現,只不過對于Component依賴,會為其創建一個命名規則為”包名依賴組件名提供對象的方法名”靜態內部類。然后通過構造模式的Builder獲取到組件對象,作為參數傳入com_exp_dagger2_AppComponent_providesCacheManager生成Provider。

假設AppMudole中提供了獲取另一個類型對象的方法,這里舉例如下

    @Provides
    String providesString(){
        return "test";
    }

這個時候我們嘗試在MainActivity中進行String對象的注入

    @Inject
    String test;

編譯會發現報錯,Dagger2找不到從哪能獲取到這個String對象

java.lang.String cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.

我們在必須在AppComponent中顯式提供String對象的方法即可

@Component(modules = AppMudole.class)
public interface AppComponent {

    CacheManager providesCacheManager();
    String providesString();
}

那么這樣來看的話就很清晰了,其實AppComponent被其他Component依賴,那么它只是作為AppModule對象的轉發者

方式2:SubComponent

AppComponent中的定義不再是提供AppModule中的對象轉發,而是變成提供子Component

@Component(modules = AppMudole.class)
public interface AppComponent {

    MainActivityComponent activityComponent();
}

這種情況下,就可以直接使用AppModule提供的對象完成注入(對此上例中的String對象注入)
然后子Comonent的定義很簡單,沒有變化,只是去除了dependencies換為SubComponent

@Singleton()
@Subcomponent(modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

需要注意的是這個時候MainModule必須提供無參構造器(這個在后面分析會提到)

注入時通過DaggerAppComponent完成注入,ClassA對象的注入是由SubComponent(即MainActivityComponent)提供的,而String對象的注入是由AppComponent提供的


    @Inject
    ClassA classA;
    @Inject
    String test;

    DaggerAppComponent.builder().build().activityComponent().inject(this);

OK,我們再來看看生成的代碼


 // DaggerAppComponent部分代碼

  @Override
  public MainActivityComponent activityComponent() {
    return new MainActivityComponentImpl();
  }

  private final class MainActivityComponentImpl implements MainActivityComponent {
    private final MainModule mainModule;

    private Provider<ClassA> providesClassAProvider;

    private MembersInjector<MainActivity> mainActivityMembersInjector;

    private MainActivityComponentImpl() {
      this.mainModule = new MainModule();
      initialize();
    }

    @SuppressWarnings("unchecked")
    private void initialize() {

      this.providesClassAProvider =
          MainModule_ProvidesClassAFactory.create(
              mainModule, DaggerAppComponent.this.providesCacheManagerProvider);

      this.mainActivityMembersInjector =
          MainActivity_MembersInjector.create(
              providesClassAProvider, DaggerAppComponent.this.providesStringProvider);
    }

    @Override
    public void inject(MainActivity activity) {
      mainActivityMembersInjector.injectMembers(activity);
    }
  }

可以看到DaggerAppComponent中為每個子Component生成了一個命名規則為“ComponentName + Impl”的內部類,并提供了獲取方法(其實是根據AppComponent接口的定義),然后在initialize方法中我們可以看到mainActivityMembersInjector由兩個部分組成:providesClassAProvider(由MainModule而來,SubComponent所用的Module),providesStringProvider(由AppModule而來,AppComponent所用的Module,對象由外部類DaggerAppComponent創建)。注意,這種情況下時候子Component不會生成任何DaggerXXXComponent類。

兩種方式比較

兩種方式都是用來實現Component之間關聯的,dependencies 方法更像Java中的組合形式,而Subcomponent更像是類之間的繼承關系。具體情況根據實際需要進行選擇,下面是我以自身的理解進行差異分析

兩者與Scope需要注意的:兩個擁有依賴關系的 Component 是不能有相同 @Scope 注解的,而使用@SubComponent 則可以使用相同的@Scope注解。

dependencies 方法

特點:

  • 可以清晰的對應Component所依賴的其他Component
  • 兩個Component都會生成對應的DaggerXXXComponent
  • 被依賴Component為目標Component提供相關對象獲取
  • 注入時使用目標Component傳入依賴Component完成

注意:

  • 被依賴Component中沒有顯式提供的依賴無法完成注入

SubComponent 方法

特點:

  • 只有父Component會生成DaggerXXXComponent
  • 父Component可以方便的管理子Component
  • 默認可獲取到父子兩Component使用到的Module所提供的對象
  • 注入時使用父Component并選擇指定的子Component完成

注意:

  • 子Component所用到的Module必須提供無參構造器

懶加載與重新加載

懶加載(Lazy):調用get方法時候才去創建對象,后續獲取都為同一個對象
重新加載(Provides):調用get方法時候會強制重新創建對象,對象是否為同一個取決于Module的實現或Scope注解

public class MainActivity extends AppCompatActivity {

    @Inject
    Lazy<ClassA> classALazy;
    @Inject
    Provider<String> provider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerAppComponent.builder().build().activityComponent().inject(this);
        ClassA classA = this.classALazy.get();
        String string = this.provider.get();

    }
}

老樣子,看看生成的代碼

  // MainActivity_MembersInjector#injectMembers
  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.classALazy = DoubleCheck.lazy(classALazyProvider);
    instance.provider = providerProvider;
  }

咱們主要就看MainActivity_MembersInjector類中的injectMembers方法,通常情況下injectMembers方法中都是直接通過相關的Provider的get方法提取對象。在使用了Lazy則通過DoubleCheck.check()處理后得到Lazy對象,而Provider則直接賦值


  // DoubleCheck 部分代碼

  /** Returns a {@link Lazy} that caches the value from the given provider. */
  public static <T> Lazy<T> lazy(Provider<T> provider) {
    if (provider instanceof Lazy) {
      @SuppressWarnings("unchecked")
      final Lazy<T> lazy = (Lazy<T>) provider;
      // Avoids memoizing a value that is already memoized.
      // NOTE: There is a pathological case where Provider<P> may implement Lazy<L>, but P and L
      // are different types using covariant return on get(). Right now this is used with
      // DoubleCheck<T> exclusively, which is implemented such that P and L are always
      // the same, so it will be fine for that case.
      return lazy;
    }
    return new DoubleCheck<T>(checkNotNull(provider));
  }

  @SuppressWarnings("unchecked") // cast only happens when result comes from the provider
  @Override
  public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          /* Get the current instance and test to see if the call to provider.get() has resulted
           * in a recursive call.  If it returns the same instance, we'll allow it, but if the
           * instances differ, throw. */
          Object currentInstance = instance;
          if (currentInstance != UNINITIALIZED && currentInstance != result) {
            throw new IllegalStateException("Scoped provider was invoked recursively returning "
                + "different results: " + currentInstance + " & " + result + ". This is likely "
                + "due to a circular dependency.");
          }
          instance = result;
          /* Null out the reference to the provider. We are never going to need it again, so we
           * can make it eligible for GC. */
          provider = null;
        }
      }
    }
    return (T) result;
  }

由上面代碼可以看出,Lazy其實是一個DoubleCheck對象,通過封裝Provider使用雙重檢驗機制完成單例實現。

總結

Component:必不可少的東西,本身不提供對象,主要用于鏈接對象提供者與注入目標,完成對象注入
Inject:可用于標記構造器,表明可通過該構造器獲取對象;可用于標記需要注入的目標成員變量;
Module:提供各類對象,方法返回值與參數不可一致,可用@Name或者自定義注解區分相關返回值的方法
Scope:用于作用域標記,可用@Single或者自定義注解實現單例或者對象作用域范圍控制
Component關聯:dependencies 方式與 SubComponent 方式,類似組合與集成,區分使用
懶加載(Lazy)與重新加載(Provider):Lazy是Provider封裝而成的DoubleCheck對象,單例
在相關的方法定義中方法名是沒有嚴格要求的,只不過是為了規范統一,核心在返回值類型

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

推薦閱讀更多精彩內容