Dagger2從入門到補胎(一)

HuYounger發表於2017-11-17

概述

最近公司的專案是用mvp+dagger2搭的框架,由於之前沒接觸過dagger2,改bug和做需求總是一臉懵逼,看了些文件介紹,和大多數學習者一樣從Dependency Injection、註解概念等等開始瞭解,然後敲程式碼上手,在此記錄下學習心得。既然是入門,那些概念和註解的歷史就不介紹了,Google一下你就知道,直接介紹最最基本的使用以及具體實現原理。

@Inject、@Component

先看一個例子,MainActivity依賴Province,Province依賴City,City依賴Street;

使用前

Street.java

public class Street {
    public Street(){}

    public String show(){
        return "人民南路";
    }
}複製程式碼

City.java

public class City {
    public Street street;
    public City(Street street) {
        this.street = street;
    }

    public String show() {
        return "成都市" + street.show();
    }
}複製程式碼

Province.java

public class Province {
    public City city;

    public Province(City city) {
        this.city = city;
    }

    public String showAddress() {
        return "四川省" + city.show();
    }
}複製程式碼

MainActivity.java

public class MainActivity extends AppCompatActivity {
    public Street street;
    public City city;
    public Province province;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        street = new Street();
        city = new City(street);
        province = new Province(city);
        Log.d("hcy", "onCreate: " + province.showAddress());
    }
}複製程式碼

可以看到,為了獲取地址資訊,在程式碼中需要例項化各種依賴到的物件,一旦依賴過多就容易影響程式碼閱讀,那麼配合使用@Inject和@Component又是怎樣的呢?

使用後

1.首先在build.gradle中新增依賴

dependencies {
  compile 'com.google.dagger:dagger-android:2.11'
  compile 'com.google.dagger:dagger-android-support:2.11' // if you use the support libraries
  annotationProcessor 'com.google.dagger:dagger-android-processor:2.11'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
}複製程式碼

Street.java

public class Street {
    @Inject
    public Street(){}

    public String show(){
        return "人民南路";
    }
}複製程式碼

2.需要依賴的成員和提供依賴的成員建構函式用@Inject標註

City.java

public class City {
    @Inject
    public Street street;
    @Inject
    public City(Street street) {
        this.street = street;
    }

    public String show() {
        return "成都市" + street.show();
    }
}複製程式碼

Province.java

public class Province {
    @Inject
    public City city;

    @Inject
    public Province(City city) {
        this.city = city;
    }

    public String showAddress() {
        return "四川省" + city.show();
    }
}複製程式碼

3.需要額外加一個介面MainActivityComponent.java,用@Component標註

@Component
public interface MainActivityComponent {
    void inject(MainActivity activity);
}複製程式碼

4.執行DaggerMainActivityComponent.create().inject(this)

此時的MainActivity.java

public class MainActivity extends AppCompatActivity {
    @Inject
    public Province province;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //in Android Studio, select Build > Rebuild Project
        DaggerMainActivityComponent.create().inject(this);
        Log.d("hcy", "onCreate: " + province.showAddress());
    }
}複製程式碼

前後的列印是一致的,可以看到MainActivity的中原本需要例項化物件的那些程式碼現在可以省略了,有助於我們更好地關注業務實現。

小結

回顧下使用註解的步驟:

1.build.gradle中新增dagger2依賴

2.使用@Inject標註在建構函式和被引用的成員變數上

3.新建MainActivityComponent介面,並用@Component標註

4.在MainActivity中執行DaggerMainActivityComponent.create().inject(this);(第一次需Rebuild Project)

原始碼分析

我們在MainaActivity中加了DaggerMainActivityComponent.create().inject(this)這句程式碼替換了之前的一些例項化的操作,那麼這句程式碼具體做了哪些工作?原來Dagger2會在編譯過程中生成對應的依賴項,這些依賴項在Android Studio該路徑下,如圖所示:

Dagger2從入門到補胎(一)

DaggerMainActivityComponent.create()

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

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

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

    public MainActivityComponent build() {  
      return new DaggerMainActivityComponent(this);
    }
}複製程式碼

可以看到,不管是通過builder().build()還是create(),最後都會呼叫DaggerMainActivityComponent建構函式;在通過原始碼閱讀後,可以將整個過程分為兩步,分別是initialize()和inject()

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

 private void initialize(final Builder builder) {
    this.cityMembersInjector = City_MembersInjector.create(Street_Factory.create());//註釋1
    this.cityProvider = City_Factory.create(cityMembersInjector, Street_Factory.create());//註釋2
    this.provinceMembersInjector = Province_MembersInjector.create(cityProvider);//註釋3
    this.provinceProvider = Province_Factory.create(provinceMembersInjector, cityProvider);//註釋4
    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provinceProvider);//註釋5
  }複製程式碼

在initialize()方法中對成員賦值,這裡的成員分為兩類,分別以_MembersInjector和_Factory為字尾;Xx_MembersInjector可以理解為注入器,用來實現Xx與內部成員的依賴:

public interface MembersInjector<T> {
  void injectMembers(T instance);
}複製程式碼

Xx_Factory是建立Xx的工廠:

public interface Provider<T> {  
    T get();
}
public interface Factory<T> extends Provider<T> {
}複製程式碼

這裡先記住兩者的功能,具體後面會分析

註釋1

先看下Street_Factory.java

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

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

  public static Factory<Street> create() {
    return INSTANCE;
  }
}複製程式碼

程式碼很簡單,通過惡漢式建立了一個Street_Factory單例(這裡的原始碼可能會有不同,之前看過一版是通過列舉建立的單例);再看下City_MembersInjector.java

public final class City_MembersInjector implements MembersInjector<City> {
  private final Provider<Street> streetProvider;

  public City_MembersInjector(Provider<Street> streetProvider) {
    assert streetProvider != null;
    this.streetProvider = streetProvider;
  }

  public static MembersInjector<City> create(Provider<Street> streetProvider) {
    return new City_MembersInjector(streetProvider);
  }

  @Override
  public void injectMembers(City instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.street = streetProvider.get();
  }
}複製程式碼

City_MembersInjector只是將傳進來的Street_Factory賦值給自己的成員變數;

註釋2

後面的功能都一樣,就用虛擬碼列出:

City_Factory.java

public final class City_Factory implements Factory<City> {
  public City_Factory(...省略引數){
    this.city_MembersInjector = city_MembersInjector;
    this.Street_Factory = street_Factory;
  }
}複製程式碼
註釋3

Province_MembersInjector.java

public final class Province_MembersInjector implements MembersInjector<Province> {
  public Province_MembersInjector(...省略引數){
    this.city_Factory = city_Factory
  }
}複製程式碼
註釋4

Province_Factory.java

public final class Province_Factory implements Factory<Province> {
  public Province_Factory(...省略引數){
    this.province_MembersInjector = province_MembersInjector
    this.city_Factory = city_Factory
  }
}複製程式碼
註釋5
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final Provider<Province> provinceProvider;

  public MainActivity_MembersInjector(Provider<Province> provinceProvider) {
    this.province_Factory = provinceProvider;
  }
}複製程式碼

到此目標工廠和注入器都已經建立完成,但是此時目標物件和依賴關係還沒產生。

inject()

inject()通過呼叫injectMembers()完成真正目標物件例項化以及依賴操作,程式碼也沒多少,就把整個完整的過程涉及到的程式碼貼出來:

//DaggerMainActivityComponent.java
public void inject(MainActivity activity) {
    mainActivityMembersInjector.injectMembers(activity);
  }

//MainActivity_MembersInjector.java  
public void injectMembers(MainActivity instance) {
  if (instance == null) {
    throw new NullPointerException("Cannot inject members into a null reference");
  }
  instance.province = provinceProvider.get();
}

//Province_Factory.java
public Province get() {
    return MembersInjectors.injectMembers(
        provinceMembersInjector, new Province(cityProvider.get()));
  }

//Province_MembersInjector.java  
public void injectMembers(Province instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.city = cityProvider.get();
  }

//City_Factory.java
public City get() {
    return MembersInjectors.injectMembers(cityMembersInjector, new City(streetProvider.get()));
  }

//City_MembersInjector.java
public void injectMembers(City instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.street = streetProvider.get();
  }
//Street_Factory.java
public Street get() {
    return new Street();
  }複製程式碼

可以看到inject()和initialize()剛好是相反的過程,直到找到依賴的源頭完成源頭物件的例項化,即這裡的Street_Factory的get()方法的返回值;在Street例項化完成之後返回給City完成City例項化,City例項化完之後對自己的成員重新賦值了一遍,即產生依賴關係:

//City_MembersInjector.java
public void injectMembers(City instance) {
  if (instance == null) {
    throw new NullPointerException("Cannot inject members into a null reference");
  }
  instance.street = streetProvider.get();
}複製程式碼

instance.street賦值的時候呼叫了streetProvider.get(),這是一個怎樣的過程呢,還是畫個圖吧

Dagger2從入門到補胎(一)

step1的時候已經完成了Street和City的例項化,接著執行instance.street = streetProvider.get(),這句程式碼即下面step2的過程:

Dagger2從入門到補胎(一)

紅色的就變成垃圾了,所以在這個過程中Street被new了兩次;繼續分析建立Province以及建立依賴的過程:

Dagger2從入門到補胎(一)

可以看到在整個過程中Street其實new了4次,如果依賴更多的話(比如最外層再加個Country),Street在記憶體中被new的次數也會*2,不過過程中這些物件最終都會變成垃圾被回收(總覺得這是額外的開銷,依賴多了豈不是編譯就慢了?),而不用dagger2只要new一次就可以了(如下圖),但是兩者最終都是一條依賴鏈

Dagger2從入門到補胎(一)

總結

整個流程:

Dagger2從入門到補胎(一)

黑色的流程線是initialize()的過程,用來建立目標例項的工廠和注入器;紅色流程線是inject()/injectMembers()的過程,用來建立目標例項以及實現依賴。最後在回過頭來看下@inject和@component這兩個標註,可以得出一些結論:

1.若一個類(Xx)的建構函式被@inject標註,則該類編譯時會產生Xx_Factory;

2.若一個類中的成員變數被@inject標註,則該類編譯時會產生Xx_MembersInjector;

3.@Component標註的介面(Xx)在編譯時生成DaggerXx,負責將上面兩個聯絡起來。

一個圖

Dagger2從入門到補胎(一)

@Module和@Provides

學習這兩個註解,配合@Inject和@Component使用完成和上面一樣的功能

使用前

和上面的使用前程式碼一致

使用後

1.建立一個Module.java,這裡取名BeanModule.java(想怎麼取名字都可以,比如周杰倫.java,但是最好別這麼幹,官方推薦以Module為字尾),並用@Module標註,建立目標例項的方法用@Provides標註(官方推薦方法名以provide為字首)如下:

@Module
public class BeanModule {
    @Provides
    Street providerStreet() {
        return new Street();
    }
    @Provides
    City providerCity(Street street) {
        return new City(street);
    }
    @Provides
    Province providerProvince(City city) {
        return new Province(city);
    }
}複製程式碼

2.修改上面的MainActivityComponent.java介面,@Component後面多了一個配置,如下:

@Component(modules = BeanModule.class)
public interface MainActivityComponent {
    void inject(MainActivity activity);
}複製程式碼

modules的作用相當於告訴Component,當你需要建立物件的時候就到我這裡來拿。MainActivity不變,執行後的效果和上面一毛一樣。

原始碼分析

先看編譯過程生成的檔案:

Dagger2從入門到補胎(一)

同樣從DaggerMainActivityComponent.create().inject(this),這句程式碼開始分析

DaggerMainActivityComponent.create()

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

public static final class Builder {
    private BeanModule beanModule;

    private Builder() {}

    public MainActivityComponent build() {
      if (beanModule == null) {
        this.beanModule = new BeanModule();
      }
      return new DaggerMainActivityComponent(this);
    }

    public Builder beanModule(BeanModule beanModule) {
      this.beanModule = Preconditions.checkNotNull(beanModule);
      return this;
    }
  }複製程式碼

不管是通過builder().build()還是create(),最後都會呼叫DaggerMainActivityComponent建構函式;和上面對比這裡多了一步,那就是建立了BeanModule例項,還是把這個過程分成兩步:initialize()和inject()

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

private void initialize(final Builder builder) {
  this.providerStreetProvider = BeanModule_ProviderStreetFactory.create(builder.beanModule);

  this.providerCityProvider =
      BeanModule_ProviderCityFactory.create(builder.beanModule, providerStreetProvider);

  this.providerProvinceProvider =
      BeanModule_ProviderProvinceFactory.create(builder.beanModule, providerCityProvider);

  this.mainActivityMembersInjector =
      MainActivity_MembersInjector.create(providerProvinceProvider);
}複製程式碼

initialize()方法對自己的成員賦值,建立了目標物件工廠和注入器,在這裡的注入器只有一個MainActivity_MembersInjector

inject()
//DaggerMainActivityComponent.java
public void inject(MainActivity activity) {
  mainActivityMembersInjector.injectMembers(activity);
}

//MainActivity_MembersInjector.java
public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.province = provinceProvider.get();
  }
//...省略中間的部分get()
//BeanModule_ProviderStreetFactory.java
public Street get() {
    return Preconditions.checkNotNull(
        module.providerStreet(), "Cannot return null from a non-@Nullable @Provides method");
  }複製程式碼

這裡的inject()和injectMembers()的工作和上面是一樣的,呼叫BeaModule中的方法建立需要的例項,若該例項在建立時依賴其他例項,則呼叫BeaModule中的對應方法先建立依賴例項,直到找到源頭,最大的區別就是在Xx_Factory中少了一步重新建立依賴的過程。用一張圖來表示建立的過程就是

Dagger2從入門到補胎(一)

可以看到,相比於上面那種在過程中new很多例項的做法,這種做法顯然更加高效。多說一點,在這些生成的XX_Factory中還多了一個靜態方法,如:

//BeanModule_ProviderStreetFactory.java
/** Proxies {@link BeanModule#providerStreet()}. */
  public static Street proxyProviderStreet(BeanModule instance) {
    return instance.providerStreet();
  }
//BeanModule_ProviderProvinceFactory.java
/** Proxies {@link BeanModule#providerProvince(City)}. */
public static Province proxyProviderProvince(BeanModule instance, City city) {
  return instance.providerProvince(city);
}複製程式碼

因此我們可以在MainActivity直接通過類名呼叫建立例項。

用圖來表示@Module、@Providers、@Component、@Inject之間的聯絡:

Dagger2從入門到補胎(一)

Why

既然用@Inject和@Component就能夠完成的功能,為啥我還要多寫那些Module類和provider方法,別跟我說是因為編譯過程中比較費時?原來@Inject並不是萬能的,官網的介紹:

But @Inject doesn’t work everywhere:

  • Interfaces can’t be constructed.
  • Third-party classes can’t be annotated.
  • Configurable objects must be configured!

如果我們要注入的物件是個介面,介面不能被例項化;或者是我們要注入的物件是第三方庫,我們沒法把@Inject標註在三方庫的建構函式上,真是這樣的麼?就拿介面舉例:

改改上面的例子,新增一個IShow介面

public interface IShow {
    String show();
}複製程式碼

修改City.java

public class City implements IShow {
    @Inject
    public Street street;

    @Inject
    public City(Street street) {
        this.street = street;
    }

    @Override
    public String show() {
        return "成都市" + street.show();
    }複製程式碼

修改Province.java,讓它接收一個介面

public class Province {
    @Inject
    public IShow city;

    @Inject
    public Province(IShow city) {
        this.city = city;
    }

    public String showAddress() {
        return "四川省" + city.show();
    }
}複製程式碼

MainActivity不變,跑起來果然GG了

Dagger2從入門到補胎(一)

而配合使用@Module和@Provides則不會出現這種情況。問題又來了,如果依賴的提供方同時存在以@Inject標註和以@Module、@Providers標註,會找哪個?回到程式碼中,把兩種寫法都補充完整,要回答這個問題還是隻有看原始碼了,通過編譯可以看到生成的檔案

Dagger2從入門到補胎(一)

這裡把兩個方案都導進來了,隨時待命,再看下DaggerMainActivityComponent.java的initialize()方法:

private void initialize(final Builder builder) {

  this.providerStreetProvider = BeanModule_ProviderStreetFactory.create(builder.beanModule);

  this.providerCityProvider =
      BeanModule_ProviderCityFactory.create(builder.beanModule, providerStreetProvider);

  this.providerProvinceProvider =
      BeanModule_ProviderProvinceFactory.create(builder.beanModule, providerCityProvider);

  this.mainActivityMembersInjector =
      MainActivity_MembersInjector.create(providerProvinceProvider);
}複製程式碼

可以發現和上面分析@Module、@Providers的原始碼是一樣的,之後的流程當然也一樣。因此如果兩種同時存在,會選擇@Module、@Providers,而另一種只是個“備胎”...

學習資料

當然dagger2還有很多很強大的功能,待續

github dagger介紹

dagger官網

相關文章