Dagger 2 是 Google 開源的一款依賴注入框架,它的前身是 square 的 Dagger 1,Dagger 2 在 Android 中有着較爲廣泛的應用。
Dagger 2 基於 Java 註解,採用 annotationProcessor(註解處理器) 在項目編譯時動態生成依賴注入需要的 Java 代碼,然後我們在合適的位置手動完成最終的依賴注入,而不是 Dagger 1 中基於反射的解決方案,所以在性能上是有保障的。
關於 annotationProcessor,有興趣的可以看我之前的一篇文章ButterKnife 原理解析
Dagger 2 雖然優秀,但對於新手上手使用可能還有一定的門檻,很容易就從入門到放棄子。這裏記錄下自己學習 Dagger 2 的過程、以及自己的理解。
一、依賴注入
依賴注入通俗的理解就是,當A類需要引用B類的對象時,將B類的對象傳入A類的過程就是依賴注入。依賴注入最重要的作用就是解耦,降低類之間的耦合,保證代碼的健壯性、可維護性、可擴展性。
常用的實現方式有構造函數、set方法、實現接口等。例如:
// 通過構造函數
public class A {
B b;
public A(B b) {
this.b = b;
}
}
// 通過set方法
public class A {
B b;
public void setB(B b) {
this.b = b;
}
}
但這些方式並不是足夠好的,如果有幾百個類需要引用B類的對象,意味着我們需要寫幾百個以B類爲參數的構造函數、或者set方法,所以隨着使用規模的增加會產生大量的模板代碼,這對後期的維護和修改帶來困難。而且在 Activity、Fragment 這樣的類中,用上邊這些方式似乎很難實現依賴注入。
面對這些問題,更好的 Dagger 2 來了。
二、基本使用
這裏以 Android 中的用法爲例,通過一個小例子來學會用 Dagger 2 完成依賴注入的基本流程。
1、添加依賴庫
在 app module 中的 build.gradle 中添加 Dagger 2 的依賴,有兩部分 Dagger 2 的核心庫,以及註解處理器:
implementation 'com.google.dagger:dagger:2.19'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
2、創建提供依賴的類
public class Cat {
@Inject
public Cat() {
}
@Override
public String toString() {
return "喵星人來了!";
}
}
我們定義了一個 Cat 類,並在它的無參構造函數上使用了 @Inject
註解,告訴 Dagger 2 這個無參構造函數可以被用來創建 Cat 對象,即依賴的提供方,至於原理後邊再說。
3、創建使用依賴對象的類
public class MainActivity extends AppCompatActivity {
@Inject
Cat cat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("cat", cat.toString());
}
}
在 MainActivity 中創建了一個 cat
變量,並加上了 @Inject
註解,來告訴 Dagger2 你要爲cat
賦值,即依賴注入。所以 MainActivity 就是依賴的需求方。
4、創建依賴注入組件
現在依賴的提供方 Cat 類有了,依賴的需求方 MainActivity 也有了,那麼如何將依賴的提供方和需求方關聯起來呢。類似於我們日常的購物,賣家和買家需要通過電商平臺的中介來完成供需信息的交換,完成最終的交易。
在 Dagger 2 中也有類似的中介,那就是Component,它負責完成依賴注入的過程,我們可以叫它依賴注入組件,大致的意思就是依賴需求方需要什麼類型的對象,依賴注入組件就從依賴提供方中拿到對應類型的對象,然後進行賦值。
所以我們需要使用@Component
註解來定義一個MainComponent
接口:
@Component
public interface MainComponent {
void inject(MainActivity activity);
}
inject
方法的參數是依賴需求方的類型,即例子中的 MainActivity,注意不可是基類的類型。
但這只是個接口,沒法直接使用呀,肯定還需要具體的依賴注入邏輯的,當然,但這些工作框架會幫我們做的。我們前邊說過 Dagger 2 採用了annotationProcessor技術,在項目編譯時動態生成依賴注入需要的 Java 代碼。
此時我們編譯項目,由於使用了@Component
註解,框架會自動幫我們生成一個MainComponent
接口的實現類DaggerMainComponent
,這個類可以在app\build\generated\source\apt\debug\包名
目錄下找到。
所以,DaggerMainComponent
就是真正的依賴注入組件,最後在 MainActivity 中添加最終完成依賴注入的代碼:
public class MainActivity extends AppCompatActivity {
@Inject
Cat cat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder()
.build()
.inject(this);
Log.e("cat", cat.toString());
}
然後運行項目,就可以看到如下的 Log,說明依賴注入成功了。
5、通用的依賴提供方
前邊我們的 Cat 類並沒有帶參的構造函數,可以直接在其無參的構造函數使用@Inject
註解,進而當做依賴提供方來提供對象。但實際的情況可能並沒有這麼簡單,可能構造函數需要參數,或者類是第三方提供的我們無法修改等,導致我們無法使用@Inject
,這些情況下就需要我們自定義依賴提供方了。
在 Dagger 2 中,如果一個類使用了@Module
註解,那麼這個類就可以用來提供依賴對象:
@Module
public class MainModule {
}
如果現在有如下的 Flower 類:
public class Flower {
private String name;
private String color;
public Flower(String name, String color) {
this.name = name;
this.color = color;
}
@Override
public String toString() {
return "Flower{" + "name='" + name + "', color='" + color + "'}";
}
}
如果要讓 MainModule 類可以提供 Flower 對象,可以做如下修改:
@Module
public class MainModule {
@Provides
public Flower provideRedRose() {
return new Flower("玫瑰", "紅色");
}
}
即聲明瞭一個provideRedFlower()
方法,並使用了@Provides
註解,這樣在底層 MainModule 類就可以調用provideRedRose()
方法,來提供對應的依賴對象了。
接下來通過如下方式將 MainModule 和 MainComponent 關聯起來,這樣依賴注入組件就知道從哪個依賴提供方取數據了:
@Component(modules = {MainModule.class})
public interface MainComponent {
void inject(MainActivity activity);
}
編譯項目後修改依賴注入配置代碼:
public class MainActivity extends AppCompatActivity {
@Inject
Flower flower;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder()
// 設置 MainModule 對象
.mainModule(new MainModule())
.build()
.inject(this);
Log.e("flower", flower.toString());
}
}
運行後可以看到如下 Log 信息:
所以通過 MainModule 類來提供依賴對象,更加的通用,這樣我們也可以去掉 Cat 類中的 @Inject
註解,進而通過 MainModule 類來提供依賴對象,更容易維護。
相信到這裏你已經掌握了 Dagger 2 的基本使用流程,知道了@Inject
、@Component
、@Module
、@Provides
這幾個註解的用途。
三、原理
相比機械性的記住這些註解以及使用流程,Dagger 2 背後的工作原理更加重要,這樣我們將明白各個模塊的職責,以及框架是如何將它們關聯起來幫助我們完成依賴注入的,明白了原理後使用 Dagger 2 就會更加得心應手了。
所以我們這裏先了解 Dagger 2 背後的基本原理,然後再學習其它內容。
以前邊的demo爲例,項目編譯後會在app\build\generated\source\apt\debug\包名
目錄下生成依賴注入的相關類,如下:
按照之前說法,DaggerMainComponent
是完成依賴注入的核心,所以從這個類開始分析,它的源碼如下:
public final class DaggerMainComponent implements MainComponent {
private MainModule mainModule;
private DaggerMainComponent(Builder builder) {
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.mainModule = builder.mainModule;
}
// 重寫inject方法,完成依賴注入
@Override
public void inject(MainActivity activity) {
injectMainActivity(activity);
}
// 完成依賴注入的重要方法
private MainActivity injectMainActivity(MainActivity instance) {
// 給 MainActivity 中的 cat 成員變量賦值
MainActivity_MembersInjector.injectCat(instance, new Cat());
// 給 MainActivity 中的 flower 成員變量賦值
MainActivity_MembersInjector.injectFlower1(
instance, MainModule_ProvideRedRoseFactory.proxyProvideRedRose(mainModule));
return instance;
}
public static final class Builder {
private MainModule mainModule;
private Builder() {}
// 完成DaggerMainComponent對象的創建
public MainComponent build() {
if (mainModule == null) {
this.mainModule = new MainModule();
}
return new DaggerMainComponent(this);
}
// 設置 mainModule 對象
public Builder mainModule(MainModule mainModule) {
this.mainModule = Preconditions.checkNotNull(mainModule);
return this;
}
}
}
典型的 Builder 構建模式,再結合之前 DaggerMainComponent 的用法來分析:
DaggerMainComponent.builder()
.mainModule(new MainModule())
.build()
.inject(this);
先通過builder()
方法創建一個 Builder 對象,再通過其mainModule()
方法設置mainModule
對象,接下來用 build()
方法就是創建DaggerMainComponent 對象,這樣它裏邊也有了一個mainModule
對象。
在 DaggerMainComponent 中還重寫了 MainComponent 接口的inject()
方法,裏邊調用的injectMainActivity()
方法是完成依賴注入的關鍵:
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectCat(instance, new Cat());
MainActivity_MembersInjector.injectFlower1(
instance, MainModule_ProvideRedRoseFactory.proxyProvideRedRose(mainModule));
return instance;
}
首先是調用MainActivity_MembersInjector
類的injectCat()
方法直接創建一個 Cat 對象,完成 MainActivity 中 cat 的賦值,injectCat()
方法聲明如下:
public static void injectCat(MainActivity instance, Cat cat) {
instance.cat = cat;
}
然後是調用injectFlower()
方法,完成 MainActivity 中 flower 的賦值,那麼 flower 對象的值從哪裏來呢?這裏調用了MainModule_ProvideRedRoseFactory
的proxyProvideRedRose()
方法:
public static Flower proxyProvideRedRose(MainModule instance) {
return Preconditions.checkNotNull(
instance.provideRedRose(), "Cannot return null from a non-@Nullable @Provides method");
}
裏邊最終是調用了我們在 MainModule 中聲明的 provideRedRose() 方法,所以在 DaggerMainComponent 內部是通過 MainActivity_MembersInjector 完成了最終的依賴注入。所以當在 Activity 中執行inject(this)
方法時,就是開始創建依賴對象,並完成注入工作。
到這裏整個依賴注入的流程就結束了,從源碼的角度來看,整個過程更像是醫生給患者注射藥物。我們可以把依賴注入組件 DaggerMainComponent 看做“醫生”,把 MainActivity_MembersInjector 看做“注射器”,MainModule 就是“藥物”,MainActivity 就是“患者”,醫生用注射器把藥物送到患者體內。
源碼中的細節,還是建議自己結合使用流程看一篇,可能有些疑問就恍然大悟了。
四、一些使用場景
Dagger 2 提供了一些好用的註解、模式來幫我們解決一些實際中遇到的問題。
1、@Named、@Qualifier
試想一下,如果 MainActivity 中需要注入多個 Flower 對象,例如紅玫瑰、白玫瑰、藍玫瑰,我們必然需要在 MainModule 中提供多個類似provideXXXRose()
的方法,但是問題來了,我們需要將 MainModule 中多個provide 方法和 MainActivity 中聲明的多個 Flower 變量對應起來呢,否則 Dagger 2 也不知道把那個 provide 方法的返回值賦給那個 Flower 變量。
爲了解決這個問題 Dagger 2 提供了@Named
註解,該註解需要設置一個屬性值來區分類型。來看如何使用,首先給 MainModule 中的方法加上@Named
:
@Module
public class MainModule {
@Named("red")
@Provides
public Flower provideRedRose() {
return new Flower("玫瑰", "紅色");
}
@Named("white")
@Provides
public Flower provideWhiteRose() {
return new Flower("玫瑰", "白色");
}
@Named("red")
就表示紅玫瑰,@Named("white")
就表示白玫瑰。我們還需要給 MainActivity 中的 Flower 變量加上對應的註解,這樣就可以把它們關聯起來了:
public class MainActivity extends AppCompatActivity {
@Named("red")
@Inject
Flower flower1;
@Named("white")
@Inject
Flower flower2;
}
可能有人要說了,@Named
需要屬性值,或者不想使用 Named 這個名字,也很簡單,那就需要使用@Qualifier
自定義註解,怎麼自定呢,先看@Named
是如何實現的:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}
照貓畫虎,我們可以自定義一個@QualifierBlue
註解,來區分藍玫瑰:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierBlue {
}
用法也類似:
@QualifierBlue
@Provides
public Flower provideBlueRose() {
return new Flower("玫瑰", "藍色");
}
@QualifierBlue
@Inject
Flower flower3;
2、@Singleton、@Scope
現在有一個 Book
類,在 Activity 中需要注入多個 Book 對象,但要求注入的是同一個 Book 對象,按照下面的寫法可以實現嗎?
@Module
public class DetailModule {
@Provides
public Book provideBook() {
return new Book("Kotlin 指南", 66.8f);
}
}
@Component(modules = {DetailModule.class})
public interface DetailComponent {
void inject(DetailActivity activity);
}
public class DetailActivity extends AppCompatActivity {
@Inject
Book book1;
@Inject
Book book2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detatil);
DaggerDetailComponent.builder()
.detailModule(new DetailModule())
.build()
.inject(this);
Log.e("book1", book1.toString());
Log.e("book2", book2.toString());
}
}
觀察輸入的 Log:
可以看到 book1、book2 並不是同一個對象,爲了實現這樣的功能, Dagger 2 提供了一個
@Singleton
註解,只需要給 Component 接口、Module 中的方法加上@Singleton
註解就可以了:
@Singleton
@Component(modules = {DetailModule.class})
public interface DetailComponent {
void inject(DetailActivity activity);
}
@Module
public class DetailModule {
@Singleton
@Provides
public Book provideBook() {
return new Book("Kotlin 指南", 66.8f);
}
}
再運行看效果:
我們的目標實現了,所以@Singleton
註解註解可以實現“單例”的效果,但這個單例只是局部的,只在單個 Activity 有效,就是說如果是兩個 Activity 想得到同一個對象,就失效了。關於這個問題我們後邊再討論。
同時我們還可以自定義類似@Singleton
的註解,爲什麼要自定義呢,可以是爲了代碼的可讀性,在不同的場景有更明確的指向性。例如可以定義一個 DetailActivityScope 註解,替換掉上邊的@Singleton
,這樣就能更好的體現 Book 對象在 DetailActivity 中是單例的。
要定義 DetailActivityScope 註解可以使用@Scope
註解,具體參考@Singleton
的實現:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface DetailActivityScope {
}
現在就可以用@DetailActivityScope
替換掉上邊的@Singleton
,最終的效果是一樣的。
3、dependencies
我們現在看上邊遺留的問題,通過 Dagger 2 如何實現全局單例,比如多個 Activity 如何共享同一個對象。在多個 Module 類裏定義相同的 provide 方法是行不通的,例如:
@Singleton
@Provides
public Book provideBook() {
return new Book("Flutter 指南", 68.8f);
}
因爲在多個 Activity 中 Component、 Module 類都已經不是同一個了,自然不能保證 Book 是同一個了。
Dagger 2 的@Component
註解可以設置dependencies
屬性,來依賴其它的 Component,這樣我們可以定義一套公共的 Component + Module,讓需要的 Component 來依賴公共的 Component,這樣問題就解決了。具體如何做,接下來詳細說。
首先定義公共的 Module 和 Component:
@Module
public class CommonModule {
@Singleton
@Provides
public Book provideBook() {
return new Book("Flutter 指南", 68.8f);
}
}
@Singleton
@Component(modules = {CommonModule.class})
public interface CommonComponent {
Book provideBook();
}
都使用了@Singleton
註解。注意,這個 CommonComponent 和我們之前的不太一樣,並沒有inject
方法,可以這樣理解,CommonComponent 並不是直接用來對應Activity 完成以依賴注入的,而是告訴依賴它的 Component 我可以給你提供什麼依賴對象,所以這裏定義了一個provideBook()
方法,和 CommonModule 中的方法對應。
接下來讓 MainComponent、ShareComponent 依賴 CommonComponent:
@CommonScope
@Component(modules = {MainModule.class}, dependencies = {CommonComponent.class})
public interface MainComponent {
void inject(MainActivity activity);
}
@CommonScope
@Component(dependencies = {CommonComponent.class})
public interface ShareComponent {
void inject(ShareActivity activity);
}
編譯項目後,就可以進行依賴注入的配置了:
DaggerMainComponent.builder()
.mainModule(new MainModule())
.commonComponent(???)
.build()
.inject(this);
問題來了,commonComponent()
方法需要一個 CommonComponent 對象,同理在 ShareActivity 中也需要一個 CommonComponent 對象,這裏我們要保證兩個 CommonComponent 對象是同一個,原因前邊已經說了,不同的 Component 對象自然無法提供相同的依賴對象。要獲得相同的 CommonComponent 對象,簡單的做法可以通過 Application 來實現,因爲在單進程的應用中,Application 的只被初始化一次,可以保證唯一性:
public class App extends Application {
private CommonComponent commonComponent;
@Override
public void onCreate() {
super.onCreate();
commonComponent = DaggerCommonComponent.builder().commonModule(new CommonModule()).build();
}
public CommonComponent getCommonComponent() {
return commonComponent;
}
}
別忘了在 AndroidManifest 中配置自定義的 Application。
所以 CommonComponent 對象的配置可以這樣寫:
commonComponent(((App) getApplication()).getCommonComponent())
我們在兩個 Activity 中分別依賴一個 Book 對象,並通過 Log 輸出:
是我們想要的結果。
4、@Subcomponent
@Subcomponent
是用來實現一個子依賴注入組件的,就是說使用@Subcomponent
註解的 Component 可以有一個父依賴注入組件,但這種父子關係並不是通過傳統的繼承方式實現的。自然的,子依賴注入組件會擁有父依賴注入組件的功能。
首先定義一個使用@Subcomponent
註解的依賴注入組件接口:
@Subcomponent(modules = {SubModule.class})
public interface MySubComponent {
void inject(SubActivity activity);
}
它依賴的 SubModule 爲:
@Module
public class SubModule {
@Provides
public Flower provideFlower() {
return new Flower("臘梅", "紅色");
}
}
如果我們想讓 DetailComponent 做爲 MySubComponent 的父組件,則需要在 DetailComponent 中定義一個返回 MySubComponent 的方法,方法參數爲其依賴的 Module 類型:
@DetailActivityScope
@Component(modules = {DetailModule.class})
public interface DetailComponent {
void inject(DetailActivity activity);
// 定義返回子組件的方法,參數爲子組件需要的module
MySubComponent getSubComponent(SubModule module);
}
到這裏我們的子組件就實現完成了,如何使用呢?子依賴注入注入組件是不能單獨直接使用的,因爲編譯後並不會生成類似DaggerMySubComponent
的輔助類,所以需要通過父組件來獲取,這也是我們需要在父組件中定義返回子組件方法的原因。具體的用法如下:
public class SubActivity extends AppCompatActivity {
@Inject
Book book;
@Inject
Flower flower;
public static void start(Context context) {
context.startActivity(new Intent(context, SubActivity.class));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub);
// 創建父組件對象
DetailComponent detailComponent = DaggerDetailComponent.builder().detailModule(new DetailModule()).build();
// 得到子組件,並完成依賴注入
detailComponent.getSubComponent(new SubModule()).inject(this);
Log.e("SubActivity-book", book.toString());
Log.e("flower", flower.toString());
}
}
我們並沒有在 SubModule 中定義提供 Flowerd 對象的方法,但是同過這種“繼承”,MySubComponent 就可以提供 Flower 對象了。運行結果如圖:
五、小結
Dagger 2 的主要內容就這些了,使用 Dagger 2 必然要編寫相關的輔助類、接口、使用各種註解,雖然沒有直接 new 一個對象或者傳統的依賴注入方式簡單,但 Dagger 2 帶來的是更好的代碼解耦,更有利於後期的擴展維護,對於那些需要長期維護的項目這一點是更加重要的。
其實,基於 Dagger 2 還有一個 DaggerAndroid 擴展庫,可以進一步簡化依賴注入的步驟,減少模板代碼,更適合 Android 開發。關於 DaggerAndroid 的用法和原理,下篇文章見。
示例代碼在 https://github.com/SheHuan/Dagger2Demo 的 master 分支上。