架構設計知識梳理(1) Dagger2

1、概述

Dagger2依賴注入框架的好處:android

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

Dagger2相對於其它框架的優勢:bash

  • 編譯期生成代碼,有錯誤會在編譯期報出。
  • 錯誤可追蹤。
  • 易於調試。

Dagger2的缺點:app

  • 缺乏靈活性。
  • 沒有動態機制。

2、Dagger2的註解

Dagger2的註解主要有如下七類:框架

  • @Inject:這個註解有兩個做用:在目標類中標記成員變量告訴Dagger這個類型的變量須要一個實例對象;標記依賴類中的構造方法,告訴Dagger我能夠提供這種類型的依賴實例。
  • @Component:用來標記接口或者抽象類,也被稱爲注入器,是@Inject@Module的橋樑,全部的Component均可以經過它的modules知道它所提供的依賴範圍,一個Componet能夠依賴一個或多個Component,並拿到被依賴Component暴露出來的實例,Componenetdependencies屬性就是肯定依賴關係的實現。
  • @Module:用來標記類,通常類名以Module結尾,Module的主要做用是用來集中管理@Provides標記的方法,咱們定義一個被@Module註解的類,Dagger就會知道在哪裏找到依賴來知足建立類的實例,Module的一個重要特徵是被設計成區塊並能夠組合在一塊兒。
  • @Provides:對方法進行註解,而且這些方法都是有返回類型的,告訴Dagger咱們向如何建立並提供該類型的依賴實例(通常會在方法中new出實例),用@Provides標記的方法,推薦用provide做爲前綴。
  • @Qualifier:限定符,當一個類的類型不足以標示一個依賴的時候,咱們就能夠用這個註解,它會調用DataModule中方法來返回合適的依賴類實例。
  • @Scope:經過自定義註解來限定做用域,全部的對象都再也不須要知道怎麼管理它的實例,Dagger2中有一個默認的做用域註解@Singleton,一般用來標記在App整個生命週期內存活的實例,也能夠定義一個@PerActivity註解,用來代表生命週期要與Activity一致。
  • @SubComponent:若是咱們須要父組件所有的提供對象,咱們就能夠用包含方式,而不是用依賴方式,包含方式不須要父組件顯示顯露對象,就能夠拿到父組件所有對象,且SubComponent只須要在父Component接扣中聲明就能夠了。

3、Dagger2的簡單應用 - @Inject@Component

第一步:基礎配置,在build.gradle中添加相應的依賴:ide

//添加(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做爲目標類中須要實例化的成員對象,給其構造函數添加@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,並根據咱們第3步中聲明的Component,調用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接口或者抽象類的基礎上,添加Dagger前綴。
        DaggerOnlyInjectComponent.builder().build().inject(this);
    }

}
複製代碼

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

  • 只能標記一個構造方法,由於若是標記兩個以上,不知道要用哪個構造提供實例。
  • 不能標記其它咱們不能修改的類,例如第三方庫。
  • 若是用@Inject標記的構造函數若是有參數,那麼這個參數也須要其它地方提供依賴,而相似於String這些咱們不能修改的類,只能用@Module中的@Provides來提供實例了。

4、採用@Module來提供依賴

採用@Module標記的類提供依賴是常規套路,@Module標記的類起管理做用,真正提供依賴實例靠的是@Provides標記的帶返回類型的方法。 第一步:和上面相似,咱們定義一個依賴類,可是它的構造方法並不須要用@Inject標記:ui

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中只有一個無參的構造方法,不然咱們須要這樣調用:

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

複製代碼

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

  • 查找Module中是否存在建立該類型的方法(即@Component標記的接口中包含了@Module標記的Module類,若是沒有則直接查找@Inject對應的構造方法)。

  • 若是存在建立類方法,則查看該方法是否有參數

  • 若是不存在參數,直接初始化該類的實例,一次依賴注入到此結束。

  • 若是存在參數,則從步驟1開始初始化每一個參數。

  • 若是不存在建立類方法,則查找該類型的類中有@Inject標記的構造方法,查看構造方法是否有參數:

  • 若是不存在參數,則直接初始化該類實例,一次依賴注入到此結束。

  • 若是存在參數,則從步驟1開始初始化每一個參數。

6、@Qualifier限定符

Dagger中,有一個已經定義好的限定符,@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中對應的限定符,這樣Dagger就知道該用那個函數來生成目標類中的依賴類實例:

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());
    }
}
複製代碼

7、@Scope

@Scope的做用主要是在組織ComponentModule的時候起到一個提醒和管理的做用,在Dagger中,有一個默認的做用域@Singleton@Scope的做用是:Dagger2能夠經過自定義Scope註解,來限定經過ModuleInject方式建立的類的實例的生命週期可以與目標類的生命週期相同。Scope的真正做用在與Component的組織:

  • 更好的管理Component之間的組織方式,不論是依賴方式仍是包含方式,都有必要用自定的Scope註解標註這些Component,並且編譯器會檢查有依賴關係或包含關係的Component,若發現有Component沒有用自定義Scope註解,則會報錯。
  • 更好地管理ComponentModule之間地關係,編譯器會檢查Component管理的Module,若發現Component的自定義Scope註解與Module中的標註建立類實例方法的註解不同,就會報錯。
  • 提升程序的可讀性。

下面是一個使用@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的時候,咱們能夠發現打印出來的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());
    }
}
複製代碼

8、組織Component

Component有三種組織方式:

  • 依賴:一個Component依賴一個或多個Component,採用的是@Componentdependencies屬性。
  • 包含:這裏就用到了@SubComponent註解,用它來標記接口或者抽象類,表示它能夠被包乾。一個Component能夠包含一個或多個Component,並且被包含的Component還能夠繼續包含其它的Component
  • 繼承:用一個Component繼承另一個Component

9、Google官方框架分析

下面是Google官方框架的目錄結構:

Paste_Image.png
能夠看出,它把每一個界面都做爲一個獨立的包( addedittask、statistics、taskdetail、tasks),而數據、依賴類是其它的兩個包( 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代表它和TaskRepositoyModuleApplicationModule這兩個Module關聯。

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

TaskRpositotyModule提供了兩種類型的數據源對象,它們是用@Local@Remote來區分的:

@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關聯起來的,首先看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);
    }

}
複製代碼
相關文章
相關標籤/搜索