天天看點

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結

轉載請标明出處:http://blog.csdn.net/shensky711/article/details/53715960

本文出自: 【HansChen的部落格】

  • 概述
  • 聲明需要注入的對象
  • 如何執行個體化出依賴
  • Module的使用
  • Component的使用
  • Dagger的進階使用
    • Components之間的關系
      • dependencies
      • Subcomponents
    • Binds注解
    • Scopes
    • Singlton
    • 自定義Scope
    • scope的作用
    • Lazy注入
    • Provider注入
    • Qualifiers注入
    • 編譯時驗證
  • 小結

概述

在開發過程中,為了實作解耦,我們經常使用依賴注入,常見的依賴注入方式有:

  • 構造方法注入:在構造方法中把依賴作為參數傳遞進去
  • setter方法注入:添加setter方法,把依賴傳遞進去
  • 接口注入:把注入方法抽到一個接口中,然後實作該接口,把依賴傳遞進去

下面用一個小栗子來說明三種方式的用法:

public class PersonService implements DependencyInjecter {

    private PersonDao personDao;

    // 構造方法注入
    public PersonService(PersonDao personDao) {
        this.personDao = personDao;
    }

    // setter方法注入
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    // 接口注入:實作DependencyInjecter接口
    @Override
    public void injectPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    ... ... 
}
           

我們來看下使用一般的依賴注入方法時,代碼會是怎麼樣的:

public class MainActivity extends AppCompatActivity {

    private PersonService mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 建立PersonService的依賴:personDao
        PersonDao personDao = new PersonDaoImpl();
        // 通過構造方法注入依賴
        mService = new PersonService(personDao);
    }
}
           

看起來還好是吧?但現實情況下,依賴情況往往是比較複雜的,比如很可能我們的依賴關系如下圖:

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結

PersonDaoImpl依賴類A,類A依賴B,B依賴C和D…在這種情況下,我們就要寫出下面這樣的代碼了:

public class MainActivity extends AppCompatActivity {

    private PersonService mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 建立依賴D
        D d = new D();
        // 建立依賴C
        C c = new C();
        // 建立依賴B
        B b = new B(c, d);
        // 建立依賴A
        A a = new A(b);
        // 建立PersonService的依賴:personDao
        PersonDao personDao = new PersonDaoImpl(a);
        // 通過構造方法注入依賴
        mService = new PersonService(personDao);
    }
}
           

MainActivity隻是想使用PersonService而已,卻不得不關注PersonService的依賴是什麼、PersonDaoImpl依賴的依賴是什麼,需要把整個依賴關系搞清楚才能使用PersonService。而且還有一個不好的地方,一旦依賴關系變更了,比如A不再依賴B了,那麼就得修改所有建立A的地方。那麼,有沒有更好的方式呢?Dagger就是為此而生的,讓我們看看使用Dagger後,MainActivity會變成什麼模樣:

public class MainActivity extends AppCompatActivity {

    @Inject
    PersonService mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Dagger注入,讀者現在可先不關注裡面做了什麼操作
        DaggerPersonServiceComponent.create().inject(MainActivity.this);

        // 注意,mService已經是非空了,可以正常使用
        mService.update(, "HansChen");
        ......
    }
}
           

之前建立A、B、C、D、PersonDaoImpl等依賴的代碼全不見了,隻需要調用一個注入語句就全搞定了。調用了注入語句之後,mService就可以正常使用了,是不是挺友善呢?至于這句注入語句具體幹了什麼,讀者現在可以先不管,後面會有詳細說明,這裡隻是做一個使用示範而已。

我們大概猜想一下,在MainActivity使用PersonService需要做哪些?

  1. 分析生成依賴關系圖,如PersonService–>PersonDaoImpl–>A–>B–>C&D
  2. 根據依賴關系圖擷取相關依賴,比如依次建立D、C、B、A、PersonDaoImpl、PersonService的執行個體
  3. 把生成的PersonService執行個體傳遞給MainActivity的mService成員變量

其實Dagger做的也就是上面這些事情了,接下來就讓我們真正開始學習Dagger吧

聲明需要注入的對象

首先我們應該用

javax.inject.Inject

去注解需要被自動注入的對象,@Inject是Java标準的依賴注入(JSR-330)注解。比如下面栗子中,需要注入的對象就是MainActivity的mService。這裡有個要注意的地方,被@Inject注解的變量不能用private修飾

public class MainActivity extends AppCompatActivity {

    // 注意,不能被private修飾
    @Inject
    PersonService mService;
    ......
}
           

如何執行個體化出依賴?

在執行依賴注入的時候,Dagger會查找@Inject注解的成員變量,并嘗試擷取該類的執行個體,Dagger最直接的方式就是直接new出相應的對象了。執行個體化對象的時候,會調用對象的構造方法,但假如有多個構造方法,具體用哪個構造方法來執行個體化對象?Dagger肯定是不會幫我們“擅自做主”的,用哪個構造方法來執行個體化對象應該是由我們做主的,是以我們需要給相應的構造方法添加@Inject注解。

當Dagger需要執行個體化該對象的時候,會調用@Inject注解的構造方法來執行個體化對象:

public class PersonService implements DependencyInjecter {

    private PersonDao personDao;

    // 用@Inject注解,相當于告訴Dagger需要執行個體化PersonService的時候,請調用這個構造方法
    @Inject
    public PersonService(PersonDao personDao) {
        this.personDao = personDao;
    }

    ......
}
           

聰明的你應該發現了,調用PersonService的構造方法需要傳入PersonDao執行個體,是以要執行個體化PersonService,必須先要執行個體化PersonDao,Dagger會幫我們自動分析出這個依賴關系,并把它添加到依賴關系圖裡面!Dagger會嘗試先去執行個體化一個PersonDao,如果PersonDao又依賴于另外一個對象A,那麼就先嘗試去執行個體化A……以此類推,是不是很像遞歸?當所有依賴都被執行個體化出來之後,我們的PersonService當然也被構造出來了。

問題又來了,如果PersonDao是一個接口呢?Dagger怎麼知道這個接口應該怎麼實作?答案是不知道的,那麼Dagger怎麼執行個體化出一個接口出來?這個就是Module存在的意義之一了。關于Module的講解我們會在後面詳細說明,我們現在隻要知道,Module裡面會定義一些方法,這些方法會傳回我們的依賴,就像:

@Module
public class PersonServiceModule {

    /**
     * 提供PersonDao接口執行個體
     */
    @Provides
    PersonDao providePersonDao(A a) {
        return new PersonDaoImpl(a);
    }
}
           

Dagger根據需求擷取一個執行個體的時候,并不總是通過new出來的,它會優先查找Module

中是否有傳回相應執行個體的方法,如果有,就調用Module的方法來擷取執行個體。

比如你用@Inject注解了一個成員變量,Dagger會查找Module中是否有用@Provides注解的,傳回該類執行個體的方法,有的話就會調用provide方法來獲得執行個體,然後注入,如果沒有的話Dagger就會嘗試new出一個執行個體。就像我們現在這個栗子,PersonService依賴于PersonDao接口,Dagger不能直接為我們new出一個接口,但我們可以提供一個Module,在Module中定義一個傳回PersonDao接口執行個體的方法,這樣,Dagger就可以解決執行個體化PersonDao的問題了。

我們再梳理一下流程,如果我們用@Inject注解了一個成員變量,并調用注入代碼之後,Dagger會這樣處理:

  1. 查找Module中是否有用@Provides注解的,傳回該類執行個體的方法
  2. 如果有,就調用那個provide方法來獲得執行個體,然後注入
  3. 如果沒有,就嘗試調用相應的類中被@Inject注解的構造方法new出一個執行個體,然後注入
  4. 如果沒有一個構造方法被@Inject注解,Dagger會因不能滿足依賴而出錯

是以假如一個變量被@Inject注解,要麼在Module中提供provide方法擷取執行個體,要麼該類提供一個被@Inject注解的構造方法,否則Dagger會出錯

Module的使用

一般而言,Dagger會擷取所有依賴的執行個體,比如當需要一個

TestBean

的時候,會通過

new TestBean()

建立執行個體并注入到類中。但是,以下情況會就不好處理了:

  1. 需要生成的是一個接口,而Dagger不能直接執行個體化接口
  2. 不能在第三方庫的類中添加注解
  3. 可配置的對象必須是配置的

為了解決以上問題,我們需要定義一個被@Module注解的類,在裡面定義用

@Provides

注解的方法。用該方法傳回所需的執行個體。

@Module
public class PersonServiceModule {

    @Provides
    D provideD() {
        return new D();
    }

    @Provides
    C provideC() {
        return new C();
    }

    @Provides
    B provideB(C c, D d) {
        return new B(c, d);
    }

    @Provides
    A provideA(B b) {
        return new A(b);
    }

    /**
     * 提供PersonDao執行個體
     */
    @Provides
    PersonDao providePersonDao(A a) {
        return new PersonDaoImpl(a);
    }
}
           

就像

providePersonDao

傳回了PersonDao接口執行個體,Dagger雖然不能直接執行個體化出PersonDao接口,但卻可以調用Module的providePersonDao方法來獲得一個執行個體。providePersonDao方法需要傳入A的執行個體,那麼這裡也構成了一個依賴關系圖。Dagger會先擷取A的執行個體,然後把執行個體傳遞給providePersonDao方法。

Component的使用

到目前為止,我們雖然知道了:

  • Dagger怎麼擷取執行個體:
    • 從Module的provide方法中擷取
    • 通過@Inject注解的構造方法new出新的執行個體
  • Dagger會推導provide方法和構造方法的參數,形成依賴圖,并“滿足”我們依賴圖的需求,擷取依賴的執行個體

看樣子需要注入的依賴可以擷取了,但是不是總覺得還有點“零碎”,整個流程還沒連貫起來?比如,Module既然是一個類,生成依賴圖的時候,怎麼知道跟哪個Module挂鈎?即使最後生成了需要的執行個體,注入的“目的地”是哪裡?怎麼才能把它注入到“目的地”?殘缺的這部分功能,正是Component提供的,Component起到了一個橋梁的作用,貫通Module和注入目标。我們來看看最開始那個例子,我們是怎麼進行依賴注入的:

public class MainActivity extends AppCompatActivity {

    @Inject
    PersonService mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        PersonServiceComponent component = DaggerPersonServiceComponent.builder()
                                                                       .personServiceModule(new PersonServiceModule())
                                                                       .build();
        // 注入,所有@Inject注解的成員變量都會同時注入
        component.inject(MainActivity.this);

        // 通過component擷取執行個體,注意,這裡隻是示範用法,其實mService在component.inject的時候已經完成了注入
        mService = component.getPersonService();
    }
}
           

這個DaggerPersonServiceComponent是什麼鬼?DaggerPersonServiceComponent其實是Dagger為我們自動生成的類,它實作了一個Component接口(這個接口是需要我們自己寫的),我們來看下它實作的接口長什麼樣子:

/**
 * 指定PersonServiceModule,當需要擷取某執行個體的時候,會查找PersonServiceModule中是否有傳回相應類型的方法,有的話就通過該方法獲得執行個體
 *
 * @author HansChen
 */
@Component(modules = PersonServiceModule.class)
public interface PersonServiceComponent {

    /**
     * 查找activity中被@Inject注解的成員變量,并嘗試擷取相應的執行個體,把執行個體賦給activity的成員變量
     * 注意函數格式:傳回值為空、帶有一個參數
     */
    void inject(MainActivity activity);

    /**
     * Dagger會嘗試從Module中擷取PersonService執行個體,如果Module中不能擷取對應執行個體,則通過PersonService的構造方法new出一個執行個體
     * 注意函數格式:參數為空,傳回值非空
     */
    PersonService getPersonService();
}
           

這個接口被Component注解修飾,它裡面可以定義3種類型的方法:

  • 傳回值為空,有一個參數:查找參數中被@Inject注解的成員變量,并嘗試擷取相應的執行個體(通過Module的provide方法或@Inject注解的構造方法new出新的執行個體),把執行個體賦給參數的成員變量
  • 傳回值非空,參數為空:擷取相應執行個體并傳回
  • 傳回值是Component,參數是Moduld,通過該方法可以建立SubComponent執行個體

既然擷取執行個體的時候,有可能用到Module,那麼就必須為這個Component指定使用的Module是什麼。具體做法就是在@Component注解中指定modules。

定義好Component之後,Dagger會自動幫我們生成實作類,這就是Dagger強大的地方!生成的類名格式是:Dagger+Component名。

Component提供了2種方法,一個是注入式方法,一個是擷取執行個體方法。具體用什麼方法,就看個人需求了。一個Component其實也對應了一個依賴圖,因為Component使用哪個Module是确定不變的,依賴關系無非也就是跟Module和類的定義有關。一旦這些都确定下來了,在這個Component範圍内,依賴關系也就被确定下來了。額外再說一點,在Dagger1中,Component的功能是由

ObjectGraph

實作的,Component是用來代替它的。

Component定義好之後,build一下工程,Dagger就會自動為我們生成實作類了,就可以使用自動生成的實作類來進行依賴注入了。到現在為止,我們已經通過Dagger完成了依賴注入。可能看起來比正常方法麻煩得多,但是Dagger架構可以讓依賴的注入和配置獨立于元件之外,它幫助你專注在那些重要的功能類上。通過聲明依賴關系和指定規則建構整個應用程式。

熟悉完Dagger基本的使用之後,接下來我們來講解一些稍微進階一點的用法:

Dagger的進階使用

Components之間的關系

在Dagger中,Component之間可以有兩種關系:Subcomponents和Component dependencies。他們有什麼作用呢?比如在我們應用中,經常會有一些依賴我們在各個界面都使用得到,比如操作資料庫、比如網絡請求。假設我們有個ServerApi的接口,在頁面A、B、C都使用到了,那麼我們要在頁面A、B、C的Component裡面都能擷取到ServerApi的執行個體,但顯然,擷取ServerApi執行個體的方法都是一樣的,我們不想寫重複的代碼。于是我們可定義一個ApplicationComponent,在裡面傳回ServerApi執行個體,通過Component之間的關系便可以共享ApplicationComponent提供的依賴圖。

下面通過Android中的一個小栗子來說明Subcomponents和Component dependencies如何使用

dependencies

先說明下各個子產品之間的關系

首先,我們定義一個ApplicationComponent,它定義了一個方法,通過它來獲得ServerApi執行個體。ApplicationComponent還關聯了ApplicationModule,這個Module是ServerApi執行個體的提供者,注意,這個Moduld還可以傳回Context執行個體

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    ServerApi getServerApi();
}
           
@Module
public class ApplicationModule {

    private final Context mAppContext;

    ApplicationModule(Context context) {
        mAppContext = context.getApplicationContext();
    }

    @Provides
    Context provideAppContext() {
        return mAppContext;
    }

    @Provides
    ServerApi provideServerApi(Context context) {
        return new ServerApiImpl(context);
    }
}
           
public class DemoApplication extends Application {

    private ApplicationComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();
    }

    public ApplicationComponent getAppComponent() {
        return mAppComponent;
    }
}
           

MainActivity使用MVP模式,在MainPresenter裡面需要傳入一個ServerApi對象

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結
// 注意,這裡有個dependencies聲明
@Component(dependencies = ApplicationComponent.class, modules = MainPresenterModule.class)
public interface MainPresenterComponent {

    MainPresenter getMainPresenter();
}
           
@Module
public class MainPresenterModule {

    private MainView mMainView;

    public MainPresenterModule(MainView mainView) {
        this.mMainView = mainView;
    }

    @Provides
    MainView provideMainView() {
        return mMainView;
    }
}
           
public class MainPresenter {

    private MainView  mMainView;
    private ServerApi mServerApi;

    @Inject
    public MainPresenter(MainView mainView, ServerApi serverApi) {
        this.mMainView = mainView;
        this.mServerApi = serverApi;
    }
}
           

先抛開dependencies,我們分析這個這個依賴樹是怎麼樣的

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結

Component中getMainPresenter的目的很簡單,就是傳回MainPresenter,而MainPresenter又依賴MainView和ServerApi,MainView還好說,在MainPresenterModule中有provide方法,但是ServerApi呢?就像上面說的那樣,如果我們在這個Moduld中也添加相應的provide方法,那真是太麻煩了(當然,這樣做完全是可以實作的),是以我們依賴了ApplicationComponent,通過dependencies,在被依賴的Component暴露的對象,在子Component中是可見的。這個是什麼意思呢?意思有兩個:

  1. 被依賴Component接口暴露的對象,可以添加到依賴者的依賴圖中
  2. Component接口沒有暴露的對象,依賴者是不可見的

對于第一點應該比較好了解,就像這個栗子,MainPresenterComponent生成MainPresenter需要ServerApi,而ApplicationComponent中有接口暴露了ServerApi,是以MainPresenterComponent可以獲得ServerApi

對于第二點,假設MainPresenter還需要傳入一個Context對象,我們注意到,ApplicationModule是可以提供Context的,那MainPresenterComponent能不能通過ApplicationComponent擷取Context執行個體?答案是不行的,因為ApplicationComponent沒有暴露這個對象。想要擷取Context,除非ApplicationComponent中再添加一個getContext的方法。

他們之間的關系可以用下圖描述:

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結

Subcomponents

Subcomponents 實作方法一:

  • 先定義子 Component,使 用@Subcomponent 标注(不可同時再使用 @Component)
  • 父 Component 中定義獲得子 Component 的方法

讓我們對上面的栗子改造改造:

去除MainPresenterComponent的Component注解,改為Subcomponent:

@Subcomponent(modules = MainPresenterModule.class)
public interface MainPresenterComponent {

    void inject(MainActivity activity);

    MainPresenter getMainPresenter();
}
           

在ApplicationComponent中新增plus方法(名字可随意取),傳回值為MainPresenterComponent,參數為MainPresenterModule:

@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    MainPresenterComponent plus(MainPresenterModule module);
}
           

這樣,就建構了一個ApplicationComponent的子圖:MainPresenterComponent。子圖和dependencies的差別就是,子圖可以範圍父圖所有的依賴,也就是說,子圖需要的依賴,不再需要在父Component中暴露任何對象,可以直接通過父圖的Moduld提供!他們的關系變為了:

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結

這裡需要注意的是,以上代碼直接在父 Component 傳回子 Component 的形式,要求子 Component 依賴的 Module 必須包含一個無參構造函數,用以自動執行個體化。如果 Module 需要傳遞參數,則需要使用

@Subcomponent.builder

的方式,實作方法二實作步驟如下:

  • 在子 Component,定義一個接口或抽象類(通常定義為 Builder),使用 @Subcomponent.Builder 标注
    • 編寫傳回值為 Builder,方法的參數為需要傳入參數的 Module
    • 編寫傳回值為目前子 Component的 無參方法
  • 父 Component 中定義獲得子 Component.Builder 的方法

代碼如下:

@Module
public class TestModule {
    public TestModule(String test) {
    }

    @Provides
    AuthManager provideAuthManager() {
        return AuthManager.getInstance();
    }
}

@Subcomponent(modules = {TestModule.class})
public interface TestComponent {

    AuthManager getAuthManager();

    @Subcomponent.Builder
    interface Builder {

        Builder createBuilder(TestModule module);

        TestComponent build();
    }
}

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
    ...
    TestComponent.Builder testComponentBuilder();
}


// 使用
TestComponent testComponent = mApplicationComponent.testComponentBuilder().createBuilder(new TestModule("test")).build();
           

Binds注解

在Dagger2中,一般都是使用@provide方法注入接口。在Android 中,一般我們會這樣做,建立一個接口 Presenter 命名 為 HomePresenter

public interface HomePresenter {
   Observable<List<User>> loadUsers()
}
           

然後建立一個這個接口的執行個體:HomePresenterImp

public class HomePresenterImp implements HomePresenter {
    public HomePresenterImp(){
    }  
    @Override
    public Observable<List<User>> loadUsers(){
        //Return user list observable
    }
}
           

然後在 Module 中,提供執行個體化的 provide 方法:

@Module
public class HomeModule {
    @Provides
    public HomePresenter providesHomePresenter(){
        return new HomePresenterImp();
    }
}
           

但是,如果我們需要添加一個依賴到 presenter 叫 UserService,那就意味着,我們也要在 module 中添加一個 provide 方法提供這個 UserService,然後在 HomePresenterImp 類中加入一個 UserService 參數的構造方法。

有沒有覺得這種方法很麻煩呢?我們還可以用 @Binds 注解,如:

@Module
public abstract class HomeModule {
    // 變為 abstract 方法, 同時 Module 也必須聲明為 abstract, 傳入的參數必須為傳回參數的實作類
    // 當需要 HomePresenter 時,dagger 會自動執行個體化 HomePresenterImp 并傳回
    @Binds
    public abstract HomePresenter bindHomePresenter(HomePresenterImp homePresenterImp);
}
           

除了友善,使用 @Binds 注解還可以讓 dagger2 生成的代碼效率更高。但是需要注意的是,由于 Module 變為抽象類,Module 不能再包含非 static 的帶 @Provides 注解的方法。而且這時候,依賴此 Module 的 Component 也不需要傳入此 Module 執行個體了(也執行個體化不了,因為它是抽象的)。相當于此 Module 僅僅作為描述依賴關系的一個類

Scopes

Scopes可是非常的有用,Dagger2可以通過自定義注解限定注解作用域。@Singleton是被Dagger預先定義的作用域注解。

  • 沒有指定作用域的@Provides方法将會在每次注入的時候都建立新的對象
  • 一個沒有scope的component不可以依賴一個有scope的元件component
  • 子元件和父元件的scope不能相同
  • Module中provide方法的scope需要與Component的scope一緻

我們通常的ApplicationComponent都會使用Singleton注解,也就會是說我們如果自定義component必須有自己的scope。讀者到這裡,可能還不能了解Scopes的作用,我們先來看下預設提供的Singlton到底有什麼作用,然後再讨論Scopes的意義:

Singlton

Singletons是java提供的一個scope,我們來看看Singletons能做什麼事情。

為@Provides注釋的方法或可注入的類添加添加注解@Singlton,建構的這個對象圖表将使用唯一的對象執行個體,比如我們有個ServerApi

方法一:用@Singleton注解類:

@Singleton
public class ServerApi {

    @Inject
    public ServerApi() {
    }

    public boolean login(String username, String password) {
        return "HansChen".equals(username) && "123456".equals(password);
    }
}
           

方法二:用@Singleton注解Module的provide方法:

@Module
public class ApplicationModule {

    @Singleton
    @Provides
    ServerApi provideServerApi() {
        return new ServerApi();
    }
}
           

然後我們有個Component:

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    ServerApi getServerApi();
}
           

然後執行依賴注入:

public class MainActivity extends AppCompatActivity {

    @Inject
    ServerApi mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ApplicationComponent component = DaggerApplicationComponent.create();
        Log.d("Hans", component.getServerApi().toString());
        Log.d("Hans", component.getServerApi().toString());
        Log.d("Hans", component.getServerApi().toString());
    }
}
           

使用了以上兩種方法的任意一種,我們都會發現,通過component.getServerApi()獲得的執行個體都是同一個執行個體。不過要注意一點的是,如果類用@Singleton注解了,但Module中又存在一個provide方法是提供該類執行個體的,但provide方法沒有用@Singleton注解,那麼Component中擷取該執行個體就不是單例的,因為會優先查找Module的方法。

這個單例是相對于同一個Component而言的,不同的Component擷取到的執行個體将會是不一樣的。

自定義Scope

既然一個沒有scope的component不可以依賴一個有scope的元件component,那麼我們必然需要自定義scope來去注解自己的Component了,定義方法如下:

@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScoped {
}
           

定義出來的FragmentScoped在使用上和Singleton是一樣的,那它和Singleton除了是不一樣的注解之外,還有什麼不一樣呢?答案是沒有!我們自定義的scope和Singleton并沒有任何不一樣,不會因為Singleton是java自帶的注解就會有什麼差別。

那麼,這個scope的設定是為了什麼呢?

scope的作用

scope除了修飾provide方法可以讓我們獲得在同一個Component執行個體範圍内的單例之外,主要的作用就是對Component和Moduld的分層管理以及依賴邏輯的可讀性。

這裡借用一個網絡上的圖檔說明:

依賴注入利器 - Dagger ‡概述聲明需要注入的對象如何執行個體化出依賴?Module的使用Component的使用Dagger的進階使用小結

ApplicationComponent一般會用singleton注解,相對的,它的Module中provide方法也隻能用singleton注解。UserComponent是用UserSCope能直接使用ApplicationModule嗎?不能!因為他倆的scope不一緻,這就是這個設定帶來的好處,防止不同層級的元件混亂。另外,因為有了scope的存在,各種元件的作用和生命周期也變得可讀起來了

Lazy注入

有時可能會需要延遲擷取一個執行個體。對任何綁定的 T,可以建構一個 Lazy 來延遲執行個體化直至第一次調用 Lazy 的 get() 方法。注入之後,第一次get的時會執行個體化出 T,之後的調用都會擷取相同的執行個體。

public class MainActivity extends AppCompatActivity implements MainView {

    // 懶加載
    @Inject
    Lazy<MainPresenter> mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainPresenterComponent component = DaggerMainPresenterComponent.builder()
                                                                       .mainPresenterModule(new MainPresenterModule(this))
                                                                       .applicationComponent(((DemoApplication) getApplication()).getAppComponent())
                                                                       .build();
        component.inject(this);
        Log.d("Hans", mPresenter.get().toString()); // 執行個體化MainPresenter
        Log.d("Hans", mPresenter.get().toString()); // 跟上次擷取的執行個體是同一個執行個體
    }
}
           

Provider注入

跟Lazy注入不一樣的是,有時候我們希望每次調用get的時候,擷取到的執行個體都是不一樣的,這時候可以用Provider注入

public class MainActivity extends AppCompatActivity implements MainView {

    // Provider
    @Inject
    Provider<MainPresenter> mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainPresenterComponent component = DaggerMainPresenterComponent.builder()
                                                                       .mainPresenterModule(new MainPresenterModule(this))
                                                                       .applicationComponent(((DemoApplication) getApplication()).getAppComponent())
                                                                       .build();
        component.inject(this);
        Log.d("Hans", mPresenter.get().toString()); // 執行個體化MainPresenter
        Log.d("Hans", mPresenter.get().toString()); // 擷取新的MainPresenter執行個體
    }
}
           

Qualifiers注入

到目前為止,我們的demo裡,Moduld的provide傳回的對象都是不一樣的,但是下面這種情況就不好處理了:

@Module
public class ApplicationModule {

    ......

    // 傳回ServerApi執行個體
    @Provides
    ServerApi provideServerApiA(Context context) {
        return new ServerApiImplA(context);
    }

    // 傳回ServerApi執行個體
    @Provides
    ServerApi provideServerApiB(Context context) {
        return new ServerApiImplB(context);
    }
}
           

provideServerApiA和provideServerApiB傳回的都是ServerApi,Dagger是無法判斷用哪個provide方法的。這時候就需要添加Qualifiers了:

@Module
public class ApplicationModule {

    ......

    @Provides
    @Named("ServerApiImplA")
    ServerApi provideServerApiA(Context context) {
        return new ServerApiImplA(context);
    }

    @Provides
    @Named("ServerApiImplB")
    ServerApi provideServerApiB(Context context) {
        return new ServerApiImplB(context);
    }
}
           

通過這樣一個限定,就能區分出2個方法的差別了,當然,在使用過程中,也同樣要指明你用哪個name的執行個體,Dagger會根據你的name來選取對應的provide方法:

public class MainPresenter {

    private MainView  mMainView;
    private ServerApi mServerApi;

    @Inject
    public MainPresenter(MainView mainView, @Named("ServerApiImplA") ServerApi serverApi) {
        this.mMainView = mainView;
        this.mServerApi = serverApi;
    }
}
           

除了用Named注解,你也可以建立你自己的限定注解:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface YourQualifier {
    String value() default "";
}
           

編譯時驗證

Dagger 包含了一個注解處理器(annotation processor)來驗證子產品和注入。這個過程很嚴格而且會抛出錯誤,當有非法綁定或綁定不成功時。下面這個例子缺少了 Executor:

@Module
class DripCoffeeModule {
    @Provides Heater provideHeater(Executor executor) {
        return new CpuHeater(executor);
    }
}
           

當編譯時,javac 會拒絕綁定缺少的部分:

[ERROR] COMPILATION ERROR :
[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.
           

可以通過給方法 Executor 添加@Provides注解來解決這個問題,或者标記這個子產品是不完整的。不完整的子產品允許缺少依賴關系

@Module(complete = false)
class DripCoffeeModule {
    @Provides Heater provideHeater(Executor executor) {
        return new CpuHeater(executor);
    }
}
           

小結

第一次接觸用Dagger架構寫的代碼時候,如果不了解各種注解作用的時候,那真會有一臉懵逼的感覺,而且單看文章,其實還是很抽象,建議大家用Dagger寫個小demo玩玩,很快就上手了,這裡提供幾個使用Dagger的栗子,希望可以幫助大家上手Dagger

  • Dagger demo
  • 谷歌官方 MVP+Dagger2 Demo