架構(gòu)設(shè)計知識梳理(1) - Dagger2

一、概述

Dagger2依賴注入框架的好處:

  • 依賴的注入和配置獨立于組件之外
  • 依賴對象是在一個獨立装哆、不耦合的地方初始化,當初始化方式改變的時候修改的代碼少。
  • 依賴注入使得單元測試更加簡單瞎嬉。

Dagger2相對于其它框架的優(yōu)點:

  • 編譯期生成代碼,有錯誤會在編譯期報出厚柳。
  • 錯誤可追蹤氧枣。
  • 易于調(diào)試。

Dagger2的缺點:

  • 缺少靈活性别垮。
  • 沒有動態(tài)機制挑胸。

二、Dagger2的注解

Dagger2的注解主要有以下七類:

  • @Inject:這個注解有兩個作用:在目標類中標記成員變量告訴Dagger這個類型的變量需要一個實例對象宰闰;標記依賴類中的構(gòu)造方法茬贵,告訴Dagger我可以提供這種類型的依賴實例。
  • @Component:用來標記接口或者抽象類移袍,也被稱為注入器解藻,是@Inject@Module的橋梁,所有的Component都可以通過它的modules知道它所提供的依賴范圍葡盗,一個Componet可以依賴一個或多個Component螟左,并拿到被依賴Component暴露出來的實例啡浊,Componenetdependencies屬性就是確定依賴關(guān)系的實現(xiàn)。
  • @Module:用來標記類胶背,一般類名以Module結(jié)尾巷嚣,Module的主要作用是用來集中管理@Provides標記的方法,我們定義一個被@Module注解的類钳吟,Dagger就會知道在哪里找到依賴來滿足創(chuàng)建類的實例廷粒,Module的一個重要特征是被設(shè)計成區(qū)塊并可以組合在一起。
  • @Provides:對方法進行注解红且,并且這些方法都是有返回類型的坝茎,告訴Dagger我們向如何創(chuàng)建并提供該類型的依賴實例(一般會在方法中new出實例),用@Provides標記的方法暇番,推薦用provide作為前綴嗤放。
  • @Qualifier:限定符,當一個類的類型不足以標示一個依賴的時候壁酬,我們就可以用這個注解次酌,它會調(diào)用DataModule中方法來返回合適的依賴類實例。
  • @Scope:通過自定義注解來限定作用域舆乔,所有的對象都不再需要知道怎么管理它的實例和措,Dagger2中有一個默認的作用域注解@Singleton,通常用來標記在App整個生命周期內(nèi)存活的實例蜕煌,也可以定義一個@PerActivity注解派阱,用來表明生命周期要與Activity一致。
  • @SubComponent:如果我們需要父組件全部的提供對象斜纪,我們就可以用包含方式贫母,而不是用依賴方式,包含方式不需要父組件顯示顯露對象盒刚,就可以拿到父組件全部對象腺劣,且SubComponent只需要在父Component接扣中聲明就可以了。

三因块、Dagger2的簡單應(yīng)用 - @Inject@Component

第一步:基礎(chǔ)配置橘原,在build.gradle中添加相應(yīng)的依賴:

//添加(1)
apply plugin: 'com.neenbedankt.android-apt'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        //添加(2)
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.0"

    defaultConfig {
        applicationId "com.demo.zejun.repodragger2"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.0'
    //添加(3)
    apt 'com.google.dagger:dagger-compiler:2.0'
    //添加(4)
    compile 'com.google.dagger:dagger:2.0'
}

第二步:User作為目標類中需要實例化的成員對象,給其構(gòu)造函數(shù)添加@Inject標簽:

public class User {

    public String name;

    @Inject
    public User() {
        name = "lizejun";
    }

    public String getName() {
        return name;
    }
}

第三步:聲明Component

@Component()
public interface OnlyInjectComponent {
    void inject(AnnotationActivity annotationActivity);
}

第四步:在目標類中添加注解@Inject涡上,并根據(jù)我們第3步中聲明的Component趾断,調(diào)用DaggerXXX方法來進行注入:

public class AnnotationActivity extends AppCompatActivity {

    @Inject
    public User mUser;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dragger2);
        //在第3步聲明的Component接口或者抽象類的基礎(chǔ)上,添加Dagger前綴吩愧。
        DaggerOnlyInjectComponent.builder().build().inject(this);
    }

}

上面這個例子有兩個缺點:

  • 只能標記一個構(gòu)造方法芋酌,因為如果標記兩個以上,不知道要用哪一個構(gòu)造提供實例雁佳。
  • 不能標記其它我們不能修改的類脐帝,例如第三方庫同云。
  • 如果用@Inject標記的構(gòu)造函數(shù)如果有參數(shù),那么這個參數(shù)也需要其它地方提供依賴堵腹,而類似于String這些我們不能修改的類炸站,只能用@Module中的@Provides來提供實例了。

四疚顷、采用@Module來提供依賴

采用@Module標記的類提供依賴是常規(guī)套路旱易,@Module標記的類起管理作用,真正提供依賴實例靠的是@Provides標記的帶返回類型的方法荡含。
第一步:和上面類似,我們定義一個依賴類届垫,但是它的構(gòu)造方法并不需要用@Inject標記:

public class Person {

    private String name;

    public Person() {
        this.name = "lizejun";
    }

    public String getName() {
        return name;
    }
}

第二步:我們需要定義一個@Module來管理這些依賴類的實例:

@Module
public class PersonDataModule {

    @Provides
    public Person providePerson() {
        return new Person();
    }
}

第三步:定義一個@Component释液,它指向上面定義的@Module

@Component(modules = {PersonDataModule.class})
public interface PersonInjectComponent {
    void inject(PersonInjectActivity injectActivity);
}

第四步:在目標類中進行依賴注入

public class PersonInjectActivity extends Activity {

    @Inject
    Person mPerson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerPersonInjectComponent.create().inject(this);
        System.out.println("Person name=" + mPerson.getName());
    }
}

這里注入的方式有兩種,一種是像上面這樣的装处,它適合于PersonDataModule中只有一個無參的構(gòu)造方法误债,否則我們需要這樣調(diào)用:

DaggerPersonInjectComponent.builder().personDataModule(new PersonDataModule()).build().inject(this);

五、初始化依賴實例的步驟

  • 查找Module中是否存在創(chuàng)建該類型的方法(即@Component標記的接口中包含了@Module標記的Module類妄迁,如果沒有則直接查找@Inject對應(yīng)的構(gòu)造方法)寝蹈。

  • 如果存在創(chuàng)建類方法,則查看該方法是否有參數(shù)

  • 如果不存在參數(shù)登淘,直接初始化該類的實例箫老,一次依賴注入到此結(jié)束。

  • 如果存在參數(shù)黔州,則從步驟1開始初始化每個參數(shù)耍鬓。

  • 如果不存在創(chuàng)建類方法,則查找該類型的類中有@Inject標記的構(gòu)造方法流妻,查看構(gòu)造方法是否有參數(shù):

  • 如果不存在參數(shù)牲蜀,則直接初始化該類實例,一次依賴注入到此結(jié)束绅这。

  • 如果存在參數(shù)涣达,則從步驟1開始初始化每個參數(shù)。

六证薇、@Qualifier限定符

Dagger中度苔,有一個已經(jīng)定義好的限定符,@Name浑度,下面我們也自己定義一個限定符:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PeopleThreeQualifier {}

第一步:和前面類似林螃,我們先定義一個需要實例化的依賴類:

public class People {

    private int count;

    public People() {
        count = 0;
    }

    public People(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }
}

第二步:我定義一個DataModule,和前面不同的是俺泣,在它的provideXXX方法的注解中疗认,我們添加了@Name(xxx)和自定義的注解PeopleThreePeople

@Module
public class PeopleDataModule {

    @Provides
    @Named("Five People")
    People provideFivePeople() {
        return new People(5);
    }

    @Provides
    @Named("Ten People")
    People provideTenPeople() {
        return new People(10);
    }

    @Provides
    @PeopleThreeQualifier
    People provideThreePeople() {
        return new People(3);
    }
}

第三步:定義Component

@Component(modules = PeopleDataModule.class)
public interface PeopleInjectComponent {
    void inject(PeopleInjectActivity peopleInjectActivity);
}

第四步:在目標類中進行依賴注入完残,在提供@Inject注解時,我們還需要聲明和PeopleDataModule中對應(yīng)的限定符横漏,這樣Dagger就知道該用那個函數(shù)來生成目標類中的依賴類實例:

public class PeopleInjectActivity extends Activity {

    @Inject
    @Named("Five People")
    People mFivePeople;

    @Inject
    @Named("Ten People")
    People mTenPeople;

    @Inject
    @PeopleThreeQualifier
    People mThreePeople;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerPeopleInjectComponent.builder().peopleDataModule(new PeopleDataModule()).build().inject(this);
        System.out.println("Five People=" + mFivePeople.getCount() + ",Ten People=" + mTenPeople.getCount() + ", Three People=" + mThreePeople.getCount());
    }
}

七谨设、@Scope

@Scope的作用主要是在組織ComponentModule的時候起到一個提醒和管理的作用,在Dagger中缎浇,有一個默認的作用域@Singleton扎拣。
@Scope的作用是:Dagger2可以通過自定義Scope注解,來限定通過ModuleInject方式創(chuàng)建的類的實例的生命周期能夠與目標類的生命周期相同素跺。Scope的真正作用在與Component的組織:

  • 更好的管理Component之間的組織方式二蓝,不管是依賴方式還是包含方式,都有必要用自定的Scope注解標注這些Component指厌,而且編譯器會檢查有依賴關(guān)系或包含關(guān)系的Component刊愚,若發(fā)現(xiàn)有Component沒有用自定義Scope注解,則會報錯踩验。
  • 更好地管理ComponentModule之間地關(guān)系鸥诽,編譯器會檢查Component管理的Module,若發(fā)現(xiàn)Component的自定義Scope注解與Module中的標注創(chuàng)建類實例方法的注解不一樣箕憾,就會報錯牡借。
  • 提高程序的可讀性。

下面是一個使用@Singleton的例子:
第一步:定義需要實例化的類:

public class AnSingleObject {

    private String objectId;

    public AnSingleObject() {
        objectId = toString();
    }

    public String getObjectId() {
        return objectId;
    }
}

第二步:定義DataModule袭异,在它的provideXXX方法钠龙,提供了@Singletion注解:

@Module
public class AnSingleObjectDataModule {

    @Provides
    @Singleton
    AnSingleObject provideAnSingleObject() {
        return new AnSingleObject();
    }
}

第三步:定義Component,和前面不同的是御铃,需要給這個Component添加@Singleton注解:

@Component(modules = {AnSingleObjectDataModule.class})
@Singleton
public abstract class AnSingleObjectInjectComponent {

    private static AnSingleObjectInjectComponent sInstance;

    public abstract void inject(AnSingleObjectInjectActivity anSingleObjectInjectActivity);

    public static AnSingleObjectInjectComponent getInstance() {
        if (sInstance == null) {
            sInstance = DaggerAnSingleObjectInjectComponent.create();
        }
        return sInstance;
    }
}

第四步:在目標類中進行依賴注入俊鱼,每次啟動Activity的時候,我們可以發(fā)現(xiàn)打印出來的hash值都是相同的:

public class AnSingleObjectInjectActivity extends Activity {

    @Inject
    AnSingleObject object;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnSingleObjectInjectComponent.getInstance().inject(this);
        System.out.println("AnSingleObject id=" + object.getObjectId());
    }
}

八畅买、組織Component

Component有三種組織方式:

  • 依賴:一個Component依賴一個或多個Component并闲,采用的是@Componentdependencies屬性。
  • 包含:這里就用到了@SubComponent注解谷羞,用它來標記接口或者抽象類帝火,表示它可以被包干。一個Component可以包含一個或多個Component湃缎,而且被包含的Component還可以繼續(xù)包含其它的Component犀填。
  • 繼承:用一個Component繼承另外一個Component

九嗓违、Google官方框架分析

下面是Google官方框架的目錄結(jié)構(gòu):

Paste_Image.png

可以看出九巡,它把每個界面都作為一個獨立的包(addedittask、statistics蹂季、taskdetail冕广、tasks)疏日,而數(shù)據(jù)、依賴類是其它的兩個包(data撒汉、util)沟优,我們先從ToDoApplication開始分析:

public class ToDoApplication extends Application {

    private TasksRepositoryComponent mRepositoryComponent;
    
    @Override
    public void onCreate() {
        super.onCreate();
        mRepositoryComponent = DaggerTasksRepositoryComponent.builder()
                .applicationModule(new ApplicationModule((getApplicationContext())))
                .build();
    }
    
    public TasksRepositoryComponent getTasksRepositoryComponent() {
        return mRepositoryComponent;
    }
}

ToDoApplication中,我們實例化了一個變量TasksRepositoryComponent睬辐,它相當于是項目中所有其它Component的管理者挠阁,它被聲明為@Singleton的,即在App的生命周期中只存在一個溯饵,同時用@Component表明它和TaskRepositoyModule侵俗、ApplicationModule這兩個Module關(guān)聯(lián)。

@Singleton
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
    TasksRepository getTasksRepository();
}

TaskRpositotyModule提供了兩種類型的數(shù)據(jù)源對象丰刊,它們是用@Local隘谣、@Remote來區(qū)分的:

@Module
public class TasksRepositoryModule {

    @Singleton
    @Provides
    @Local
    TasksDataSource provideTasksLocalDataSource(Context context) {
        return new TasksLocalDataSource(context);
    }

    @Singleton
    @Provides
    @Remote
    TasksDataSource provideTasksRemoteDataSource() {
        return new FakeTasksRemoteDataSource();
    }

}

接下來再看一下ApplicationModule

@Module
public final class ApplicationModule {

    private final Context mContext;

    ApplicationModule(Context context) {
        mContext = context;
    }

    @Provides
    Context provideContext() {
        return mContext;
    }
}

下面我們用一個比較簡單的界面來看一下TasksRepositoryComponent是怎么和其它的Component關(guān)聯(lián)起來的,首先看StatisticsActivity

public class StatisticsActivity extends AppCompatActivity {

    private DrawerLayout mDrawerLayout;

    @Inject 
    StatisticsPresenter mStatiticsPresenter; //依靠Dagger實例化的對象藻三。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.statistics_act);
        //初始化Fragment界面洪橘。
        StatisticsFragment statisticsFragment = (StatisticsFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (statisticsFragment == null) {
            statisticsFragment = StatisticsFragment.newInstance();
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
                    statisticsFragment, R.id.contentFrame);
        }

        DaggerStatisticsComponent.builder()
            .statisticsPresenterModule(new StatisticsPresenterModule(statisticsFragment))
            .tasksRepositoryComponent(((ToDoApplication) getApplication())
            .getTasksRepositoryComponent())
            .build().inject(this);
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末跪者,一起剝皮案震驚了整個濱河市棵帽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渣玲,老刑警劉巖逗概,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忘衍,居然都是意外死亡逾苫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門枚钓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铅搓,“玉大人,你說我怎么就攤上這事搀捷⌒顷” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵嫩舟,是天一觀的道長氢烘。 經(jīng)常有香客問我,道長家厌,這世上最難降的妖魔是什么播玖? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮饭于,結(jié)果婚禮上蜀踏,老公的妹妹穿的比我還像新娘维蒙。我一直安慰自己,他們只是感情好脓斩,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布木西。 她就那樣靜靜地躺著,像睡著了一般随静。 火紅的嫁衣襯著肌膚如雪八千。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天燎猛,我揣著相機與錄音恋捆,去河邊找鬼。 笑死重绷,一個胖子當著我的面吹牛沸停,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昭卓,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼愤钾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了候醒?” 一聲冷哼從身側(cè)響起能颁,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倒淫,沒想到半個月后伙菊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡敌土,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年镜硕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片返干。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡兴枯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矩欠,到底是詐尸還是另有隱情财剖,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布晚顷,位于F島的核電站峰伙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏该默。R本人自食惡果不足惜瞳氓,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匣摘,春花似錦店诗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赠叼,卻和暖如春擦囊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嘴办。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工瞬场, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涧郊。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓贯被,卻偏偏與公主長得像,于是被迫代替她去往敵國和親妆艘。 傳聞我的和親對象是個殘疾皇子彤灶,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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