【Android】個人Dagger2學習歷程:從一頭霧水到恍然大悟

前言

關於Dagger2的教程在網上已經有不少不少了,對於使用和原理都講得比較明白,可是對於第一次接觸的人們來講(好比我),不免會看得一頭霧水,因此這裏我就記錄一下我學習Dagger2的過程,分享最快速的學習方法給你們。javascript

介紹

Dagger2是一個依賴注入的框架,什麼是依賴注入?簡單的來講就是類中依賴的對象只要聲明就可使用了,它的建立由框架來管理。以下代碼所示,application直接就能夠拿來用了。java

public class LoginActivity extends Activity{

    @Inject
    Application application;
}複製代碼

開始

剛開始接觸Dagger2的時候大量閱讀了網上的教程,主要是一些概念性的東西,篇幅長了一下就看暈了,因此這裏推薦你們直接看代碼。把代碼運行起來,結合文章和代碼一塊兒看,相信你很快就能上手了。
gitHub地址android

在主項目的build.gradle中添加git

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'複製代碼

在Module的build.gradle中添加github

compile 'com.google.dagger:dagger:2.6'
apt 'com.google.dagger:dagger-compiler:2.6'複製代碼

@Component

首先這個先定義一個全局的AppComponent,爲何須要全局的AppComponent呢?由於這裏放得都是一些公共的對象,它們的生命週期是和Application一致的。一般狀況下一個項目定義一個AppComponent,其餘每一個Activity或Fragment會對應一個Component。數據庫

@Singleton
@Component(modules = {AppModule.class, HttpModule.class, ApiServiceModule.class, DBModule.class})
public interface AppComponent {

    Application application();

    Gson gson();
    //網絡訪問Service
    ServiceManager serviceManager();
    //數據庫訪問
    DBManager DBManager();
}複製代碼

AppComponent是一個接口,裏面定義了提供依賴的方法聲明,這個AppComponent提供了Application、Gson、ServiceManager、DBManager依賴(方法名沒有限制),dagger框架會根據對象類型把它們注入到須要使用它們的地方。
那這些提供的對象是從哪裏來的呢?總不能憑空產生吧,這就要看module了,AppComponent中引用的AppModule、HttpModule、ApiServiceModule、DBModule,如今咱們進入AppModule中看看。api

@Module & @Provides

@Module
public class AppModule {

    public AppModule(HuiApplication application) {
        this.mApplication = application;
    }

    private Application mApplication;

    @Singleton
    @Provides
    public Application provideApplication() {
        return mApplication;
    }

    @Singleton
    @Provides
    public Gson provideGson() {
        return new Gson();
    }

}複製代碼

能夠看到Module就是提供這些依賴的地方,dagger會根據@Provides標記的方法返回依賴對象,這個AppModule中提供了Application和Gson對象的建立。可能你們都注意到@Singleton這個註解了吧,若是須要讓依賴對象是單例的話標註一下就能夠,後面還會提到。網絡

若是你的provide方法裏須要用到其餘provide提供的對象,能夠直接經過方法參數傳進來,以下所示,provideRetrofit()方法中須要用到OkHttpClient和HttpUrl,直接傳進來就能夠了。架構

@Module
public class HttpModule {
    @Singleton
    @Provides
    Retrofit provideRetrofit(OkHttpClient client, HttpUrl baseUrl) {
        return new Retrofit.Builder()
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(baseUrl)
                .build();
    }

    @Singleton
    @Provides
    OkHttpClient provideOkHttpClient() {

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS);

        return builder.build();
    }

}複製代碼

AppComponent

如今須要建立AppComponent,由於這是全局的Component,天然是在Application中建立了:app

public class HuiApplication extends Application{

    private AppComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        mAppComponent = DaggerAppComponent
                .builder()
                .appModule(new AppModule(this))
                .apiServiceModule(new ApiServiceModule())
                .dBModule(new DBModule())
                .httpModule(new HttpModule())
                .build();
    }


    public AppComponent getAppComponent() {
        return mAppComponent;
    }
}複製代碼

bulid一下項目,dagger2會爲每一個component建立Dagger+Component名的類,該類中會提供建立和設置每個module實例的方法,能夠看到這裏的module是咱們本身new了傳進去的,咱們能夠爲各個module作一些初始化的處理。這裏爲AppComponent建立一個get方法,接下來就要到Activity了。上面說了一般狀況下每一個Activity會對應一個Component,那如今就爲LoginActivity建立一個LoginComponent:

@dependencies

@ActivityScope
@Component(dependencies = AppComponent.class)
public interface LoginComponent {

    void inject(LoginActivity activity);
}複製代碼

首先看到@dependencies,這裏就把AppComponent中提供的一些對象依賴了過來,實現了全局共用。同時聲明一個inject方法,參數是你要注入到的類(方法名詞不限,這裏用inject比較形象)。如今就看看LoginActivity是如何注入的:

@Inject

public class LoginActivity extends Activity {

    @Inject
    ServiceManager serviceManager;
    @Inject
    DBManager DBManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        HuiApplication mApplication = (HuiApplication) getApplication();
        setupActivityComponent(mApplication.getAppComponent());
        initData();
    }


    @Override
    protected void setupActivityComponent(AppComponent appComponent) {
        DaggerLoginComponent
                .builder()
                .appComponent(appComponent)
                .build()
                .inject(this);
    }
}複製代碼

一樣定義了LoginComponent後會自動生成DaggerLoginComponent,這裏從Application中獲取以前建立的AppComponent,最後調用inject,注入就完成了。此時使用@Inject來標記成員變量就可使用了,有沒有感受很神奇?此時你可能會以爲疑惑,接下來就開始對上面使用的一些註解和方法進行講解。

概念

dagger2是什麼?

Dagger2是一款基於Java註解來實現的徹底在編譯階段完成依賴注入的開源庫,主要用於模塊間解耦、提升代碼的健壯性和可維護性。Dagger2在編譯階段經過apt利用Java註解自動生成Java代碼,而後結合手寫的代碼來自動幫咱們完成依賴注入的工做。

下圖能很好地展現Dagger2這幾個註解的做用:

註解介紹

@Component
用於標註接口,是依賴需求方和依賴提供方之間的橋樑。被Component標註的接口在編譯時會生成該接口的實現類(Dagger+Component名字),咱們經過調用這個實現類的方法完成注入;Component接口中主要定義一些提供依賴的聲明

@Inject有三個做用
一是用來標記須要依賴的變量,以此告訴Dagger2爲它提供依賴;
二是用來標記構造函數,Dagger2經過@Inject註解能夠在須要這個類實例的時候來找到這個構造函數並把相關實例構造出來,以此來爲被@Inject標記了的變量提供依賴,例如:

public class Student {

    private String name;
    private int age;

    @Inject
    public Student() {

    }
}複製代碼

三是用來標記普通方法,該方法會在對象注入完成以後調用,能夠根據這一特性所以作一些初始化的工做。

@Module
@Module用於標註提供依賴的類。你可能會有點困惑,上面不是提到用@Inject標記構造函數就能夠提供依賴了麼,爲何還須要@Module?不少時候咱們須要提供依賴的構造函數是第三方庫的,咱們無法給它加上@Inject註解,又好比說提供以來的構造函數是帶參數的,若是咱們之所簡單的使用@Inject標記它,那麼他的參數又怎麼來呢?@Module正是幫咱們解決這些問題的。

@Provides
@Provides用於標註Module所標註的類中的方法,該方法在須要提供依賴時被調用,從而把預先提供好的對象當作依賴給標註了@Inject的變量賦值;

@Scope
@Scope一樣用於自定義註解,我能能夠經過@Scope自定義的註解來限定註解做用域,實現局部的單例;好比咱們前面使用到的@ActivityScope:

@Scope
@Retention(RUNTIME)
public @interface ActivityScope {}複製代碼

若是須要提供局部單例支持,則須要在Component中和@provides註解的方法上@ActivityScope,這裏說的局部單例的意思是在該Component中是惟一的,若是Component是全局惟一的話就是全局單例了,好比AppComponent。

@Singleton
@Singleton其實就是一個經過@Scope定義的註解,咱們通常經過它來標記全局單例(AppComponent)。

咱們提到@Inject和@Module均可以提供依賴,那若是咱們即在構造函數上經過標記@Inject提供依賴,有經過@Module提供依賴Dagger2會如何選擇呢?具體規則以下:
步驟1:首先查找@Module標註的類中是否存在提供依賴的方法。
步驟2:若存在提供依賴的方法,查看該方法是否存在參數。
a:若存在參數,則按從步驟1開始依次初始化每一個參數;
b:若不存在,則直接初始化該類實例,完成一次依賴注入。
步驟3:若不存在提供依賴的方法,則查找@Inject標註的構造函數,看構造函數是否存在參數。
a:若存在參數,則從步驟1開始依次初始化每個參數
b:若不存在,則直接初始化該類實例,完成一次依賴注入。

Dagger2趕上MVP

若是你的項目是採用MVP架構的,那麼結合Dagger2將會是一件很是棒的體驗,它讓M-V-P進一步解藕,架構更清晰。
在上面的LoginActivity基礎上實現MVP模式。

LoginContract

MVP接口契約類,定義view和model的接口

public interface LoginContract {

    interface View extends BaseView {

        /** * 登陸成功 * @param result */
        void loginSuccess(String result);
    }

    interface Model extends IModel {

        /** * 登陸 * @param mobile * @param password */
        Observable<String> login(String mobile, String password);

    }
}複製代碼

LoginModule

定義Module,只要提供view和model的依賴,能夠看到LoginModel是經過方法參數注入進來的,這樣model和view就解耦了。

@Module
public class LoginModule {
    private LoginContract.View view;

    public LoginModule(LoginContract.View view) {
        this.view = view;
    }

    @ActivityScope
    @Provides
    LoginContract.View provideLoginView() {
        return this.view;
    }

    @ActivityScope
    @Provides
    LoginContract.Model provideLoginModel(LoginModel model) {
        return model;
    }
}複製代碼

LoginComponent

在以前的loginComponent中添加LoginModule

@ActivityScope
@Component(modules = LoginModule.class, dependencies = AppComponent.class)
public interface LoginComponent {

    void inject(LoginActivity activity);
}複製代碼

LoginActivity

在LoginActivity中初始化LoginComponent,咱們就從這裏開始看看MVP的依賴是怎麼行程的:

public class LoginActivity extends BaseActivity<LoginPresenter> implements LoginContract.View {

    @Override
    protected void setupActivityComponent(AppComponent appComponent) {
        DaggerLoginComponent
                .builder()
                .appComponent(appComponent)
                .loginModule(new LoginModule(this))
                .build()
                .inject(this);
    }
}複製代碼

咱們的view就是當前的Activity,因此new LoginModule(this)這裏就提供了view的依賴。
這裏定義了BaseActivity並使用了泛型,設置成當前界面的presenter,這裏能夠知道注入的過程是在BaesActivity中完成的,如今看看BaseActivity:

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity {

    ....省略代碼

    @Inject
    protected P mPresenter;

    ....省略代碼
}複製代碼

BaseActivity中其實作的操做很簡單,經過@Inject註解將對應的Presenter注入進來。這樣在LoginActivity中就可使用該Presenter了,如今咱們看看LoginPresenter的實現。

LoginPresenter

@ActivityScope
public class LoginPresenter extends BasePresenter<LoginContract.Model, LoginContract.View> {

    @Inject
    public LoginPresenter() {
    }

}複製代碼

Presenter中和Actvity的作法基本相似,對象的注入仍是放在父類裏面,經過泛型的方式肯定類型。這裏能夠看到這裏有一個@Inject標註的空構造方法,這個是必須的,爲了就是在LoginActivity中能夠依賴到該Presenter。

public class BasePresenter<M extends IModel, V extends BaseView> implements IPresenter {
    @Inject
    protected M mModel;
    @Inject
    protected V mView;
}複製代碼

BasePresenter裏面就是View和Model的注入。
model的實現和presenter的是原理是同樣的,這裏就不一一述說了,這樣MVP的架構就簡歷起來了:

  • 在View(Activity)中注入Presenter;
  • 在Presenter中注入View 和 Model
  • 在Model中注入其餘一些數據處理的對象(數據庫實例和網絡請求實例)

Dagger2生成的代碼解析

到這裏,你是否是以爲爲何Dagger2會如此神奇?咱們這裏就對生成的代碼DaggerAppComponent來進行解析,看看它是怎麼實現依賴注入的。

DaggerAppComponent
    .builder()
    .appModule(new AppModule(this))
    .apiServiceModule(new ApiServiceModule())
    .dBModule(new DBModule())
    .httpModule(new HttpModule())
    .build();複製代碼

那就從build方法開始:

public AppComponent build() {
      if (appModule == null) {
        throw new IllegalStateException(AppModule.class.getCanonicalName() + " must be set");
      }
      if (httpModule == null) {
        this.httpModule = new HttpModule();
      }
      if (apiServiceModule == null) {
        this.apiServiceModule = new ApiServiceModule();
      }
      if (dBModule == null) {
        this.dBModule = new DBModule();
      }
      return new DaggerAppComponent(this);
    }複製代碼

build方法中會對咱們傳入的module進行NULL檢查,能夠看出來,若是咱們的model的構造函數是無參的話,能夠不用設置,dagger2會幫咱們初始化,接着看new DaggerAppComponent():

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


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

    this.provideApplicationProvider =
        DoubleCheck.provider(AppModule_ProvideApplicationFactory.create(builder.appModule));

    this.provideOkHttpClientProvider =
        DoubleCheck.provider(HttpModule_ProvideOkHttpClientFactory.create(builder.httpModule));

    this.provideBaseUrlProvider =
        DoubleCheck.provider(
            ApiServiceModule_ProvideBaseUrlFactory.create(builder.apiServiceModule));

    this.provideRetrofitProvider =
        DoubleCheck.provider(
            HttpModule_ProvideRetrofitFactory.create(
                builder.httpModule, provideOkHttpClientProvider, provideBaseUrlProvider));

    this.provideUserServiceProvider =
        DoubleCheck.provider(
            ApiServiceModule_ProvideUserServiceFactory.create(
                builder.apiServiceModule, provideRetrofitProvider));

    this.serviceManagerProvider =
        DoubleCheck.provider(ServiceManager_Factory.create(provideUserServiceProvider));

    this.provideCommonSQLiteHelperProvider =
        DoubleCheck.provider(
            DBModule_ProvideCommonSQLiteHelperFactory.create(
                builder.dBModule, provideApplicationProvider));

    this.provideUserInfoDaoProvider =
        DoubleCheck.provider(
            DBModule_ProvideUserInfoDaoFactory.create(
                builder.dBModule, provideCommonSQLiteHelperProvider));

    this.dBManagerMembersInjector = DBManager_MembersInjector.create(provideUserInfoDaoProvider);

    this.dBManagerProvider =
        DoubleCheck.provider(
            DBManager_Factory.create(dBManagerMembersInjector, provideApplicationProvider));

    this.provideGsonProvider =
        DoubleCheck.provider(AppModule_ProvideGsonFactory.create(builder.appModule));
  }複製代碼

在構造方法裏面調用了initialize(builder),這裏面對全部Provider進行初始,這個方法也是完成注入的方法,這裏涉及到兩個對象:

  • Provider & Factory
    其實就是一個包裝類,裏面提供了get方法返回對應的包裝對象,好比Provider ,get方法就返回它持有的Application對象。這些Provider就是咱們全部提供依賴的對象(包括在@module類中使用@Provides註解標註的對象,或者@Inject標記構造方法的對象)
  • XX_MembersInjector
    顧名思義,這個是對象注入器,哪些使用了@Inject註解的類就會生成對應的MembersInjector,經過調用其injectMembers()方法實現對象注入。

DoubleCheck.provider()有什麼做用呢,它是實現局部單例的,返回一個實現單例的Provider。

這裏咱們看下LoginActivity_MembersInjector 是怎麼注入LoginPresenter的:

public final class LoginActivity_MembersInjector implements MembersInjector<LoginActivity> {
  private final Provider<LoginPresenter> mPresenterProvider;

  public LoginActivity_MembersInjector(Provider<LoginPresenter> mPresenterProvider) {
    assert mPresenterProvider != null;
    this.mPresenterProvider = mPresenterProvider;
  }

  public static MembersInjector<LoginActivity> create(Provider<LoginPresenter> mPresenterProvider) {
    return new LoginActivity_MembersInjector(mPresenterProvider);
  }

  @Override
  public void injectMembers(LoginActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    cn.xdeveloper.dagger2.base.mvp.BaseActivity_MembersInjector.injectMPresenter(
        instance, mPresenterProvider);
  }
}複製代碼

在injectMembers方法中調用了父類的injectMembers方法,由於咱們把注入的過程抽取到父類了,再看看父類的injectMembers方法:

public static <P extends BasePresenter> void injectMPresenter(
      BaseActivity<P> instance, Provider<P> mPresenterProvider) {
    instance.mPresenter = mPresenterProvider.get();
  }複製代碼

經過簡單的對象賦值就完成了注入,那調用LoginActivity_MembersInjector的injectMembers方法的地方是哪裏呢?

@Override
public void inject(LoginActivity activity) {
    loginActivityMembersInjector.injectMembers(activity);
}複製代碼

沒錯這個方法就是咱們在LoginComponent中定義的inject接口的實現方法。
這裏你可能會以爲奇怪,爲何我定義了inject方法名就會生成injectMembers的實現呢?咱們再看看以前定義的LoginComponent的代碼:

@ActivityScope
@Component(modules = LoginModule.class, dependencies = AppComponent.class)
public interface LoginComponent {

    void inject(LoginActivity activity);
}複製代碼

這裏的方法申明是有講究的:

  • 若是參數有值的,表明這個方法是注入方法,注入的類就是該參數類,框架會爲其建立injectMembers的實現,這個時候是不容許有返回值的,這裏方法名是能夠隨意填寫,叫inject比較形象;
  • 若是參數是沒有值的時候,則表明該component提供了依賴,返回類型就是該依賴對象,好比以前的AppComponet中定義的:
@Singleton
@Component(modules = {AppModule.class, HttpModule.class, ApiServiceModule.class, DBModule.class})
public interface AppComponent {

    Application application();

    ServiceManager serviceManager();

    DBManager DBManager();

    Gson gson();
}複製代碼

那麼這些定義的方法有什麼用呢?最簡單的方法就是看代碼中哪裏引用了它們就知道了:

this.applicationProvider =
        new Factory<Application>() {
          private final AppComponent appComponent = builder.appComponent;

          @Override
          public Application get() {
            return Preconditions.checkNotNull(
                appComponent.application(),
                "Cannot return null from a non-@Nullable component method");
          }
        };複製代碼

這裏是DaggerLoginActivityComponent的初始化方法裏,由於咱們的LoginActivityComponent是依賴AppComponnet的,那要怎麼引用這些AppComponent中已經初始化好的對象呢?則是經過上面定義的這些接口方法來訪問的。

後續問題

由於Dagger2是在編譯階段完成依賴注入,沒有了反射帶來的效率問題,但同時就會缺少了靈活性。
我在重構項目的時候就遇到了這麼一個問題,因爲個人項目是多數據庫的,一個用戶對應一個數據庫,這樣在依賴SQLDataBaseHelper的時候就無從下手了,由於這個在編譯期間是沒法知道用戶信息的,思前想後終於想到了一個辦法:
咱們能夠在AppComponent中管理一個叫DBManager的對象,在DBManager裏面含有各類Dao對象,可是這些Dao的建立是由咱們本身去建立而不是靠dagger2注入的,這樣的話咱們就能夠在其餘須要使用數據庫的地方@Inject DBManager就能夠了,附上代碼僅供參考:

@Singleton
public class DBManager {

    private Application application;

    private ContactDao contactDao;

    @Inject
    public DBManager(Application application) {
        this.application = application;
    }


    public ContactDao getContactDao(Long userId) {
        if (contactDao == null) {
            synchronized (DBManager.class) {
                if (contactDao == null)
                    contactDao = new ContactDao(PrivateDBHelper.getInstance(application, userId));
            }
        }
        return contactDao;
    }
}複製代碼

結尾

以上就是所有我對dagger2的瞭解,最初我也是從一頭霧水,如今終算恍然大悟,仍是那句老話:代碼是最好的老師。對於瞭解dagger2的注入原理的話多看生成的代碼,邏輯仍是挺清晰的。
最後,但願你們能早日擁抱dagger2。

代碼地址 GitHubQQ:318531018

相關文章
相關標籤/搜索