依賴注入利器 - Dagger ?

概述

在開發(fā)過程中秽荤,為了實現(xiàn)解耦纷责,我們經(jīng)常使用依賴注入哼丈,常見的依賴注入方式有:

  • 構(gòu)造方法注入:在構(gòu)造方法中把依賴作為參數(shù)傳遞進去
  • setter方法注入:添加setter方法边器,把依賴傳遞進去
  • 接口注入:把注入方法抽到一個接口中训枢,然后實現(xiàn)該接口,把依賴傳遞進去

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

public class PersonService implements DependencyInjecter {

    private PersonDao personDao;

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

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

    // 接口注入:實現(xiàn)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);

        // 創(chuàng)建PersonService的依賴:personDao
        PersonDao personDao = new PersonDaoImpl();
        // 通過構(gòu)造方法注入依賴
        mService = new PersonService(personDao);
    }
}

看起來還好是吧恒界?但現(xiàn)實情況下,依賴情況往往是比較復(fù)雜的袋坑,比如很可能我們的依賴關(guān)系如下圖:


2019-9-2-11-38-41.png

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);

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

MainActivity只是想使用PersonService而已婆誓,卻不得不關(guān)注PersonService的依賴是什么、PersonDaoImpl依賴的依賴是什么也颤,需要把整個依賴關(guān)系搞清楚才能使用PersonService洋幻。而且還有一個不好的地方,一旦依賴關(guān)系變更了翅娶,比如A不再依賴B了文留,那么就得修改所有創(chuàng)建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注入森书,讀者現(xiàn)在可先不關(guān)注里面做了什么操作
        DaggerPersonServiceComponent.create().inject(MainActivity.this);

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

之前創(chuàng)建A凛膏、B杨名、C、D猖毫、PersonDaoImpl等依賴的代碼全不見了台谍,只需要調(diào)用一個注入語句就全搞定了。調(diào)用了注入語句之后吁断,mService就可以正常使用了趁蕊,是不是挺方便呢?至于這句注入語句具體干了什么胯府,讀者現(xiàn)在可以先不管介衔,后面會有詳細(xì)說明,這里只是做一個使用演示而已骂因。

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

  1. 分析生成依賴關(guān)系圖寒波,如PersonService-->PersonDaoImpl-->A-->B-->C&D
  2. 根據(jù)依賴關(guān)系圖獲取相關(guān)依賴,比如依次創(chuàng)建D升熊、C俄烁、B、A级野、PersonDaoImpl页屠、PersonService的實例
  3. 把生成的PersonService實例傳遞給MainActivity的mService成員變量

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

聲明需要注入的對象

首先我們應(yīng)該用javax.inject.Inject去注解需要被自動注入的對象蓖柔,@Inject是Java標(biāo)準(zhǔn)的依賴注入(JSR-330)注解辰企。比如下面栗子中,需要注入的對象就是MainActivity的mService况鸣。這里有個要注意的地方牢贸,被@Inject注解的變量不能用private修飾

public class MainActivity extends AppCompatActivity {

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

如何實例化出依賴镐捧?

在執(zhí)行依賴注入的時候潜索,Dagger會查找@Inject注解的成員變量臭增,并嘗試獲取該類的實例,Dagger最直接的方式就是直接new出相應(yīng)的對象了竹习。實例化對象的時候誊抛,會調(diào)用對象的構(gòu)造方法,但假如有多個構(gòu)造方法整陌,具體用哪個構(gòu)造方法來實例化對象拗窃?Dagger肯定是不會幫我們“擅自做主”的,用哪個構(gòu)造方法來實例化對象應(yīng)該是由我們做主的蔓榄,所以我們需要給相應(yīng)的構(gòu)造方法添加@Inject注解并炮。
當(dāng)Dagger需要實例化該對象的時候,會調(diào)用@Inject注解的構(gòu)造方法來實例化對象:

public class PersonService implements DependencyInjecter {

    private PersonDao personDao;

    // 用@Inject注解甥郑,相當(dāng)于告訴Dagger需要實例化PersonService的時候逃魄,請調(diào)用這個構(gòu)造方法
    @Inject
    public PersonService(PersonDao personDao) {
        this.personDao = personDao;
    }

    ......
}

聰明的你應(yīng)該發(fā)現(xiàn)了,調(diào)用PersonService的構(gòu)造方法需要傳入PersonDao實例澜搅,所以要實例化PersonService伍俘,必須先要實例化PersonDao,Dagger會幫我們自動分析出這個依賴關(guān)系勉躺,并把它添加到依賴關(guān)系圖里面癌瘾!Dagger會嘗試先去實例化一個PersonDao,如果PersonDao又依賴于另外一個對象A,那么就先嘗試去實例化A......以此類推饵溅,是不是很像遞歸妨退?當(dāng)所有依賴都被實例化出來之后,我們的PersonService當(dāng)然也被構(gòu)造出來了蜕企。

問題又來了咬荷,如果PersonDao是一個接口呢?Dagger怎么知道這個接口應(yīng)該怎么實現(xiàn)轻掩?答案是不知道的幸乒,那么Dagger怎么實例化出一個接口出來?這個就是Module存在的意義之一了唇牧。關(guān)于Module的講解我們會在后面詳細(xì)說明罕扎,我們現(xiàn)在只要知道,Module里面會定義一些方法丐重,這些方法會返回我們的依賴腔召,就像:

@Module
public class PersonServiceModule {

    /**
     * 提供PersonDao接口實例
     */
    @Provides
    PersonDao providePersonDao(A a) {
        return new PersonDaoImpl(a);
    }
}

Dagger根據(jù)需求獲取一個實例的時候,并不總是通過new出來的弥臼,它會優(yōu)先查找Module
中是否有返回相應(yīng)實例的方法宴咧,如果有,就調(diào)用Module的方法來獲取實例径缅。

比如你用@Inject注解了一個成員變量掺栅,Dagger會查找Module中是否有用@Provides注解的烙肺,返回該類實例的方法,有的話就會調(diào)用provide方法來獲得實例氧卧,然后注入桃笙,如果沒有的話Dagger就會嘗試new出一個實例。就像我們現(xiàn)在這個栗子沙绝,PersonService依賴于PersonDao接口搏明,Dagger不能直接為我們new出一個接口,但我們可以提供一個Module闪檬,在Module中定義一個返回PersonDao接口實例的方法星著,這樣,Dagger就可以解決實例化PersonDao的問題了粗悯。

我們再梳理一下流程虚循,如果我們用@Inject注解了一個成員變量,并調(diào)用注入代碼之后样傍,Dagger會這樣處理:

  1. 查找Module中是否有用@Provides注解的横缔,返回該類實例的方法
  2. 如果有,就調(diào)用那個provide方法來獲得實例衫哥,然后注入
  3. 如果沒有茎刚,就嘗試調(diào)用相應(yīng)的類中被@Inject注解的構(gòu)造方法new出一個實例,然后注入
  4. 如果沒有一個構(gòu)造方法被@Inject注解撤逢,Dagger會因不能滿足依賴而出錯

所以假如一個變量被@Inject注解膛锭,要么在Module中提供provide方法獲取實例,要么該類提供一個被@Inject注解的構(gòu)造方法蚊荣,否則Dagger會出錯

Module的使用

一般而言泉沾,Dagger會獲取所有依賴的實例,比如當(dāng)需要一個TestBean的時候妇押,會通過new TestBean()創(chuàng)建實例并注入到類中。但是姓迅,以下情況會就不好處理了:

  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接口扩然,但卻可以調(diào)用Module的providePersonDao方法來獲得一個實例。providePersonDao方法需要傳入A的實例聋伦,那么這里也構(gòu)成了一個依賴關(guān)系圖夫偶。Dagger會先獲取A的實例界睁,然后把實例傳遞給providePersonDao方法。

Component的使用

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

  • Dagger怎么獲取實例:
    • 從Module的provide方法中獲取
    • 通過@Inject注解的構(gòu)造方法new出新的實例
  • Dagger會推導(dǎo)provide方法和構(gòu)造方法的參數(shù)翻斟,形成依賴圖,并“滿足”我們依賴圖的需求说铃,獲取依賴的實例

看樣子需要注入的依賴可以獲取了访惜,但是不是總覺得還有點“零碎”,整個流程還沒連貫起來腻扇?比如债热,Module既然是一個類,生成依賴圖的時候幼苛,怎么知道跟哪個Module掛鉤窒篱?即使最后生成了需要的實例,注入的“目的地”是哪里蚓峦?怎么才能把它注入到“目的地”舌剂?殘缺的這部分功能,正是Component提供的暑椰,Component起到了一個橋梁的作用霍转,貫通Module和注入目標(biāo)。我們來看看最開始那個例子一汽,我們是怎么進行依賴注入的:

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的時候已經(jīng)完成了注入
        mService = component.getPersonService();
    }
}

這個DaggerPersonServiceComponent是什么鬼岩喷?DaggerPersonServiceComponent其實是Dagger為我們自動生成的類,它實現(xiàn)了一個Component接口(這個接口是需要我們自己寫的)监憎,我們來看下它實現(xiàn)的接口長什么樣子:

/**
 * 指定PersonServiceModule纱意,當(dāng)需要獲取某實例的時候,會查找PersonServiceModule中是否有返回相應(yīng)類型的方法鲸阔,有的話就通過該方法獲得實例
 *
 * @author HansChen
 */
@Component(modules = PersonServiceModule.class)
public interface PersonServiceComponent {

    /**
     * 查找activity中被@Inject注解的成員變量偷霉,并嘗試獲取相應(yīng)的實例,把實例賦給activity的成員變量
     * 注意函數(shù)格式:返回值為空褐筛、帶有一個參數(shù)
     */
    void inject(MainActivity activity);

    /**
     * Dagger會嘗試從Module中獲取PersonService實例类少,如果Module中不能獲取對應(yīng)實例,則通過PersonService的構(gòu)造方法new出一個實例
     * 注意函數(shù)格式:參數(shù)為空渔扎,返回值非空
     */
    PersonService getPersonService();
}

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

  • 返回值為空,有一個參數(shù):查找參數(shù)中被@Inject注解的成員變量,并嘗試獲取相應(yīng)的實例(通過Module的provide方法或@Inject注解的構(gòu)造方法new出新的實例)残吩,把實例賦給參數(shù)的成員變量
  • 返回值非空财忽,參數(shù)為空:獲取相應(yīng)實例并返回
  • 返回值是Component,參數(shù)是Moduld世剖,通過該方法可以創(chuàng)建SubComponent實例

既然獲取實例的時候定罢,有可能用到Module,那么就必須為這個Component指定使用的Module是什么旁瘫。具體做法就是在@Component注解中指定modules祖凫。
定義好Component之后,Dagger會自動幫我們生成實現(xiàn)類酬凳,這就是Dagger強大的地方惠况!生成的類名格式是:Dagger+Component名。
Component提供了2種方法宁仔,一個是注入式方法稠屠,一個是獲取實例方法。具體用什么方法翎苫,就看個人需求了权埠。一個Component其實也對應(yīng)了一個依賴圖,因為Component使用哪個Module是確定不變的煎谍,依賴關(guān)系無非也就是跟Module和類的定義有關(guān)攘蔽。一旦這些都確定下來了,在這個Component范圍內(nèi)呐粘,依賴關(guān)系也就被確定下來了满俗。額外再說一點,在Dagger1中作岖,Component的功能是由ObjectGraph實現(xiàn)的唆垃,Component是用來代替它的。

Component定義好之后痘儡,build一下工程辕万,Dagger就會自動為我們生成實現(xiàn)類了,就可以使用自動生成的實現(xiàn)類來進行依賴注入了沉删。到現(xiàn)在為止蓄坏,我們已經(jīng)通過Dagger完成了依賴注入〕竽睿可能看起來比正常方法麻煩得多,但是Dagger框架可以讓依賴的注入和配置獨立于組件之外结蟋,它幫助你專注在那些重要的功能類上脯倚。通過聲明依賴關(guān)系和指定規(guī)則構(gòu)建整個應(yīng)用程序。

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

Dagger的進階使用

Components之間的關(guān)系

在Dagger中推正,Component之間可以有兩種關(guān)系:Subcomponents和Component dependencies恍涂。他們有什么作用呢?比如在我們應(yīng)用中植榕,經(jīng)常會有一些依賴我們在各個界面都使用得到再沧,比如操作數(shù)據(jù)庫、比如網(wǎng)絡(luò)請求尊残。假設(shè)我們有個ServerApi的接口炒瘸,在頁面A、B寝衫、C都使用到了顷扩,那么我們要在頁面A、B慰毅、C的Component里面都能獲取到ServerApi的實例隘截,但顯然,獲取ServerApi實例的方法都是一樣的汹胃,我們不想寫重復(fù)的代碼婶芭。于是我們可定義一個ApplicationComponent,在里面返回ServerApi實例着饥,通過Component之間的關(guān)系便可以共享ApplicationComponent提供的依賴圖犀农。

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

dependencies

先說明下各個模塊之間的關(guān)系
首先,我們定義一個ApplicationComponent贱勃,它定義了一個方法井赌,通過它來獲得ServerApi實例。ApplicationComponent還關(guān)聯(lián)了ApplicationModule贵扰,這個Module是ServerApi實例的提供者仇穗,注意,這個Moduld還可以返回Context實例


2019-9-2-11-41-18.png
@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對象


2019-9-2-11-43-13.png
// 注意粤剧,這里有個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,我們分析這個這個依賴樹是怎么樣的


2019-9-2-11-43-43.png

Component中g(shù)etMainPresenter的目的很簡單旱捧,就是返回MainPresenter脏里,而MainPresenter又依賴MainView和ServerApi,MainView還好說球切,在MainPresenterModule中有provide方法谷誓,但是ServerApi呢?就像上面說的那樣吨凑,如果我們在這個Moduld中也添加相應(yīng)的provide方法捍歪,那真是太麻煩了(當(dāng)然户辱,這樣做完全是可以實現(xiàn)的),所以我們依賴了ApplicationComponent糙臼,通過dependencies庐镐,在被依賴的Component暴露的對象,在子Component中是可見的变逃。這個是什么意思呢必逆?意思有兩個:

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

對于第一點應(yīng)該比較好理解名眉,就像這個栗子,MainPresenterComponent生成MainPresenter需要ServerApi锤窑,而ApplicationComponent中有接口暴露了ServerApi璧针,所以MainPresenterComponent可以獲得ServerApi
對于第二點,假設(shè)MainPresenter還需要傳入一個Context對象渊啰,我們注意到探橱,ApplicationModule是可以提供Context的,那MainPresenterComponent能不能通過ApplicationComponent獲取Context實例绘证?答案是不行的隧膏,因為ApplicationComponent沒有暴露這個對象。想要獲取Context嚷那,除非ApplicationComponent中再添加一個getContext的方法胞枕。

他們之間的關(guān)系可以用下圖描述:


2019-9-2-11-44-12.png

Subcomponents

Subcomponents 實現(xiàn)方法一:

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

讓我們對上面的栗子改造改造:
去除MainPresenterComponent的Component注解魏宽,改為Subcomponent:

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

    void inject(MainActivity activity);

    MainPresenter getMainPresenter();
}

在ApplicationComponent中新增plus方法(名字可隨意雀骸),返回值為MainPresenterComponent队询,參數(shù)為MainPresenterModule:

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

    MainPresenterComponent plus(MainPresenterModule module);
}

這樣派桩,就構(gòu)建了一個ApplicationComponent的子圖:MainPresenterComponent。子圖和dependencies的區(qū)別就是蚌斩,子圖可以范圍父圖所有的依賴铆惑,也就是說,子圖需要的依賴送膳,不再需要在父Component中暴露任何對象员魏,可以直接通過父圖的Moduld提供!他們的關(guān)系變?yōu)榱耍?/p>

2019-9-2-11-44-34.png

這里需要注意的是叠聋,以上代碼直接在父 Component 返回子 Component 的形式撕阎,要求子 Component 依賴的 Module 必須包含一個無參構(gòu)造函數(shù),用以自動實例化碌补。如果 Module 需要傳遞參數(shù)闻书,則需要使用 @Subcomponent.builder 的方式名斟,實現(xiàn)方法二實現(xiàn)步驟如下:

  • 在子 Component,定義一個接口或抽象類(通常定義為 Builder)魄眉,使用 @Subcomponent.Builder 標(biāo)注
    • 編寫返回值為 Builder,方法的參數(shù)為需要傳入?yún)?shù)的 Module
    • 編寫返回值為當(dāng)前子 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 中,一般我們會這樣做囊骤,創(chuàng)建一個接口 Presenter 命名 為 HomePresenter

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

然后創(chuàng)建一個這個接口的實例: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 參數(shù)的構(gòu)造方法滑蚯。
有沒有覺得這種方法很麻煩呢浪蹂?我們還可以用 @Binds 注解,如:

@Module
public abstract class HomeModule {
    // 變?yōu)?abstract 方法告材, 同時 Module 也必須聲明為 abstract坤次, 傳入的參數(shù)必須為返回參數(shù)的實現(xiàn)類
    // 當(dāng)需要 HomePresenter 時,dagger 會自動實例化 HomePresenterImp 并返回
    @Binds
    public abstract HomePresenter bindHomePresenter(HomePresenterImp homePresenterImp);
}

除了方便斥赋,使用 @Binds 注解還可以讓 dagger2 生成的代碼效率更高缰猴。但是需要注意的是,由于 Module 變?yōu)槌橄箢惏探#琈odule 不能再包含非 static 的帶 @Provides 注解的方法滑绒。而且這時候,依賴此 Module 的 Component 也不需要傳入此 Module 實例了(也實例化不了隘膘,因為它是抽象的)疑故。相當(dāng)于此 Module 僅僅作為描述依賴關(guān)系的一個類

Scopes

Scopes可是非常的有用,Dagger2可以通過自定義注解限定注解作用域棘幸。@Singleton是被Dagger預(yù)先定義的作用域注解焰扳。

  • 沒有指定作用域的@Provides方法將會在每次注入的時候都創(chuàng)建新的對象
  • 一個沒有scope的component不可以依賴一個有scope的組件component
  • 子組件和父組件的scope不能相同
  • Module中provide方法的scope需要與Component的scope一致

我們通常的ApplicationComponent都會使用Singleton注解,也就會是說我們?nèi)绻远xcomponent必須有自己的scope误续。讀者到這里吨悍,可能還不能理解Scopes的作用,我們先來看下默認(rèn)提供的Singlton到底有什么作用蹋嵌,然后再討論Scopes的意義:

Singlton

Singletons是java提供的一個scope育瓜,我們來看看Singletons能做什么事情。
為@Provides注釋的方法或可注入的類添加添加注解@Singlton栽烂,構(gòu)建的這個對象圖表將使用唯一的對象實例躏仇,比如我們有個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();
}

然后執(zhí)行依賴注入:

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());
    }
}

使用了以上兩種方法的任意一種恋脚,我們都會發(fā)現(xiàn),通過component.getServerApi()獲得的實例都是同一個實例焰手。不過要注意一點的是糟描,如果類用@Singleton注解了,但Module中又存在一個provide方法是提供該類實例的书妻,但provide方法沒有用@Singleton注解船响,那么Component中獲取該實例就不是單例的,因為會優(yōu)先查找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自帶的注解就會有什么區(qū)別抵窒。

那么,這個scope的設(shè)定是為了什么呢叠骑?

scope的作用

scope除了修飾provide方法可以讓我們獲得在同一個Component實例范圍內(nèi)的單例之外李皇,主要的作用就是對Component和Moduld的分層管理以及依賴邏輯的可讀性。
這里借用一個網(wǎng)絡(luò)上的圖片說明:


2019-9-2-11-44-58.png

ApplicationComponent一般會用singleton注解宙枷,相對的掉房,它的Module中provide方法也只能用singleton注解。UserComponent是用UserSCope能直接使用ApplicationModule嗎慰丛?不能卓囚!因為他倆的scope不一致,這就是這個設(shè)定帶來的好處诅病,防止不同層級的組件混亂哪亿。另外,因為有了scope的存在贤笆,各種組件的作用和生命周期也變得可讀起來了

Lazy注入

有時可能會需要延遲獲取一個實例蝇棉。對任何綁定的 T,可以構(gòu)建一個 Lazy<T> 來延遲實例化直至第一次調(diào)用 Lazy<T> 的 get() 方法芥永。注入之后篡殷,第一次get的時會實例化出 T,之后的調(diào)用都會獲取相同的實例埋涧。

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注入不一樣的是板辽,有時候我們希望每次調(diào)用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);
    }
}

通過這樣一個限定麻捻,就能區(qū)分出2個方法的區(qū)別了,當(dāng)然呀袱,在使用過程中,也同樣要指明你用哪個name的實例,Dagger會根據(jù)你的name來選取對應(yīng)的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注解郑叠,你也可以創(chuàng)建你自己的限定注解:

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

編譯時驗證

Dagger 包含了一個注解處理器(annotation processor)來驗證模塊和注入夜赵。這個過程很嚴(yán)格而且會拋出錯誤,當(dāng)有非法綁定或綁定不成功時乡革。下面這個例子缺少了 Executor:

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

當(dāng)編譯時寇僧,javac 會拒絕綁定缺少的部分:

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

可以通過給方法 Executor 添加@Provides注解來解決這個問題,或者標(biāo)記這個模塊是不完整的沸版。不完整的模塊允許缺少依賴關(guān)系

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

小結(jié)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钓觉,一起剝皮案震驚了整個濱河市茴肥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荡灾,老刑警劉巖瓤狐,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異批幌,居然都是意外死亡础锐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門逼裆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郁稍,“玉大人,你說我怎么就攤上這事胜宇∫” “怎么了恢着?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長财破。 經(jīng)常有香客問我掰派,道長,這世上最難降的妖魔是什么左痢? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任靡羡,我火速辦了婚禮,結(jié)果婚禮上俊性,老公的妹妹穿的比我還像新娘略步。我一直安慰自己,他們只是感情好定页,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布趟薄。 她就那樣靜靜地躺著,像睡著了一般典徊。 火紅的嫁衣襯著肌膚如雪杭煎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天卒落,我揣著相機與錄音羡铲,去河邊找鬼。 笑死儡毕,一個胖子當(dāng)著我的面吹牛也切,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妥曲,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼贾费,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了檐盟?” 一聲冷哼從身側(cè)響起褂萧,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葵萎,沒想到半個月后导犹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡羡忘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年谎痢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卷雕。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡节猿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滨嘱,我是刑警寧澤峰鄙,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站太雨,受9級特大地震影響吟榴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜囊扳,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一吩翻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锥咸,春花似錦狭瞎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缔刹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劣针,已是汗流浹背校镐。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捺典,地道東北人鸟廓。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像襟己,于是被迫代替她去往敵國和親引谜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容