一、概述
Dagger2
依賴注入框架的好處:
- 依賴的注入和配置獨立於元件之外
- 依賴物件是在一個獨立、不耦合的地方初始化,當初始化方式改變的時候修改的程式碼少。
- 依賴注入使得單元測試更加簡單。
Dagger2
相對於其它框架的優點:
- 編譯期生成程式碼,有錯誤會在編譯期報出。
- 錯誤可追蹤。
- 易於除錯。
Dagger2
的缺點:
- 缺少靈活性。
- 沒有動態機制。
二、Dagger2
的註解
Dagger2
的註解主要有以下七類:
@Inject
:這個註解有兩個作用:在目標類中標記成員變數告訴Dagger
這個型別的變數需要一個例項物件;標記依賴類中的構造方法,告訴Dagger
我可以提供這種型別的依賴例項。@Component
:用來標記介面或者抽象類,也被稱為注入器,是@Inject
和@Module
的橋樑,所有的Component
都可以通過它的modules
知道它所提供的依賴範圍,一個Componet
可以依賴一個或多個Component
,並拿到被依賴Component
暴露出來的例項,Componenet
的dependencies
屬性就是確定依賴關係的實現。@Module
:用來標記類,一般類名以Module
結尾,Module
的主要作用是用來集中管理@Provides
標記的方法,我們定義一個被@Module
註解的類,Dagger
就會知道在哪裡找到依賴來滿足建立類的例項,Module
的一個重要特徵是被設計成區塊並可以組合在一起。@Provides
:對方法進行註解,並且這些方法都是有返回型別的,告訴Dagger
我們向如何建立並提供該型別的依賴例項(一般會在方法中new
出例項),用@Provides
標記的方法,推薦用provide
作為字首。@Qualifier
:限定符,當一個類的型別不足以標示一個依賴的時候,我們就可以用這個註解,它會呼叫DataModule
中方法來返回合適的依賴類例項。@Scope
:通過自定義註解來限定作用域,所有的物件都不再需要知道怎麼管理它的例項,Dagger2
中有一個預設的作用域註解@Singleton
,通常用來標記在App
整個生命週期記憶體活的例項,也可以定義一個@PerActivity
註解,用來表明生命週期要與Activity
一致。@SubComponent
:如果我們需要父元件全部的提供物件,我們就可以用包含方式,而不是用依賴方式,包含方式不需要父元件顯示顯露物件,就可以拿到父元件全部物件,且SubComponent
只需要在父Component
接扣中宣告就可以了。
三、Dagger2
的簡單應用 - @Inject
和@Component
第一步:基礎配置,在build.gradle
中新增相應的依賴:
//新增(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);
}
}
複製程式碼
上面這個例子有兩個缺點:
- 只能標記一個構造方法,因為如果標記兩個以上,不知道要用哪一個構造提供例項。
- 不能標記其它我們不能修改的類,例如第三方庫。
- 如果用
@Inject
標記的建構函式如果有引數,那麼這個引數也需要其它地方提供依賴,而類似於String
這些我們不能修改的類,只能用@Module
中的@Provides
來提供例項了。
四、採用@Module
來提供依賴
採用@Module
標記的類提供依賴是常規套路,@Module
標記的類起管理作用,真正提供依賴例項靠的是@Provides
標記的帶返回型別的方法。
第一步:和上面類似,我們定義一個依賴類,但是它的構造方法並不需要用@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
中只有一個無參的構造方法,否則我們需要這樣呼叫:
DaggerPersonInjectComponent.builder().personDataModule(new PersonDataModule()).build().inject(this);
複製程式碼
五、初始化依賴例項的步驟
-
查詢
Module
中是否存在建立該型別的方法(即@Component
標記的介面中包含了@Module
標記的Module
類,如果沒有則直接查詢@Inject
對應的構造方法)。 -
如果存在建立類方法,則檢視該方法是否有引數
-
如果不存在引數,直接初始化該類的例項,一次依賴注入到此結束。
-
如果存在引數,則從步驟1開始初始化每個引數。
-
如果不存在建立類方法,則查詢該型別的類中有
@Inject
標記的構造方法,檢視構造方法是否有引數: -
如果不存在引數,則直接初始化該類例項,一次依賴注入到此結束。
-
如果存在引數,則從步驟1開始初始化每個引數。
六、@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());
}
}
複製程式碼
七、@Scope
@Scope
的作用主要是在組織Component
和Module
的時候起到一個提醒和管理的作用,在Dagger
中,有一個預設的作用域@Singleton
。
@Scope
的作用是:Dagger2
可以通過自定義Scope
註解,來限定通過Module
和Inject
方式建立的類的例項的生命週期能夠與目標類的生命週期相同。Scope
的真正作用在與Component
的組織:
- 更好的管理
Component
之間的組織方式,不管是依賴方式還是包含方式,都有必要用自定的Scope
註解標註這些Component
,而且編譯器會檢查有依賴關係或包含關係的Component
,若發現有Component
沒有用自定義Scope
註解,則會報錯。 - 更好地管理
Component
與Module
之間地關係,編譯器會檢查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());
}
}
複製程式碼
八、組織Component
Component
有三種組織方式:
- 依賴:一個
Component
依賴一個或多個Component
,採用的是@Component
的dependencies
屬性。 - 包含:這裡就用到了
@SubComponent
註解,用它來標記介面或者抽象類,表示它可以被包乾。一個Component
可以包含一個或多個Component
,而且被包含的Component
還可以繼續包含其它的Component
。 - 繼承:用一個
Component
繼承另外一個Component
。
九、Google
官方框架分析
下面是Google
官方框架的目錄結構:
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
表明它和TaskRepositoyModule
、ApplicationModule
這兩個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);
}
}
複製程式碼