Dagger2新版注解及源碼解析

一、序

接:Dagger2.1不是Dagger2

demo地址:https://github.com/mrqatom/DaggerInjection

通過學習,我們知道了新版Dagger的用法,可是作為有追求的騷年,不能僅僅成為API的搬運工,必須要了解一下其中具體的用法以及實現方式。首先我們來看看幾個注解的具體作用。

二、@Component.Builder

我們在AppComponent里有如下代碼

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }
image.gif

我們大概也猜出他的作用,就是自定義構造component

以前我們的用法像這樣:

 @Module
 public class AppModule {

    Application application;

    public AppModule(Application application) {
       this.application = application;
    }

    @Provides
    Application providesApplication() {
       return application;
    }
    @Provides
    public SharedPreferences providePreferences() {
        return application.getSharedPreferences(DATA_STORE,
                              Context.MODE_PRIVATE);
    }

 }

//使用時
DaggerAppComponent appComponent = DaggerAppComponent.builder()
         .appModule(new AppModule(this)) //this : application 
         .build();
image.gif

現在Dagger允許我們自定義Builder:

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

   void inject(MainActivity mainActivity);

   @Component.Builder
   interface Builder {
        AppComponent build();
        Builder appModule(AppModule appModule);
    }
}
image.gif

就像我們以前的用法一樣。這看起來多此一舉,不過,配合@BindsInstance就會發生不一樣的化學反應,下面我們來看看:

@Module
 public class AppModule {

     @Provides
     @Singleton
     public SharedPreferences providePreferences(
                                    Application application) {
         return application.getSharedPreferences(
                                    "store", Context.MODE_PRIVATE);
     }
 }

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

      AppComponent build();
      @BindsInstance Builder application(Application application);      
  }

//使用時
DaggerAppComponent appComponent = DaggerAppComponent.builder()
           .application(this)
           .build();
image.gif

我們的代碼發生了如下改變:

1、AppModule不再需要Application為參數的構造函數,可以直接使用Application

2、component里加入application并加上了@BindsInstance注解,而且無需傳入Module

3、使用時無需傳入Module而僅僅需要application即可

這個注解有效的簡化了Module的初始化并減少了與module的耦合,所以再回頭看看demo里的AppComponent是不是更清晰了些呢?

三、@IntoMap

這個注解看名字就可以猜測,用來把什么東西加入map里。

是的,就是把依賴加入map里,先來看看官方簡單的例子:

@Module
class MyModule {
  @Provides @IntoMap
  @StringKey("foo")
  static Long provideFooValue() {
    return 100L;
  }

  @Provides @IntoMap
  @ClassKey(Thing.class)
  static String provideThingValue() {
    return "value for Thing";
  }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<String, Long> longsByString();
  Map<Class<?>, String> stringsByClass();
}

@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.longsByString().get("foo")).isEqualTo(100L);
  assertThat(myComponent.stringsByClass().get(Thing.class))
      .isEqualTo("value for Thing");
}
image.gif

我們來梳理一下重點:

1、@intoMap需要和@provides及@binds之類的注解配合使用,這很好理解,就是把依賴in to map而已

2、需要和@mapKey配合使用,即map的key,比如StringKey、ClassKey、ActivityKey...balabala...

3、使用時就和普通map一樣

四、@Binds

@Binds和@provides一樣,都是提供依賴的作用,也可以說是優化版,我們先來看看@provides的用法:

@Provides
public LoginContract.Presenter 
  provideLoginPresenter(LoginPresenter loginPresenter) {
    return loginPresenter;
}
image.gif

這應該是Dagger使用者比較常見的格式了,相比之下@Binds方式就顯得更簡潔了:

@Binds
public abstract LoginContract.Presenter
  provideLoginPresenter(LoginPresenter loginPresenter);
image.gif

以下是重點:

1、binds方式是抽象方法,無需方法體

2、只能有一個參數且return類型與其相同

3、使用@Binds后該module變為抽象(廢話

4、@binds與@provides不得在同一個Module中使用,必須分開寫在兩個Module里,可以使用include使其關聯就像這樣

@Module(includes = Declarations.class)
public class MainActivityModule {
    @Provides
    MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
        return new MainPresenter(mainView, apiService);
    }
}

@Module
public abstract class Declarations {
    @Binds
    abstract MainView provideMainView(MainActivity mainActivity);
}
image.gif

5、如果你一定要將他們寫在一個module里,也有方案,把@provides方法變為static即可:

@Module
public abstract class MainActivityModule {
    @Binds
    abstract MainView provideMainView(MainActivity mainActivity);

    @Provides
    static MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
        return new MainPresenter(mainView, apiService);
    }
}
image.gif

五、源碼解析

Dagger2是利用APT自動化形成,這里我就不詳述了不然一篇文章根本不夠,大家可以自行谷歌關鍵字:‘APT’、‘編譯期注解’

我不會一段一段代碼的貼,而是講述大概流程以及一些思想,避免“休閑式學習”,所以大家需要自己打開源碼瀏覽,如果僅看文章大概率會懵逼:),先來看一段Dagger的初始代碼

public class MyApplication extends Application implements HasActivityInjector {
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().application(this).build().inject(this);
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }
}
image.gif

繼承HasActivityInjector重寫activityInjector,定義DispatchingAndroidInjector加上@Inject這里先提一下,后面有用。

主要是 DaggerAppComponent.builder().application(this).build().inject(this) 這句代碼,這是一句很明顯的建造者模式的代碼,是由我們自定義的@Component.Builder形成的,當我們調用.build時會對DaggerAppComponent進行初始化即調用initialize方法,代碼如下:

  private void initialize(final Builder builder) {
    this.mainActivityComponentBuilderProvider =
        new Provider<MainActivityComponent.Builder>() {
          @Override
          public MainActivityComponent.Builder get() {
            return new MainActivityComponentBuilder();
          }
        };
  }
image.gif

初始化時創建了ActivityBuilder中由@Binds注解的方法return的類,并用Provider封裝了一層,其實也就是我們的@Subcomponent.Builder修飾的類而已,這里我們僅有一個MainActivity

這個類實際上只是一個創建者,調用build方法創建真正的實現類:MainActivityComponentImpl,在這個類里會提供我們Module里用provides注解的依賴,我們對比一下:

    //Module中
    @Provides
    MainView provideMainView(MainActivity mainActivity){
        return mainActivity;
    }
    @Provides
    MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
        return new MainPresenter(mainView, apiService);
    }

    //源碼中
    private MainView getMainView() {
      return MainActivityModule_ProvideMainViewFactory.proxyProvideMainView(
          mainActivityModule, seedInstance);
    }

    private MainPresenter getMainPresenter() {
      return MainActivityModule_ProvideMainPresenterFactory.proxyProvideMainPresenter(
          mainActivityModule, getMainView(), new ApiService());
    }

image.gif

proxyProvideMainPresenter其實就是調用module的provideMainPresenter來生成一個Mainpresenter

當MainActivityComponentImpl的inject方法被調用時,以上方法也同時被調用,像這樣:

    @Override
    public void inject(MainActivity arg0) {
      injectMainActivity(arg0);
    }

    private MainActivity injectMainActivity(MainActivity instance) {
      //....省略
      MainActivity_MembersInjector.injectPresenter(instance, getMainPresenter());
      return instance;
    }
image.gif

MainActivity_MembersInjector.injectPresenter會將生成的Mainpresenter注入到MainActivity里,也就完成了依賴注入。

至于inject方法什么時候被調用,以及MainActivity如何傳入的留待后文分析,我們先回到初始化的地方。

DaggerAppComponent構建完成之后,又調用了他的Inject方法,這個方法了只做了一件事,就是初始化MyApplication中的

DispatchingAndroidInjector,而他里面就保存了我們上面提到的MainActivityComponentBuilder,如果有多個Activity的話,就會把他們保存到一個map中,key為activity名字,這就是@IntoMap的作用了。

所以DispatchingAndroidInjector其實提供了一個倉庫的作用,倉庫里保存了我們在ActivityBuilder里@binds修飾的類,其實也就是我們所有的Activity

還記得我們在MyApplication中重寫的方法嗎?

@Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }
image.gif

通過這個方法就可以獲取到該“倉庫”,然后一步步獲取到真正的實現類完成依賴注入,具體我們繼續看。

那我們Activity如何獲取到該’倉庫‘呢?在每一個Activity中都需要調用一句代碼:

  AndroidInjection.inject(this);

  //實現
  public static void inject(Activity activity) {
    checkNotNull(activity, "activity");
    //拿到application
    Application application = activity.getApplication();
    //判斷application是否繼承了HasActivityInjector
    if (!(application instanceof HasActivityInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              application.getClass().getCanonicalName(),
              HasActivityInjector.class.getCanonicalName()));
    }
    //通過剛剛講到的方法拿到“倉庫”
    AndroidInjector<Activity> activityInjector =
        ((HasActivityInjector) application).activityInjector();
    checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());
    //調用“倉庫”的inject方法,并傳入了相應的Activity
    activityInjector.inject(activity);
  }

image.gif

“倉庫”的inject方法會在map里找到我們之前所說的真正實現類完成所有依賴注入工作。

因為“倉庫”保存了我們所有Activity,所以只要在Activity里調用上述代碼就能完成該Activity的依賴注入。

至于Fragment的注入是如何實現由讀者自行閱讀,就是在Activity依賴注入實現類里又創建了一個Fragment的ComponentBuilder而已,類似于遞歸的思想。

至于@ContributesAndroidInjector的實現方式就是和基本版相同,只是自動生成了component而已,幫我們簡化了操作。

總結

看完大家可能覺得Dagger源碼解析部分很少,事實上Dagger的源碼確實不算難,適合剛剛學習看源碼的童鞋,克服源碼的恐懼就是現在了。至于編譯期注解方面的知識,建議大家去看Arouter源碼上手。

Dagger的思想是非常優秀的,加上現在更新的越來越簡潔,非常推薦大家嘗試使用。

課外拓展:研究@Singleton以及@scope的源碼實現

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。