Android 開源專案原始碼解析 -->Dagger 原始碼解析(十三)

Wei_Leng發表於2016-09-27

專案:Dagger

1. 功能介紹

1.1 Dagger

Dagger 是一款 Java 平臺的依賴注入庫,關於依賴注入,詳細見 依賴注入簡介

Java 的依賴注入庫中,最有名的應該屬 Google 的 Guice,Spring 也很有名,不過是專注於 J2EE 開發。Guice 的功能非常強大,但它是通過在執行時讀取註解來實現依賴注入的,依賴的生成和注入需要依靠 Java 的反射機制,這對於對效能非常敏感的 Android 來說是一個硬傷。基於此,Dagger 應運而生。

Dagger 同樣使用註解來實現依賴注入,但它利用 APT(Annotation Process Tool) 在編譯時生成輔助類,這些類繼承特定父類或實現特定介面,程式在執行時 Dagger 載入這些輔助類,呼叫相應介面完成依賴生成和注入。Dagger 對於程式的效能影響非常小,因此更加適用於 Android 應用的開發。

1.2 依賴注入相關概念

依賴(Dependency):如果在 Class A 中,有個屬性是 Class B 的例項,則稱 Class B 是 Class A 的依賴,本文中我們將 Class A 稱為宿主(Host),並且全文用 Host 表示;Class B 稱為依賴(Dependency),並且全文用 Dependency 表示。一個 Host 可能是另外一個類的 Dependency。

宿主(Host):如果 Class B 是 Class A 的 Dependency,則稱 Class A 是 Class B 的宿主(Host)。

依賴注入:如果 Class B 是 Class A 的 Dependency,B 的賦值不是寫死在了類或建構函式中,而是通過建構函式或其他函式的引數傳入,這種賦值方式我們稱之為依賴注入。

更詳細介紹可見 依賴注入簡介

1.3 Dagger 基本使用

本文將以一個簡單的“老闆和程式設計師” App 為例。

Activity 中有一個 Boss 類屬性,現在你想把一個 Boss 物件注入到這個 Activity 中,那麼有兩個問題需要解決:Boss 物件應該怎樣被生成 以及 Boss 物件怎樣被設定到 Activity 中。

(1). Boss 物件怎樣生成

在 Boss 類的建構函式前新增一個 @Inject 註解,Dagger 就會在需要獲取 Boss 物件時,呼叫這個被標記的建構函式,從而生成一個 Boss 物件。

public class Boss {
    ...

    @Inject
    public Boss() {
        ...
    }

    ...
}

需要注意的是,如果建構函式含有引數,Dagger 會在呼叫構造物件的時候先去獲取這些引數(不然誰來傳參?),所以你要保證它的引數也提供可被 Dagger 呼叫到的生成函式。Dagger 可呼叫的物件生成方式有兩種:一種是用 @Inject 修飾的建構函式,上面就是這種方式。另外一種是用 @Provides 修飾的函式,下面會講到。

(2). Boss 物件怎樣被設定到 Activity 中

通過 @Inject 註解了建構函式之後,在 Activity 中的 Boss 屬性宣告之前也新增 @Inject 註解。像這種在屬性前新增的 @Inject 註解的目的是告訴 Dagger 哪些屬性需要被注入。

public class MainActivity extends Activity {
    @Inject Boss boss;
    ...
}

最後,我們在合適的位置(例如 onCreate() 函式中)呼叫 ObjectGraph.inject() 函式,Dagger 就會自動呼叫上面 (1) 中的生成方法生成依賴的例項,並注入到當前物件(MainActivity)。

public class MainActivity extends Activity {
    @Inject Boss boss;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ObjectGraph.create(AppModule.class).inject(this);
    }
    ...
}

具體怎麼注入即設定的過程後面會詳細介紹,這裡簡單透露下,APT 會在 MainActivity 所在 package 下生成一個輔助類 MainActivity$$InjectAdapter,這個類有個 injectMembers() 函式,程式碼類似:

public void injectMembers(MainActivity paramMainActivity) {
    paramMainActivity.boss = ((Boss)boss.get());
    ……
}

上面我們已經通過 ObjectGraph.inject() 函式傳入了 paramMainActivity,並且 boss 屬性是 package 許可權,所以 Dagger 只需要呼叫這個輔助類的 injectMembers() 函式即可完成依賴注入,這裡的 boss.get() 會呼叫 Boss 的生成函式。
到此為止,使用 Dagger 的 @Inject 方式將一個 Boss 物件注入到 MainActivity 的流程就完成了。

(3). ObjectGraph.create(AppModule.class) 函式簡介

上面 onCreate() 函式中出現了兩個類:ObjectGraph 和 AppModule。其中 ObjectGraph 是由 Dagger 提供的類,可以簡單理解為一個依賴管理類,它的 create() 函式的引數是一個陣列,為所有需要用到的 Module(例如本例中的 AppModule)。AppModule 是一個自定義類,在 Dagger 中稱為Module,通過 @Module 註解進行標記,程式碼如下:

@Module(injects = MainActivity.class)
public class AppModule {
}

可以看到,AppModule 是一個空類,除了一行註解外沒有任何程式碼。
@Module 註解表示這個類是一個Module,Module 的作用是提供資訊,讓 ObjectGraph 知道哪些類物件需要被依賴注入,以及該怎麼生成某些依賴(這在下面會具體介紹)。例如,上面這段程式碼中宣告瞭需要依賴注入的類為 MainActivity。
需要在 Module 類中顯式宣告這些資訊看起來很麻煩,多此一舉的方式和 Dagger 的原理有關,下面會講到。

1.4 自定義依賴生成方式

(1). @Provides 修飾的生成函式

對建構函式進行註解是很好用的依賴物件生成方式,然而它並不適用於所有情況。例如:

  • 介面(Interface)是沒有建構函式的,當然就不能對建構函式進行註解
  • 第三方庫提供的類,我們無法修改原始碼,因此也不能註解它們的建構函式
  • 有些類需要提供統一的生成函式(一般會同時私有化建構函式)或需要動態選擇初始化的配置,而不是使用一個單一的建構函式

對於以上三種情況,可以使用 @Provides 註解來標記自定義的生成函式,從而被 Dagger 呼叫。形式如下:

@Provides
Coder provideCoder(Boss boss) {
    return new Coder(boss);
}

和建構函式一樣,@Provides 註解修飾的函式如果含有引數,它的所有引數也需要提供可被 Dagger 呼叫到的生成函式。
需要注意的是,所有 @Provides 註解的生成函式都需要在Module中定義實現,這就是上面提到的 Module 的作用之一——讓 ObjectGraph 知道怎麼生成某些依賴。

@Module
public class AppModule {
    @Provides
    Coder provideCoder(Boss boss) {
        return new Coder(boss);
    }
}
(2). @Inject 和 @Provide 兩種依賴生成方式區別

a. @Inject 用於注入可例項化的類,@Provides 可用於注入所有類
b. @Inject 可用於修飾屬性和建構函式,可用於任何非 Module 類,@Provides 只可用於用於修飾非建構函式,並且該函式必須在某個Module內部
c. @Inject 修飾的函式只能是建構函式,@Provides 修飾的函式必須以 provide 開頭

1.5 單例

Dagger 支援單例(事實上單例也是依賴注入最常用的場景),使用方式也很簡單:

// @Inject 註解建構函式的單例模式
@Singleton
public class Boss {
    ...

    @Inject
    public Boss() {
        ...
    }

    ...
}
// @Provides 註解函式的單例模式
@Provides
@Singleton
Coder provideCoder(Boss boss) {
    return new Coder(boss);
}

在相應函式新增 @Singleton 註解,依賴的物件就只會被初始化一次,之後的每次都會被直接注入相同的物件。

1.6 Qualifier(限定符)

如果有兩類程式設計師,他們的能力值 power 分別是 5 和 1000,應該怎樣讓 Dagger 對他們做出區分呢?使用 @Qualifier 註解即可。

(1). 建立一個 @Qualifier 註解,用於區分兩類程式設計師:

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

(2). 為這兩類程式設計師分別設定 @Provides 函式,並使用 @Qualifier 註解對他們做出不同的標記:

@Provides @Level("low") Coder provideLowLevelCoder() {
    Coder coder = new Coder();
    coder.setName("戰五渣");
    coder.setPower(5);
    return coder;
}

@Provides @Level("high") Coder provideHighLevelCoder() {
    Coder coder = new Coder();
    coder.setName("大神");
    coder.setPower(1000);
    return coder;
}

(3). 在宣告 @Inject 物件的時候,加上對應的 @Qualifier 註解。

@Inject @Level("low") Coder lowLevelCoder;
@Inject @Level("high") Coder highLevelCoder;

1.7 編譯時檢查

實質上,Dagger 會在編譯時對程式碼進行檢查,並在檢查不通過的時候報編譯錯誤,具體原因請看下面的詳細原理介紹。檢查內容主要有三點:
(1). 所有需要依賴注入的類,需要被顯式宣告在相應的Module中。
(2). 一個Module中所有 @Provides 函式的引數都必須在這個 Module 中提供相應的被 @Provides 修飾的函式,或者在 @Module 註解後新增 "complete = false" 註明這是一個不完整 Module,表示它依賴不屬於這個 Module 的其他 Denpendency。
(3). 一個Module中所有的 @Provides 函式都要被它宣告的注入物件所使用,或者在 @Module 註解後新增 "library = ture" 註明它含有對外的 Denpendency,可能被其他Module依賴。

1.8 Dagger 相關概念

Module:也叫 ModuleClass,指被 @Module 註解修飾的類,為 Dagger 提供需要依賴注入的 Host 資訊及一些 Dependency 的生成方式。

ModuleAdapter:指由 APT 根據 @Module 註解自動生成的類,父類是 Dagger 的 ModuleAdapter.java,與 ModuleClass 對應,以 ModuleClass 的 ClassName 加上 $$ModuleAdapter 命名,在 ModuleClass 的同一個 package 下。

Binding:指由 APT 根據 @Inject 註解和 @Provides 註解自動生成,最終繼承自 Binding.java 的類。為下面介紹的 DAG 圖中的一個節點,每個 Host 及依賴都是一個 Binding。

InjectAdapter:每個屬性或建構函式被 @Inject 修飾的類都會生成一個 繼承自 Binding.java 的子類,生成類以修飾類的 ClassName 加上 $$InjectAdapter 命名,在該類的同一個 package 下。

ProvidesAdapter:每個被 @Provides 修飾的生成函式都會生成一個繼承自 ProvidesBinding.java 的子類,ProvidesBinding.java 繼承自 Binding.java,生成類以 Provide 函式名首字母大寫加上 ProvidesAdapter 命名,是 Provide 函式所在 Module 對應生成的ModuleAdapter中的靜態內部類。
Binding 更具體資訊在下面會介紹。

Binding 安裝:指將 Binding 新增到 Binding 庫中。對 Dagger Linker.java 程式碼來說是將 Binding 新增到 Linker.bindings 屬性中,Linker.bindings 屬性表示某個 ObjectGraph 已安裝的所有 Binding。對於下面的 DAG 圖來說是將節點放到圖中,但尚未跟其他任何節點連線起來。

Binding 連線:把當前 Binding 和它內部依賴的 Binding 進行連線,即初始化這個 Binding 內部的所有 Binding,使它們可用。對 DAG 的角度說,就是把某個節點與其所依賴的各個節點連線起來。

2. 總體設計

2.1 概述

事實上,Dagger 這個庫的取名不僅僅來自它的本意“匕首”,同時也暗示了它的原理。Jake Wharton 在對 Dagger 的介紹中指出,Dagger 即 DAG-er,這裡的 DAG 即資料結構中的 DAG——有向無環圖(Directed Acyclic Graph)。也就是說,Dagger 是一個基於有向無環圖結構的依賴注入庫。

2.2 DAG(有向無環圖)

已經瞭解 DAG 的可以跳過這節。
DAG 是資料結構的一種。在一組節點中,每一個節點指向一個或多個節點,但不存在一條正向的鏈最終重新指向自己(即不存在環),這樣的結構稱為有向無環圖,即 DAG。

DAG

上圖中的資料結構就是一個有向無環圖。圖中一共存在 6 個節點和 7 個箭頭,但任何一個節點都無法從自己發射出的箭頭通過某條迴路重新指向自己。

2.3 Dagger 中依賴注入與 DAG 的關係

Dagger 的運作機制,是運用 APT(Annotation Process Tool) 在編譯時生成一些用於設定規則的程式碼,然後在執行時將這些規則進行動態組合 // TODO 不太理解意思,生成一個(或多個)DAG,然後由 DAG 來完成所有依賴的獲取,實現依賴注入。關於 DAG 究竟是怎樣一步步生成的,後面再講,這裡先說一下在 Dagger 中依賴注入與 DAG 的關係。

DAG-DI

我把前面那張圖的每個節點重新命名,得到了上圖。上圖代表了某個應用程式內的一整套依賴關係,其中每個箭頭都表示兩個類之間依賴關係,Host 和 Dependency 都是其中的一個節點。

可以看出,一個程式中的整套依賴關係其實就是一個 DAG。而實際上,Dagger 也是這麼做的:預先建立一個 DAG,然後在需要獲取物件的時候通過這個依賴關係圖來獲取到物件並返回,若獲取失敗則進行查詢,查詢到後再補充到 DAG 中。

Dagger 是支援傳遞依賴的。例如在上圖中,當需要獲取一個 CustomView,會首先獲取一個 DataHelper 作為獲取 CustomView 的必要引數;此時如果 DataHelper 還未初始化,則還要分別拿到 HttpHelper 和 Database 用來初始化 DataHelper;以此類推。

Dagger 不支援迴圈依賴,即依賴關係圖中不能出現環。原因很簡單,如果雞依賴蛋,蛋依賴雞,誰來創造世界?總有一個要先產生的。

2.4 工作流程

(1). 編譯時,通過 APT 檢視所有 java 檔案,並根據註解生成一些新的 java 檔案,即InjectAdapterProvidesAdapterModuleAdapter,這些檔案用於執行時輔助 DAG 的建立和完善。然後,將這些新生成的 java 檔案和專案原有的 java 檔案一併編譯成 class 檔案。
(2). 執行時,在 Application 或某個具體模組的初始化處,使用ObjectGraph類來載入部分依賴(實質上是利用編譯時生成的ModuleAdapters載入了所有的ProvidesBinding,後面會講到),形成一個不完整的依賴關係圖。
(3). 這個不完整的依賴關係圖生成之後,就可以呼叫ObjectGraph的相應函式來獲取例項和注入依賴了。實現依賴注入的函式有兩個:ObjectGraph.get(Class<T> type)函式,用於直接獲取物件;ObjectGraph.inject(T instance)函式,用於對指定物件進行屬性的注入。在這些獲取例項和注入依賴的過程中,如果用到了還未載入的依賴,程式會自動對它們進行載入(實質上是載入的編譯時生成的InjectAdapter)。在此過程中,記憶體中的 DAG 也被補充地越來越完整。

3. 流程圖

3.1 編譯時:

dagger_flow_chart_compile

3.2 執行時(初始化後):

dagger_flow_chart_runtime

4. 詳細設計

4.1 類關係圖

uml_types

上圖是 Dagger 整體框架最簡類關係圖。大致原理可以描述為:Linker通過Loader載入需要的Binding並把它們拼裝成合理的依賴關係圖 ObjectGraph,由ObjectGraph(其子類DaggerObjectGraph)最終實現依賴注入的管理。
ObjectGraph 是個抽象類,DaggerObjectGraph 是它目前唯一的子類,對 Dagger 的呼叫實際都是對 DaggerObjectGraph 的呼叫。

4.2 類功能詳細介紹

4.2.1 Binding.java —— 節點

Binding 是一個泛型抽象類,相當於依賴關係 DAG 圖中的節點,依賴關係 DAG 圖中得每一個節點都有一個由 APT 生成的繼承自 Binding 的類與之對應,而依賴關係 DAG 圖中的每一個節點與HostDependency一一對應,所以每個HostDependency必然有一個由 APT 生成的繼承自 Binding 的子類與之對應,我們先簡單的將這些生成類分為HostBindingDependencyBinding

(1). Binding.java 實現的介面

Binding.java 實現了兩個介面,第一個是 javax 的Provider介面,此介面提供了 get() 函式用於返回一個Dependency例項,當然也可以是Host例項。
第二個介面是 Dagger 中的MembersInjector介面,此介面提供了 injectMembers() 用來向Host物件中注入(即設定)Dependency
單純的DependencyBinding只要實現Provider介面,在 get() 函式中返回自己的例項即可。單純的HostBinding只要實現MembersInjector,在 injectMembers() 函式中呼叫DependencyBinding的 get() 函式得到依賴,然後對自己的依賴進行注入即可。如果一個類既是Host又是Dependency,則與它對應的Binding這兩個介面都需要實現。

(2). 生成的 Binding 程式碼示例

如下的 Host 和 Dependency 類

public class Host {
    @Inject Dependency dependency;
}

public class Dependency {
    @Inject
    public Dependency() {
        ……
    }
}

由 APT 生成的 Binding 應該類似

public final class Host$$InjectAdapter extends Binding<Host> implements MembersInjector<Host> {
    private Binding<Dependency> dependencyBinding;

    ……

    public void attach(Linker linker) {
        dependencyBinding = (Dependency$$InjectAdapter)linker.requestBinding(……);
    }

    public void injectMembers(Host host) {
        host.dependency = (Dependency)dependencyBinding.get();
    }
}
public final class Dependency$$InjectAdapter extends Binding<Dependency> implements Provider<Dependency> {

    ……

    public Dependency get() {
        return new Dependency();
    }
}

HostBinding指的是生成類 Host$$InjectAdapter,DependencyBinding指的是生成類 Dependency$$InjectAdapter,我們可以看到HostBinding的 attach 方法用於得到DependencyBinding的例項,然後在 injectMembers() 函式中通過呼叫這個例項的 get() 函式注入 Dependency,DependencyBinding 的 get() 函式就是呼叫Dependency的生成方法。

(3). Binding 分類

上面我們將生成的 Binding 子類簡單分為了HostBindingDependencyBinding,實際根據前面的注入方式我們知道依賴的生成方式有 @Inject 和 @Provides 兩種,對這兩種方式,Dagger 生成 Binding 子類的規則不同。

對於 @Inject 方式的注入,APT 會在Dependency同一個 package 下以Dependency的 ClassName 加上 $$InjectAdapter 為類名生成一個 Binding 子類。 對於 @Provides 方式的注入,@Provides 的生成函式必須寫在某個Module內部,與此 Module 對應的ModuleAdapter(Module$$ModuleAdapter)內部會有一個此 @Provides 方式對應的 Binding 子類,繼承自 Binding 的子類 ProvidesBinding,以 @Provides 函式名首字母大寫加上 ProvidesAdapter 命名。

所以實際自動生成的 Binding 子類我們可以分為三種:
第一種是Host對應的 Binding,本文中我們統一稱為HostBinding。這些HostBinding和被 @Module 修飾的Module injects 值中每個元素一一對應,他們提供 get()、injectMembers()、attach() 函式。

第二種是 Inject Dependecy 對應的 Binding 子類,本文中我們統一稱為InjectBinding。這些InjectBinding和所有含有 @Inject 修飾的建構函式的類一一對應,他們提供 get() 函式,不提供 injectMembers() 函式。如果它同時是個Host,也會提供 injectMembers() 函式。

第三種是 Provide Dependecy 對應的 Binding 子類,本文中我們統一稱為ProvidesBindingProvidesBinding和 @Module 類中的被 @Provides 修飾的函式一一對應,他們只提供 get() 函式,不提供 injectMembers() 函式。
上面三種 Binding 中,第一、二種會在 ObjectGraph.create 時載入進來,第三種在用的時候才會被動態載入。InjectBindingProvidesBinding統稱為DependencyBinding

Binding.java 的主要函式:

(1). get()

表示得到此 Binding 對應的DependencyInjectBinding會在 get() 中呼叫被 @Inject 修飾的建構函式,ProvidesBinding會在 get() 函式中呼叫被 @Provides 修飾的生成函式。

(2). injectMembers(T t)

表示向此 Binding 對應Host物件中注入依賴,這個函式的實現一般就是對被 @Inject 修飾的屬性進行賦值,值為DependencyBinding的 get() 函式返回值。

(3). attach(Linker linker)

表示HostBinding獲取依賴的 Binding 即DependencyBinding物件,對於 DAG 圖來說相當於把圖中兩個節點連線起來。對於DependencyBinding此函式為空。

(4). getDependencies(…)

表示HostBinding得到依賴的DependencyBinding),這個函式在對 DAG 圖進行問題檢測,比如迴圈依賴檢測時用到。

Binding.java 的主要屬性:

(1). provideKey

表示 Binding 所屬 Host 或 Dependency 的類名,是 Binding 唯一的 key,在 Linker 管理 Binding 時會用到,作為儲存所有 Binding 的 Map 的 key。對HostBinding值為 HostClassName.toString(),DependencyBinding值為 DependencyClassName.toString()。

(2). membersKey

// TODO。對HostBinding值為 members/ 加上 HostClassName.toString(),InjectBinding值為 members/ 加上 DependencyClassName.toString(),ProvidesBinding值為 null。ProvidesBinding值為 null,因為它預設就連線好了。

(3). requiredBy

表示這個 Binding 屬於誰,對HostBinding值為 HostClass.class,InjectBinding值為 DependencyClass.class,ProvidesBinding值為 ProvideMethodName.toString()。

(4). bits

表示 Binding 特性的標誌位,如是是否是單例(SINGLETON)、是否已連線(LINKED),是否被訪問(VISITING)、是否是可被其他 Module 依賴的 Library(LIBRARY)、是否依賴其他 Module 的 Binding(DEPENDED_ON)、是否不存在迴圈依賴(CYCLE_FREE)。

4.2.2 Linker.java —— 拼裝者

Linker 是 Dagger 最核心的大腦部分,它負責呼叫 Loader 載入 Binding,儲存並管理所有 Binding、呼叫 attach 方法初始化依賴的 DependencyBinding。對於 DAG 圖來說,Linker 就相當於一個管家,負責呼叫載入器載入節點到圖中、儲存並管理圖中所有的節點,連線圖中有依賴關係的節點,也就是 DAG 圖的拼裝。
Dagger 在執行時維護一個或多個Linker,Linker 與 ObjectGraph 一一對應。

Linker.java 的主要屬性:

(1). bindings

本文稱為 ObjectGraph 的 Binding 庫,表示 ObjectGraph 已安裝的所有 Binding,包括尚未連線的 Binding,對於 DAG 圖來說就是所有在圖中的節點,包括尚未跟其他任何節點連線起來的節點。
bindings 資料結構為 HashMap,value 就是具體的 Binding,key 是用來唯一確定 Binding 的字串,為 Binding.java 中的 provideKey 和 membersKey,具體形式是類名加上一個用於區分同型別的字首。這些 Binding 不僅包含已連線的,也包含未連線的。

表示待連線的 Binding 佇列,包含了所有待連線的 Binding。對於 DAG 圖來說就是所有在圖中但未和任何節點連線的節點。

連線(Link):從 DAG 的角度說,就是把某個節點與其所依賴的各個節點連線起來。而對於 Binding 來說,就是把當前 Binding 和它依賴的 Binding (ProvidesBinding)進行連線,即初始化這個 Binding 依賴的所有 Binding,使它們可用。

(3). attachSuccess

一個標誌,對於某個 Binding,在獲取它依賴的DependencyBinding時,如果他所有的DependencyBinding都已經新增到Binding庫中,attachSuccess 則為 true,否則為 false。如果為 false,表示該 Binding 尚未連線,新增到待連線佇列中,否則標記為已連線。

(4). linkedBindings

預設為 null,只有在 linkAll() 函式被呼叫後才有效,用於儲存所有已經連線的 Binding,同時也是一個標記,表示這個 ObjectGraph 已經不能被改變。

(5). Loader plugin

Loader 負責載入類,主要是載入 APT 生成的輔助類(InjectAdapter、ModuleAdapter)。

(6). errors

Linker.linkRequested() 執行過程中積累的 errors。

Linker.java 的主要函式:

(1). requestBinding(String key ……)

根據傳入的 key 返回一個 Binding。首先,會嘗試從 Bindings 變數(Binding 庫)中查詢這個 key,如果找到了,就將找到的 Binding 返回(如果找到後發現這個 Binding 還未連線,還需要它放進 toLink 中);如果找不到,說明需要的 Binding 是一個InjectBinding(因為另一種 Binding——ProvidesBinding 在初始化時就已經載入完畢了),就生成一個包含了這個 key 的DeferredBinding,並把它新增到 toLink(等待稍後載入)後返回 null。

(2). linkRequested()

迴圈取出 toLink 中的 Binding:
如果是個DeferredBinding載入相應的InjectAdapter後新增到toLinkbindings中,等待下次迴圈。
否則呼叫 attach 函式進行連線,對於DependencyBinding連線完成。對於HostBinding利用 attach() 函式獲取依賴的 Binding 即DependencyBinding物件,在獲取DependencyBinding的過程中呼叫 requestBinding() 函式查詢 Binding,不存在或未連線會繼續新增到 toLink 佇列中,如此迴圈。
直到所有依賴DependencyBinding被初始化結束。
對 DAG 圖來說就是一次廣度優先遍歷。

(3). installBindings(BindingsGroup toInstall)

安裝 Bindings,表示將 Binding 新增到 ObjectGraph 中,但尚未連線。對 DAG 圖來說就是就是將節點放到圖中,但尚未和任何其他節點連線。

(4). linkAll()

將 Binding 庫中所有未連線的 Binding 新增到 toLink 中,呼叫 linkRequested() 進行連線。

(5). fullyLinkedBindings()

返回已經全部連線的 Binding,如果沒有呼叫過 linkAll() 則返回 null

4.2.3 Loader.java —— 類載入器及物件生成器

Loader 是一個純工具類,它通過 ClassLoader 載入 APT 生成的ModuleAdapter類和InjectAdapter類,並初始化一個該類物件返回。另外,Loader 是一個抽象類,在執行時,Dagger 使用的是 Loader 的子類FailoverLoader

Loader.java 的主要函式:

(1). loadClass(ClassLoader classLoader, String name)

用指定的 ClassLoader 根據類名得到類,並快取起來。

(2). instantiate(String name, ClassLoader classLoader)

用指定的 ClassLoader 根據類名獲取類的例項。

(3). getModuleAdapter(Class moduleClass)

獲取指定的 Module 類所對應的 ModuleAdapter 例項。

(4). getAtInjectBinding(String key……)

根據 key 獲取 Inject Dependecy 對應的 InjectAdapter 例項。

(5). getStaticInjection(Class<?> injectedClass)

根據被注入的 Class 獲取對應的 StaticInjection 例項。

Loader.java 的主要變數:

(1). Memoizer<classloader, memoizer<string,="" class<?="" style="box-sizing: border-box;">>> caches

用來快取被初始化過的物件,是一個巢狀的 Memoizer 結構,Memoizer具體可看後面介紹,簡單理解就是巢狀的 HashMap,第一層 Key 是 ClassLoader,第二層 Key 是 ClassName,Value 是 Class 物件。

4.2.4 FailoverLoader.java

FailoverLoader 是 Loader 的一個子類,它載入類的策略是首先查詢 APT 生成的類,如果查詢失敗,則直接使用反射查詢和初始化。
FailoverLoader.java 的主要函式:

(1). getModuleAdapter(Class moduleClass)

獲取指定的 Module 類所對應的 ModuleAdapter 例項,如果在生成類中查詢失敗,則會呼叫 ReflectiveAtInjectBinding.create(type, mustHaveInjections) 通過反射直接初始化物件。

(2). getAtInjectBinding(String key……)

根據 key 獲取 Inject Dependecy 對應的 InjectAdapter 例項。如果在生成類中查詢失敗,則會呼叫 ReflectiveStaticInjection.create(injectedClass) 通過反射直接初始化物件。

(3). getStaticInjection(Class<?> injectedClass)

根據被注入的 Class 獲取對應的 StaticInjection 例項。

FailoverLoader.java 的主要變數:

(1). Memoizer<class<? style="box-sizing: border-box;">, ModuleAdapter<?>> loadedAdapters

用來快取初始化過的 ModuleAdapter 物件,是一個巢狀的 Memoizer 結構,具體可看下面介紹,簡單理解就是巢狀的 HashMap,第一層 Key 是 ClassLoader,第二層 Key 是 ClassName,Value 是 Class 物件。

4.2.5 ObjectGraph —— 管理者

ObjectGraph 是個抽象類,負責 Dagger 所有的業務邏輯,Dagger 最關鍵流程都是從這個類發起的,包括依賴關係圖建立、例項(依賴或宿主)獲取、依賴注入。
ObjectGraph 主要函式有:

(1). create(Object... modules)

這是個靜態的建構函式,用於返回一個 ObjectGraph 的例項,是使用 Dagger 呼叫的第一個函式。引數為 ModuleClass 物件,函式作用是根據 ModuleClass 構建一個依賴關係圖。此函式實現會直接呼叫

DaggerObjectGraph.makeGraph(null, new FailoverLoader(), modules)

返回一個DaggerObjectGraph物件,我們會在下面DaggerObjectGraph介紹中具體介紹實現過程。

(2). inject(T instance)

抽象函式,表示向某個 Host 物件中注入依賴。

(3). injectStatics()

抽象函式,表示向 ObjectGraph 中相關的 Host 注入靜態屬性。

(4). get(Class type)

抽象函式,表示得到某個物件的例項,多用於得到依賴的例項。

(5). plus(Object... modules)

抽象函式,表示返回一個新的包含當前 ObjectGraph 中所有 Binding 的 ObjectGraph。

(6). validate()

抽象函式,表示對當前 ObjectGraph 做檢查。

4.2.6 DaggerObjectGraph

DaggerObjectGraph 是 ObjectGraph 的靜態內部類,也是 ObjectGraph 目前唯一的子類。因為 ObjectGraph 的 create() 函式直接返回了 DaggerObjectGraph 物件,所以對 Dagger 的呼叫實際都是對 DaggerObjectGraph 的呼叫。
DaggerObjectGraph 主要屬性有:

(1). Map injectableTypes

記錄了所有需要被依賴注入的 Host 型別,以 Host 的 ClassName 加上一定規則字首(// TODO)做為 key,以其所對應的 Module 為 value。

(2). Map staticInjections

記錄了所有需要被靜態依賴注入的 Host 型別,以 Host 的 ClassName 加上一定規則字首(// TODO)做為 key,以其所對應的 Module 為 value。

(3). Linker linker

Linker 是 負責呼叫 Loader 載入 Binding,儲存並管理所有 Binding、呼叫 attach 方法初始化依賴的 DependencyBinding。具體見上面Linker.java介紹。

(4). Loader plugin

Loader 負責通過 ClassLoader 載入 APT 生成的ModuleAdapter類和InjectAdapter類。
PS:這個變數名叫 plugin,實際也說明了 Dagger 的一大優勢,就是它是支援 ClassLoader,這樣通過 Dagger 實現依賴注入的 Android 應用,外掛化時 Dagger 不會對其產生影響,而截止這個分析文件完成時,輕量級的 ButterKnife 都不支援多個 ClassLoader。
DaggerObjectGraph 主要函式有:

(1). makeGraph 函式

makeGraph 函式首先會通過 Modules.loadModules 函式得到所有的 ModuleAdapter;
然後遍歷所有 ModuleAdapter,將其中需要依賴注入的 Host 型別(injectableTypes)、需要靜態靜態注入的 Host 型別(staticInjections)、所有的 Binding(這裡是ProvidesBinding)都儲存下來,做為新的 DaggerObjectGraph 物件構造入參。另一種 Binding —— InjectBinding 會在需要用到的時候進行動態載入;
第三步新建 Linker 儲存上面的 Binding;
最後用這些變數一起構建新的 DaggerObjectGraph 物件。

(2). inject(T instance)

表示向某個 Host 物件中注入依賴。首先根據下面的 getInjectableTypeBinding() 函式查詢到 Host 對應的 InjectBinding,然後呼叫 injectMembers() 函式注入依賴,將依賴注入結束的 Host 返回。

(3). injectStatics()

表示向 ObjectGraph 中相關的 Host 注入靜態屬性。

(4). get(Class type)

表示得到某個物件的例項,多用於得到 Denpendency 的例項。首先根據下面的 getInjectableTypeBinding() 函式查詢到 Denpendency 對應的 Binding,然後呼叫 get() 返回該 Denpendency 例項。

(5). plus(Object... modules)

抽象函式,表示返回一個新的包含當前 ObjectGraph 中所有物件的 ObjectGraph。

(6). validate()

表示對當前 ObjectGraph 做檢查,首先會利用 Linker 查詢到所有節點並連線起來,然後呼叫 ProblemDetector 進行檢查。ProblemDetector 會在後面解釋作用。

(7). getInjectableTypeBinding(ClassLoader classLoader, String injectableKey, String key)

表示根據 key 得到某個 Binding。首先會從 ObjectGraph.injectableTypes 中得到其對應的 Module,然後通過 linker.requestBinding 查詢其對應的 Binding,若未查詢到的 Binding 或是尚未連線,則呼叫 linker.linkRequested() 得到 InjectBindng 並將其新增到 ObjectGraph 中,此時再次通過 linker.requestBinding 即可查詢到其對應的 Binding,返回即可。

(8). linkInjectableTypes()

查詢 injectableTypes 記錄的所有需要被依賴注入的 Host 型別對應的HostBinding

(9). linkStaticInjections()

查詢 staticInjections 記錄的所有需要被靜態依賴注入的 Host 型別對應的HostBinding

(10) linkEverything()

首先檢查是否連線過,沒有的話,則先呼叫 linkInjectableTypes() 和 linkStaticInjections() 將所有 HostBinding 新增到 Linker 中,然後呼叫 linker.linkAll() 進行全部 Binding 的依賴關聯。

4.2.7 BindingsGroup.java

內部主要一個 LinkedHashMap 變數,key 為需要需要依賴注入的類類全名,value 為其對應的 Binding 物件。

4.2.8 DeferredBinding.java

DeferredBinding 是 Binding 的一個子類,實際就是一個標記,在 linker.requestBinding 時候如果某個 Binding 不存在,則生成一個 DeferredBinding 新增到 toLink 佇列中,在 linker.linkRequested 如果碰到 DeferredBinding 則根據 key 獲得真正的 Binding 新增到 toLink 佇列中。

4.2.9 Keys.java

這是個 Key 相關的工具類。 getMembersKey(Class<?> key) 用於返回以 "members/" + keyClassName 的字串。

(1). boxIfPrimitive(Type type) 函式用於將原始型別轉換為複雜型別

// TODO 其他函式作用

4.2.10 Memoizer.java

一個小的快取抽象類,內部主要是一個用於儲存資料的 HashMap 屬性和兩個讀寫重入鎖。
Memoizer 主要函式有:

(1). create(K key)

需要子類實現的抽象函式,表示建立 Value 的方式。

(2). get(K key)

表示根據 key 從快取中得到 value,value 如果已經存在則直接返回,否則呼叫 create(K key) 函式新建 value,存入快取並返回。
Memoizer 主要用在 Loader 中,Loder 中包含一個巢狀的 Memoizer 變數,內外分別作為類和 ClassLoader 的快取。

4.2.11 ModuleAdapter.java

抽象類,APT 會為每一個被 @Module 修飾的類自動生成一個繼承自這個 ModuleAdapter 的子類。該子類會以 ModuleClass 的 ClassName 加上 $$ModuleAdapter 命名,在 ModuleClass 的同一個 package 下。
ModuleAdapter 主要屬性有:

(1). Class moduleClass

表示 ModuleAdapter 對應的 ModuleClass。

(2). injectableTypes

String 陣列,儲存需要依賴注入的類類名。為 @Module 註解的 injects 屬性值。

(3). staticInjections

Class 陣列,儲存有靜態屬性依賴需要注入的類。

(4). boolean overrides

表示某個 Module 的 @Provides 函式可以覆蓋其他 Module,建議只在測試以及開發模式使用。

(5). includes

表示 Module 由哪些其他類組成。

(6). boolean complete

表示這個 Module 需要的所有 Binding 是否可以互相提供依賴,即是否能組成一個完整的 DAG。True 表示可以,False 表示不可以。如果一個 Module 有外部依賴的 Bindings 則為 False。

(7). boolean library

表示這個 Module 是否提供對外的DenpendencyBinding,True 表示是,False 表示所有 Binding 僅被自己用到。

4.2.12 Modules.java

Modules.java 對外只有一個靜態的 loadModules 函式,作用是返回一組 Module 類所對應的一組 ModuleAdapter 例項。
該函式入參為 Loader 和一個 ModuleClass 物件陣列 seedModulesOrClasses,函式返回一個 HashMap,key 為 ModuleAdapter 物件,Value 為類似入參的 ModuleClass 物件,返回結果不僅包含入參 ModuleClass 及其對應的ModuleAdapter,也包含入參 ModuleClass 巢狀的 ModuleClass 及其對應的ModuleAdapter。

loadModules 的邏輯比較簡單,先通過 Loader.getModuleAdapter() 函式依次得到入參 seedModulesOrClasses 對應的 ModuleAdapter,然後查詢得到的 ModuleAdapter 巢狀的 ModuleClass 對應的 ModuleAdapter,ModuleAdapter 巢狀的 ModuleClass 都存放在 ModuleAdapter 的 includes 物件中,由 APT 在編譯時解析生成。

4.2.13 ProblemDetector.java

Binding 問題檢測。 ProblemDetector 主要函式有:

(1). detectCircularDependencies(Collection bindings)

檢測一組 Binding 是否存在迴圈依賴。

(2). detectUnusedBinding(Collection bindings)

檢測一組 Binding 中是否存在無用的 Binding,即既不依賴其他 Binding 也不被其他 Binding 依賴,對於 DAG 圖來說就是孤立的節點。

(3). detectProblems(Collection values)

檢測一組 Binding 是否存在問題,直接呼叫上面兩個函式檢測。這個函式會被 DaggerObjectGraph.validate() 呼叫進行檢測。

4.2.14 BuiltInBinding.java

ProvidesBinding 是 Binding 的子類,它的作用是在 attach 時就已經得到了最終的 Binding,get() 呼叫時直接返回即可。

4.2.15 LazyBinding.java

LazyBinding 是 Binding 的子類,它的作用是延遲例項化 Binding,呼叫它的 get() 函式時只是返回一個 Lazy 匿名內部類物件,只有呼叫這個物件的 get() 函式時才會 返回真正的 Dependency。

這樣做的一個好處就是如果,真正的 Binding 的生成很耗費效能,則可以在最開始時只生成輕量級的 LazyBinding,真正要使用時才初始化真正的 Binding。

4.2.16 ProvidesBinding.java

ProvidesBinding 是 Binding 的子類,對於 Provide 方式的注入,APT 會一個繼承自 ProvidesBinding.java 的子類,該生成類以 Provide 函式名首字母大寫加上 ProvidesAdapter 命名,是 Provide 函式所在 Module 對應生成的ModuleAdapter中的靜態內部類。
ProvidesBinding 主要屬性有:

(1). moduleClass

表示被 @Provides 修飾的函式所在的 Module 類名。

(2). methodName

表示被 @Provides 修飾的函式函式名。

4.2.17 SetBinding.java

SetBinding 是 Binding 的子類,它的不同處在於儲存了父 Binding,這樣就形成了一個連結串列。

4.2.18 StaticInjection.java

抽象類,APT 會為每一個被 @Inject 修飾的靜態屬性自動生成一個繼承自這個 StaticInjection 的子類。該子類會以屬性類的 ClassName 加上 $$StaticInjection 命名,在屬性類的同一個 package 下。

4.2.19 Lazy.java

Lazy 是一個介面,用來標記表示可以通過 get() 函式得到真正的物件。

4.2.20 MembersInjector.java

MembersInjector 是一個介面,提供了 injectMembers() 用來向Host物件中注入(即設定)DependencyHostDependency需要實現此介面。

4.2.21 Module.java

Module 是一個執行時註解,可以用來修飾類、介面、Enum。用來為 Dagger 提供需要依賴注入的 Host 資訊及一些 Dependency 的生成方式。Module 的屬性都在ModuleAdapter.java中介紹過,唯一沒有介紹過的 addsTo 表示 Module 可以作為哪些類的依賴。

被 @Module 註解修飾類(ModuleClass),APT 會生成一個以 ModuleClass 的 ClassName 加上 $$ModuleAdapter 命名,在 ModuleClass 的同一個 package 下的子類。

4.2.22 Provides.java

Provides 是一個註解,只可以用來修飾函式。
每個被 @Provides 修飾的生成函式都會生成一個繼承自 ProvidesBinding.java 的子類,ProvidesBinding.java 繼承自 Binding.java,生成類以 Provide 函式名首字母大寫加上 ProvidesAdapter 命名,是 Provide 函式所在 Module 對應生成的ModuleAdapter中的靜態內部類。 Binding 更具體資訊在下面會介紹。

4.2.23 ErrorHandler Interface

位於 Linker.java 內部,表示處理 Linker.linkRequested() 執行過程中的 error。

4.2.24 ThrowingErrorHandler.java

上面 ErrorHandler Interface 的實現類,將 errors 彙總後以 IllegalStateException 丟擲,為 Linker.linkRequested() 執行過程中積累的 errors 的預設處理方式。

5. 聊聊 Dagger 本身

Dagger 由於其自身的複雜性,其實是一個上手難度頗高的庫,難學會、難用好。但從功能上來講,它又是一個實用價值非常高的庫。而且即將釋出的 Dagger 2.0 已經被 Square 轉手交給了 Google 來開發和維護,從今以後它就是 Google 的官方庫了,那麼不論從官方支援方面還是從流行度上面, Dagger 都將會有一個很大的提升。關於 Dagger 的功能和用法,我會寫一篇文章詳細講述。在本文的最後,列兩個可能比較多人會問的問題和簡單的回答:

(1). Dagger 適合什麼樣的專案

Dagger 是一個依賴注入庫,而依賴注入是一種優秀的程式設計思想,它可以通過解耦專案來提升專案的可閱讀性、可擴充套件性和可維護性,並使得單元測試更為方便。因此,Dagger 適用於所有專案

(2). Dagger 適合什麼樣的個人和團隊

Dagger 適合有學習能力並且願意學習的個人和團隊。這裡要注意,如果你是開發團隊的負責人,在決定啟用 Dagger 之前一定要確認你的所有隊員(起碼是大部分隊員)都符合這樣的條件,否則 Dagger 可能會起反作用,畢竟——它不是 ButterKnife。


相關文章