Android依賴注入Dagger的使用和原始碼解析(上篇)
一、基本概念
依賴注入(DI)和控制反轉(IOC):
依賴注入是從應用程式的角度在描述,可以把依賴注入描述完整點:應用程式依賴容器建立並注入它所需要的外部資源;而控制反轉是從容器的角度在描述,描述完整點:容器控制應用程式,由容器反向的嚮應用程式注入應用程式所需要的外部資源。
使用依賴注入可以帶來以下好處:
-
依賴的注入和配置獨立於元件之外。
-
因為物件是在一個獨立、不耦合的地方初始化,所以當注入抽象方法的時候,我們只需要修改物件的實現方法,而不用大改程式碼庫。
-
依賴可以注入到一個元件中:我們可以注入這些依賴的模擬實現,這樣使得測試更加簡單。
二、Java和ANDROID依賴注入
為了最大程度的提高程式碼的複用性、測試性和維護性,java的依賴注入為注入類中的使用定義了一整套註解(和介面)標準 Java 依賴注入標準(JSR-330,Dependency Injection for Java)1.0 規範已於2009年 10 月份釋出 。
Dagger1是Android上最流行的依賴注入框架。它是由Square公司受到Guice啟發建立的。Dagger2是Dagger1的分支,由谷歌公司接手開發,目前的版本是2.2。Dagger2是受到AutoValue專案的啟發
Android開發從一開始的MVC框架,到MVP,到MVVM,不斷變化。現在MVVM的data-binding還在實驗階段,傳統的MVC框架Activity內部可能包含大量的程式碼,難以維護,現在主流的架構還是使用MVP(Model + View + Presenter)的方式。但是 MVP 框架也有可能在Presenter中集中大量的程式碼,引入DI框架Dagger2 可以實現 Presenter 與 Activity 之間的解耦,Presenter和其它業務邏輯之間的解耦,提高模組化和可維護性。
Dagger github地址為
https://github.com/square/dagger
https://github.com/google/dagger
Dagger2對Dagger的改進如下
再也沒有使用反射:圖的驗證、配置和預先設定都在編譯的時候執行。
容易除錯和可跟蹤:完全具體地呼叫提供和建立的堆疊
更好的效能:谷歌聲稱他們提高了13%的處理效能
程式碼混淆:使用派遣方法,就如同自己寫的程式碼一樣
這裡我們只分析Dagger2的使用和原始碼,以下的所有Dagger指的都是Dagger2
三、Dagger的使用
1、引入相關庫
Dagger的gradle並不是簡單的在dependencies中加上相關compile就可以了,這裡說一下配置方法
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile group: 'com.google.dagger', name: 'dagger', version: '2.4'
compile group: 'com.google.dagger', name: 'dagger-compiler', version: '2.4'
}
生成的中間檔案在build/generated/source/apt/debug中
2、註解
@Inject: 通常在需要依賴的地方使用這個註解。告訴Dagger這個類或者欄位需要依賴注入。這樣,Dagger就會構造一個這個類的例項並滿足他們的依賴。@Module: Modules類裡面的方法專門提供依賴,所以我們定義一個類,用@Module註解,這樣Dagger在構造類的例項的時候,就知道從哪裡去找到需要的 依賴。modules的一個重要特徵是它們設計為分割槽並組合在一起。
@Provides: 在modules中,我們定義的方法是用這個註解,以此來告訴Dagger我們想要構造物件並提供這些依賴。
@Component: Components從根本上來說就是一個注入器,也可以說是@Inject和@Module的橋樑,它的主要作用就是連線這兩個部分。 Components可以提供所有定義了的型別的例項,比如:我們必須用@Component註解一個介面然後列出所有的 @Modules組成該元件,如 果缺失了任何一塊都會在編譯的時候報錯。所有的元件都可以通過它的modules知道依賴的範圍。
@Scope: ,Dagger2可以通過自定義註解限定註解作用域。
3、注入依賴
Dagger提供的注入依賴方法有:
構造方法注入:在類的構造方法前面註釋@Inject
成員變數注入:在類的成員變數(非私有)前面註釋@Inject
函式方法注入:在函式前面註釋@Inject
以上順序是Dagger建議使用的,因為在執行的過程中,總會有一些奇怪的問題甚至是空指標,這也意味著依賴在物件建立的時候可能還沒有初始化完成。
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
...
}
class CoffeeMaker {
@Inject Heater heater;
...
}
class CoffeeMaker {
@Inject
public void setPump(Pump pump){
this.pump=pump;
}
...
}
如果 @Inject註解了成員變數,但是沒有註解構造方法,有需要的話會注入,但是不會建立新的物件。如果對無參建構函式進行註解,可以建立新物件。
沒有通過 @Inject註解的類是不能用Dagger建立的。
4、滿足依賴關係
介面不能構造。
第三方類不能註明。
配置物件必須配置
我們需要通過被 @Provides 註解所標註的方法來實現依賴。每個方法的返回的型別就是我們需要實現的依賴。
一旦需要注入一個 Heater,provideHeater() 就會被呼叫:
@Provides Heater provideHeater() {
return new ElectricHeater();
}
被 @Provides 標註的方法自身也可以有依賴。需要 Pump 作為依賴來返回一個 Thermosiphon 的例項:
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
所有的 @Provides 標註的方法都必須屬於一個 module。用 @Module 標註的類就是一個 module。@Module
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
}
依照慣例,@Provides 標註的方法以 provide 作為字首來命名,而 module 類都用 Module 作為字尾來命名。
5、構建圖
用 @Inject 和 @Provides 來標註的類最終會組成一個由物件構成的圖,
在Dagger2中,該集合依賴關係是一個介面定義的方法,沒有任何引數,只返回所需的型別。通過將@Componet註解應用到這樣的介面上,並將模組型別傳遞給模組引數。
Dagger是一個基於有向無環圖結構的依賴注入庫,DAG——有向無環圖(Directed Acyclic Graph),因此Dagger的使用過程中不能出現迴圈依賴。
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
具體實現是在介面名的基礎上加上Dagger字首,呼叫builder方法設定依賴構建例項。CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
如果 @Component不是在最外層註解,除了字首,還需要用_連線外部結構的名字class Foo {
static class Bar {
@Component
interface BazComponent {}
}
}
生成的元件名稱為DaggerFoo_Bar_BazComponent
有一個可訪問的預設建構函式的module都可以作為構建者,如果沒有設定,會自動構造一個例項。而對於@Provides方法都是靜態的module,實現不需要例項。如果所有的依賴性會未經使用者建立一個例項的依賴構造,則生成的實現類將具有create()方法可用於獲取一個新的例項,而不必處理builder方式。
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
6、單例和使用範圍繫結
使用@Provides 註解的方法,加上 @Singleton,就能保證生成單例。整個生命週期中只有一個例項
使用@Singleton註解的類可以多執行緒共享。
不同的作用域的生命週期是不同的,單例Singleton註解(Application scope)是最長的scope
元件可能有多個作用域註釋。他們有相同的使用範圍,不同的註解相當於別名,所以元件可以包括任何其宣告作用域範圍的繫結。要宣告一個元件與給定作用域關聯,只需將scope註釋應用到元件介面。
@Provides @Singleton static Heater provideHeater() {
return new ElectricHeater();
}
@Singleton
class CoffeeMaker {
...
}
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
CoffeeMaker maker();
}
自定義scope
@Scope
@Retention(RUNTIME)
public @interface PerActivity {
}
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface HomeComponent extends AbstractActivityComponent {
void inject(HomeActivity homeActivity);
void inject(HomeFragment homeFragment);
}
7、可複用的使用範圍
有時候需要限制@Inject註解的類例項化得物件個數,不需要保證任何元件或者子元件的生命週期內完全相同的例項使用,在android中記憶體比較珍貴,這種方式比較有用。使用@Reusable註解的類例項化物件之後會被快取。父元件快取之後,子類會重用。
誰也不能保證該元件將呼叫繫結只有一次,使用@Reusable繫結返回值可變的物件,或者指向相同例項很重要的物件,是很危險的。如果不關心建立多少物件,不可變物件不限定範圍,使用@Reusable註解是安全的。
@Reusable // It doesn't matter how many scoopers we use, but don't waste them.
class CoffeeScooper {
@Inject CoffeeScooper() {}
}
@Module
class CashRegisterModule {
@Provides
@Reusable // DON'T DO THIS! You do care which register you put your cash in.
// Use a specific scope instead.
static CashRegister badIdeaCashRegister() {
return new CashRegister();
}
}
@Reusable // DON'T DO THIS! You really do want a new filter each time, so this
// should be unscoped.
class CoffeeFilter {
@Inject CoffeeFilter() {}
}
8、懶載入注入
有些時候需要懶載入,要用到Lazy<T>
只有一個get方法,如果不呼叫,dagger不會建立物件
呼叫之後,建立物件並儲存
再次呼叫,返回相同物件
class GridingCoffeeMaker {
@Inject Lazy<Grinder> lazyGrinder;
public void brew() {
while (needsGrinding()) {
// Grinder created once on first call to .get() and cached.
lazyGrinder.get().grind();
}
}
}
9、使用Provider注入
有些情況下, 你需要多個物件例項, 而不是僅僅注入一個物件例項。這時你可以利用Provider實現, 每次呼叫Provider的get()函式將返回新的<T>的物件例項。
class BigCoffeeMaker {
@Inject Provider<Filter> filterProvider;
public void brew(int numberOfPots) {
...
for (int p = 0; p < numberOfPots; p++) {
maker.addFilter(filterProvider.get()); //new filter every time.
maker.addCoffee(...);
maker.percolate();
...
}
}
}
10、Qualifiers的使用
有時單獨型別是不足以識別區分依賴性。這個時候可以使用Qualifier註解,這裡使用@Named註解,
class ExpensiveCoffeeMaker {
@Inject @Named("water") Heater waterHeater;
@Inject @Named("hot plate") Heater hotPlateHeater;
...
}
@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
return new ElectricHeater(70);
}
@Provides @Named("water") static Heater provideWaterHeater() {
return new ElectricHeater(93);
}
對這個框架使用的比較少,主要參考github文件
http://google.github.io/dagger/users-guide.html
使用MVP比較少,做的專案基本都是MVC的,這個框架不是非常適用。不能充分發揮Dagger優勢。本文不是很深入。後期深入看一下原始碼,再做詳細分析。
歡迎掃描二維碼,關注公眾賬號。
相關文章
- Android依賴注入:Dagger、RoboGuice和ButterKnifeAndroid依賴注入GUI
- Laravel 依賴注入原始碼解析Laravel依賴注入原始碼
- Spring原始碼解析——依賴注入(二)Spring原始碼依賴注入
- 用 Dagger 2 實現依賴注入依賴注入
- 依賴注入之Dagger2初探依賴注入
- spring原始碼解析之IOC容器(三)——依賴注入Spring原始碼依賴注入
- Android單元測試(6):使用dagger2來做依賴注入Android依賴注入
- React 原始碼中的依賴注入方法React原始碼依賴注入
- 放棄dagger?Anrdoi依賴注入框架koin依賴注入框架
- Android 依賴注入可以更簡單 —— 新版本 Dagger 2 使用教學Android依賴注入
- Dagger2 知識梳理(3) 使用 dependencies 和 @SubComponent 完成依賴注入依賴注入
- 死磕Spring原始碼-依賴注入Spring原始碼依賴注入
- Spring原始碼系列:依賴注入-引言Spring原始碼依賴注入
- 解析依賴注入(DI)的本質依賴注入
- Spring原始碼系列:依賴注入(三)-屬性注入Spring原始碼依賴注入
- webapi - 使用依賴注入WebAPI依賴注入
- .NET 透過原始碼深究依賴注入原理原始碼依賴注入
- Spring原始碼系列:依賴注入(二)createBeanSpring原始碼依賴注入Bean
- Spring原始碼系列:依賴注入(一)getBeanSpring原始碼依賴注入Bean
- Dagger 2 系列(一) -- 前奏篇:依賴注入的基本介紹依賴注入
- Android 依賴注入框架RoboGuiceAndroid依賴注入框架GUI
- 類的反射和依賴注入反射依賴注入
- Dagger2 知識梳理(1) Dagger2 依賴注入的兩種方式依賴注入
- JavaScript中依賴注入詳細解析JavaScript依賴注入
- IOC容器和依賴注入依賴注入
- Spring原始碼系列:依賴注入(四)-總結Spring原始碼依賴注入
- Spring 原始碼分析之 bean 依賴注入原理(注入屬性)Spring原始碼Bean依賴注入
- Android 開源專案原始碼解析 -->Dagger 原始碼解析(十三)Android原始碼
- Android依賴注入之BufferKnife 8.0註解使用Android依賴注入
- Spring原始碼分析(二)bean的例項化和IOC依賴注入Spring原始碼Bean依賴注入
- 依賴注入?依賴注入是如何實現解耦的?依賴注入解耦
- 從原始碼解析vue的響應式原理-依賴收集、依賴觸發原始碼Vue
- 分解uber依賴注入庫dig-原始碼分析依賴注入原始碼
- .net core 原始碼分析(9) 依賴注入(DI)-Dependency Injection原始碼依賴注入
- 依賴注入和控制反轉依賴注入
- Dagger2 知識梳理(2) @Qulifier 和 @Named 解決依賴注入迷失依賴注入
- Laravel 使用依賴注入呼叫方法Laravel依賴注入
- 使用ReflectionTestUtils解決依賴注入依賴注入