出來混遲早要還的,技術債Dagger2:Android篇(下)進一步理解Dagger

鹹魚正翻身發表於2019-04-11

前言

警告!這不是一個乾貨的文章!

個人認為,學技術不宜太浮躁。對於一項技術一味地追求乾貨其實並不一定有用,貨太乾容易噎著,哈哈~不如循序漸進慢下來,一點點去體會技術的前因後果。(這個系列適合於:瞭解但沒有在專案裡大規模應用Dagger2的讀者)

出來混遲早要還的,技術債Dagger2:基礎篇

出來混遲早要還的,技術債Dagger2:Android篇(上)

出來混遲早要還的,技術債Dagger2:Android篇(中)@Scope、@Singleton

本以為閱讀一些文件,寫一些Demo就能駕馭工作中的專案...我錯了,我再也不會有這麼愚蠢的想法了... 這麼多依賴關係,誰扛得住啊!所以還是一點點來吧。

正文

前倆篇文章過後,我猜大家對下面的程式碼已經很熟悉了:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
  void inject(MainActivity mainActivity);
  SharedPreferences getSharedPrefs();
}

@Module
public class AppModule {
    Application application;
 
    public AppModule(Application application) {
       this.application = application;
    }
 
    @Provides
    Application providesApplication() {
       return application;
    }
    
    @Provides
    @Singleton
    public SharedPreferences providePreferences() {
        return application.getSharedPreferences(DATA_STORE,Context.MODE_PRIVATE);
    }
}


DaggerAppComponent appComponent = DaggerAppComponent.builder()
     .appModule(new AppModule(this)) 
     .build();
複製程式碼

很基本,很簡單的Dagger應用。不過大家有沒有感覺到這個appModule(new AppModule(this))特別煩?安利我用的時候,說依賴注入,看不見new。我哼哧哼哧寫這麼多,這不還是new出來的?

那麼問題來了,是不是可以不需要appModule(new AppModule(this))呢?當然可以。用過Dagger-Android的朋友,肯定很清楚,的確看不到任何new。那麼這篇文章,我們們就來看看如何徹底不用new。

當然這也是Dagger-Android的原理

@Component.Builder

做這一切的前提是這個註解。這個註解是幹啥的呢?說白了,給Component提供Module的依賴。

我們們先來想一個問題,下面程式碼存在的意義:

DaggerAppComponent appComponent = DaggerAppComponent.builder()
     .appModule(new AppModule(this)) 
     .build();
複製程式碼

Dagger為我們所需的依賴生成例項,那麼必然需要構建整個依賴關係網。Dagger就算是再神奇,也不可能憑空把整個我們所需要依賴關係構建出來,所以需要我們適時的“提供和引導”。那麼new AppModule(this)就是給Dagger進行提供,因為從我們上述的程式碼中,Dagger是不知道該怎麼去例項化這個AppModule,因此需要我們對其進行提供。

那麼話又說回來,我們在上述的程式碼中,告訴它如何去例項化AppModule,不就可以避免我們手動去new AppModule(this)了麼?

沒錯,@Component.Builder就是做這個的。

累死了,繞了一圈不知道大家理沒理解@Component.Builder存在的含義了。

AppModule(Application application)

對於我們的AppModule來說,例項化它的關鍵是如何提供一個Application

public AppModule(Application application) {
   this.application = application;
}
複製程式碼

對於Dagger也是如此,它不能例項化AppModule的原因是它不知道或者說沒辦法去獲取一個Application例項。因此,對於Dagger來說,我們應該給它提供Application的例項,而非AppModule

開始改造

改造需要一步步來:第一步,我們使用@Component.Builder去改造AppComponent

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
   void inject(MainActivity mainActivity);
   SharedPreferences getSharedPrefs();
   // 改造內容
   @Component.Builder
   interface Builder {
        AppComponent build();
        // 此時還未改造這行程式碼
        Builder appModule(AppModule appModule);
    }
}
複製程式碼

現在我們告訴了Dagger,你要以@Component.Builder註解的介面那樣去例項化我們所需的AppComponent

是不是發現這裡新增的方法和DaggerAppComponent生成的程式碼很像?沒錯Dagger預設例項化AppComponent就是以這種程式碼進行的。

不過,對於我們這個AppModule來說,我們不需要關係它是怎麼初始化(因為我們只需要它所提供給我們的依賴)。對於Dagger來說也是如此: Dagger想要為我們提供被@Provides標註的依賴,只需要擁有Application例項即可。因為只要擁有Application例項Dagger就有辦法例項化AppModule,直接new即可。

所以這裡我們需要一種方式來告訴Dagger:我要提供給你,在@Module中需要的內容。對於我們們的這個demo來說,我們們需要用一種方式把Dagger所需要的Application給他。

而做到這一點的就是@BindsInstance

@BindsInstance

按照官網的介紹,此註解用於標識Component Builder/SubComponent Builder中的某個方法,該方法允許將例項繫結到Component中。

所以對於我們的AppModule來說,它只需要提供@Provides的內容就可以了!,它所需要的,Dagger會按照我們的“指示”,注入進來。也就是這個樣子:

@Module
public class AppModule {
     @Provides
     @Singleton
     // 外部傳入Application例項(Dagger會按照我們的“指示”,注入進來)
     public SharedPreferences providePreferences(Application application) {
         return application.getSharedPreferences("store", Context.MODE_PRIVATE);
     }
 }
複製程式碼

而我們的AppConponent這樣就可以了:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
   void inject(MainActivity mainActivity);
   SharedPreferences getSharedPrefs();
   @Component.Builder
   interface Builder {
        AppComponent build();
        @BindsInstance 
        // 此時的application就會被,注入到AppModule的providePreferences方法中
        Builder application(Application application);      
  }
}
複製程式碼

build過後,我們初始化DaggerAppComponent,只需如此寫:

DaggerAppComponent appComponent = DaggerAppComponent.builder()
   .application(this)
   .build();
複製程式碼

那麼Dagger是如何為我們生成DaggerAppComponent的呢?

public final class DaggerAppComponent implements AppComponent {
  private Provider<Application> applicationProvider;

  private Provider<SharedPreferences> providePreferencesProvider;

  private DaggerAppComponent(Builder builder) {
    initialize(builder);
  }

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

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {
    this.applicationProvider = InstanceFactory.create(builder.application);
    this.providePreferencesProvider =
        DoubleCheck.provider(
            AppModule_ProvidePreferencesFactory.create(builder.appModule, applicationProvider));
  }

  @Override
  public void inject(MainActivity mainActivity) {}

  @Override
  public SharedPreferences getSharedPrefs() {
    return providePreferencesProvider.get();
  }

  private static final class Builder implements AppComponent.Builder {
    private AppModule appModule;

    private Application application;

    @Override
    public AppComponent build() {
      if (appModule == null) {
        this.appModule = new AppModule();
      }
      if (application == null) {
        throw new IllegalStateException(Application.class.getCanonicalName() + " must be set");
      }
      return new DaggerAppComponent(this);
    }

    @Override
    public Builder application(Application application) {
      this.application = Preconditions.checkNotNull(application);
      return this;
    }
  }
}
複製程式碼

對於AppModule來說,只是簡單的new出來,當我們需要@Provides時,只是將所需的application傳進去。而我們的application以成員變數的身份呆在了DaggerAppComponent“體內”。

尾聲

這篇內容,其實並不是為了去講@Component.Builder@BindsInstance。而是儘可能的通過它們,來加深大家去Component和Module的理解。去感受Dagger的實現,更好的去理解依賴注入。對與我們而言,需要用其形,學其神。

這樣我們才不會受制於框架,而是駕馭框架。

我是一個應屆生,最近和朋友們維護了一個公眾號,內容是我們在從應屆生過渡到開發這一路所踩過的坑,以及我們一步步學習的記錄,如果感興趣的朋友可以關注一下,一同加油~

個人公眾號:鹹魚正翻身

相關文章