Dagger 是為 Java 和 Android 平臺提供的一個完全靜態(tài)的,在編譯時進行依賴注入的框架致开。Dagger 由 Square 公司出品芭毙,Dagger2 是 Google 在 Dagger 基礎上的二次開發(fā)。
Dagger2 官方資料
Github: https://github.com/google/dagger
官方文檔: https://google.github.io/dagger/
API: https://google.github.io/dagger/api/latest/
Dagger2 優(yōu)點
為什么要使用這個框架呢诫硕?
- 提升開發(fā)效率钞钙,省去重復的簡單體力勞動鳄橘,同時使代碼更加優(yōu)雅。比如 new 一個實例的過程就是簡單的重復的勞動芒炼,Dagger2 完全可以勝任此工作瘫怜,使開發(fā)人員將精力放在關鍵業(yè)務上。
- 更好的管理類實例本刽。ApplicationComponent 管理整個 app 的全局類實例鲸湃,所有的全局類實例都統(tǒng)一交給 ApplicationComponent 管理,并且它們的生命周期與 app 的生命周期一樣子寓。每個頁面對應自己的 Component暗挑,頁面 Component 管理著自己頁面所依賴的所有類實例。因為 Component斜友,Module炸裆,整個 app 的類實例結構變的很清晰。
- 解耦鲜屏。假如某類的構造函數(shù)發(fā)生變化烹看,那些設計到的類都要進行修改,解耦避免了代碼的僵化性洛史。
依賴注入
Dagger2 是一個依賴注入框架惯殊,那么依賴注入又是什么呢?我們先把這個詞拆開看虹菲,一個是依賴靠胜,一個是注入掉瞳。
class Player{
MediaFile media;
public Player() {
media = new MediaFile();
}
}
看上述代碼毕源,在 Player 類中,依賴一個 MediaFile 類的對象陕习。依賴就是有聯(lián)系霎褐,有地方使用到它就是對它有依賴,一個系統(tǒng)不可能完全的避免依賴该镣。如果一個類在任何地方都沒使用到冻璃,那么這個類就可以刪掉了。注入就是將 MediaFile 的一個實例對象賦值給 media 引用。其實注入的方法有很多種省艳,構造函數(shù)只是其中一種娘纷,我們還可以通過 setter 方法注入,如
public void setMedia(MediaFile media) {
this.media = media;
}
通過 接口方式注入跋炕,如
class Player implements IPlayer {
MediaFile media;
public void play(IMediaFile file) {
media = file;
}
}
現(xiàn)在來看依賴注入赖晶,就是在有依賴需要的地方傳入一個實例對象啊。那么依賴注入這個詞到底是怎么來的呢辐烂?我們還是先看一下依賴倒置原則吧遏插!
DIP,依賴倒置原則
DIP纠修,英文全稱 Dependence Inversion Principle胳嘲,由軟件開發(fā)大師 Robert C. Martin 提出,具體描述為:
- 高層模塊不應該依賴于低層模塊扣草,二者都應該依賴于抽象了牛。
- 抽象不應該依賴于細節(jié)。細節(jié)應該依賴于抽象辰妙。
直接講原則有點枯燥白魂,假設我們想用播放器播放一段音頻,看一段普通代碼實現(xiàn):
public class Test {
Player player;
MediaFile mediaFile;
public void test(){
player = new Player();
mediaFile = new MediaFile();
player.play(mediaFile);
}
}
上述代碼就違反了 DIP 原則上岗,Test 類 直接依賴低層模塊 Player福荸、MediaFile,而沒有依賴于其抽象肴掷。我們可以用 DIP 原則修改敬锐,將 Player 抽象成 IPlayer 接口,MediaFile 抽象為 IMediaFile呆瞻,讓 DI 類去依賴二者的抽象台夺,修改后代碼如下所示,
public class DI {
IMediaFile mediaFile;
IPlayer player;
public void test() {
mediaFile = new MediaFile();
player = new Player();
player.play(mediaFile);
}
interface IPlayer {
void play(IMediaFile file);
}
class Player implements IPlayer {
public void play(IMediaFile file) {
}
}
interface IMediaFile {
String FilePath();
}
class MediaFile implements IMediaFile {
public String FilePath() {
return "";
}
}
}
注意:實際開發(fā)過程中不要為了實現(xiàn)某一原則而過度設計痴脾。
IOC 颤介,控制反轉
控制反轉,Inversion of Control赞赖,是 Martin Flower 在2004 年總結提煉出來滚朵,也是面向對象重要的負責之一,旨在減小程序中不同部分的耦合問題前域。
在 DI 類中辕近,我們只能生成固定的播放器和媒體文件,假如我想換一個播放器播放視頻咋辦匿垄,用哪個播放器播放音頻或者視頻移宅,是否能夠根據(jù)需求動態(tài)配置呢归粉? IOC 就解決了這個問題。我們假設共有兩款播放器:QQ 和 百度漏峰,兩種媒體文件:音頻和視頻糠悼。我們可以把這種具體需求交給一個配置文件 config.properties(文件名、后綴名可隨便寫)浅乔,然后讓程序運行的時候去讀取配置文件的內容去生成具體的實例绢掰。看代碼(僅核心部分代碼)童擎,
public static void main(String[] args) {
// 采用哪種播放器滴劲,播放哪個媒體文件可以轉給第三方框架控制,由第三方框架去讀取配置文件(可行方式一)顾复,再決定生產哪個播放器實例和媒體文件的實例班挖。
// 控制權 由起先的用戶 轉給了第三方框架。
Properties properties = new Properties();
try {
/**
* 根據(jù)配置文件的位置進行讀取數(shù)據(jù)芯砸,由于現(xiàn)在.properties文件在src目錄下萧芙,所以直接
* IoC.class.getClassLoader().getResourceAsStream("config.properties")就可以獲取到配置文件的信息
*/
properties.load(IoC.class.getClassLoader().getResourceAsStream("ioc/config.properties"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/**
*將配置文件中的信息進行分割,返回成一個數(shù)組
*/
String playerName = properties.getProperty("player");
String mediaName = properties.getProperty("media");
IPlayer _player = PlayerFactory.getPlayer(playerName);
IMediaFile _mtype = MediaFactory.getMediaFile(mediaName);
_player.play(_mtype);
}
config.properties 文件內容如下假丧,運行程序輸出結果為“QQPlayer+video”双揪。配置文件內容以鍵值對的形式存在,player 后對應是具體的播放器包帚,media 后對應的是具體的媒體類型渔期,這個可以按需修改。
player:qqq
media:video
IOC 和 DI 的關系:
所以控制反轉 IoC(Inversion of Control)是說創(chuàng)建對象的控制權進行轉移渴邦,以前創(chuàng)建對象的主動權和創(chuàng)建時機是由自己把控的疯趟,而現(xiàn)在這種權力轉移到第三方,比如轉移交給了 IoC 容器谋梭,它就是一個專門用來創(chuàng)建對象的工廠信峻,你要什么對象,它就給你什么對象瓮床,有了 IoC 容器盹舞,依賴關系就變了,原先的依賴關系就沒了隘庄,它們都依賴 IoC 容器了踢步,通過 IoC 容器來建立它們之間的關系。
DI(依賴注入)其實就是 IOC 的另外一種說法峭沦,DI是由Martin Fowler 在2004年初的一篇論文中首次提出的贾虽。他總結:控制的什么被反轉了?就是:獲得依賴對象的方式反轉了吼鱼。
簡單使用
若想在工程中使用 Dagger2 框架蓬豁,需要在 build.gradle 文件中添加配置:
dependencies {
...
compile 'com.google.dagger:dagger:2.11-rc2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
}
若 Android Gradle plugin 插件版本低于 2.2,還需要引入 android-apt 插件菇肃,https://bitbucket.org/hvisser/android-apt
接下來就可以使用了地粪,下文將會介紹最簡單的用法并將Dagger 的整個框架串一下,高級用法及原理會在以后的文章中介紹琐谤。
case-1
假設我們有一輛下車蟆技,名字是蘭博基尼,我們在 Activity 中將其名字顯示出來斗忌。此用例只需用到兩個注解质礼,@Inject 和 @Component,@Inject 用在 Car 的構造函數(shù)上和被依賴的地方织阳。@Component 相當于一個中間人的角色眶蕉,負責牽線,將需要依賴的地方和提供依賴的地方連接起來唧躲。
public class Car {
private String name = "我是一輛小車造挽,Lamborghini";
@Inject
public Car() {
}
@Override
public String toString(){
return name;
}
}
public interface CarComponent {
void injects(MainActivity mainActivity);
}
Activity 代碼如下
public class MainActivity extends AppCompatActivity {
@Inject
Car mCar;
TextView mTv;
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerCarComponent.builder().build().injects(this);
mTv = findViewById(R.id.tv);
mTv.setText(mCar.toString());
}
}
顯示結果如下圖,證明 mCar 經過了初始化弄痹,要不然會有空指針異常饭入。
case-2
case-1 有一個局限性,就是 @Inject 必須要修飾一個類的構造方法肛真,若我們是應用的第三方工具包谐丢,我們是無法修改其源碼的,這時候我們可以用 @Module 蚓让、@Provides來幫忙庇谆。
- 第一步,用 @Module 聲明一個類凭疮,代表這個類是擁有對外提供實例的功能饭耳。
- 第二步,用 @Provides 修飾一個方法执解,該方法返回具體的實例對象寞肖。
- 第三步,修改用 @Component 修飾的 interface衰腌,指定其需要的 Module 模塊新蟆。
@Module
public class CarModule {
@Provides
public Car getsssssssCar(){
return new Car();
}
}
@Component(modules = CarModule.class)
public interface CarComponent {
void injects(MainActivity mainActivity);
}
其余代碼不變,運行程序右蕊,仍能看到上圖顯示結果琼稻。
相關注解
看上述代碼,分析可以發(fā)現(xiàn)饶囚,要實現(xiàn)依賴注入至少需要三個角色:
- 依賴需求者帕翻,類似于 MainActivity 中 用 @Inject 修飾的引用鸠补。
- 依賴提供者,類似用 @Inject 修飾的構造函數(shù)和及 Module嘀掸。
- 中間人紫岩,將二者串聯(lián)起來的角色,一個小手牽依賴的需求者睬塌,一個小手牽依賴的提供者泉蝌。
我們將其中用到的注解解釋下:
- @Inject,注意下揩晴,這個注解類的全名稱是javax.inject.Inject勋陪,是 Java 擴展包定義的注解。@Inject 可以修飾一個引用硫兰,代表此處可通過依賴注入傳入一個實例對象诅愚,需要注意的是引用的訪問修飾符不能是 private 和 protected,默認包訪問權限即可瞄崇;還可以修飾一個類的構造函數(shù)呻粹,作為一個標記,代表框架可能會根據(jù)該標調用此構造函數(shù)生成實例對象苏研。
- @Module等浊、@Provides,主要是為了解決第三方類庫問題而生的摹蘑,Module 中可以定義多個創(chuàng)建實例的方法筹燕,這些方法用 @Provides 標注。
- @Component衅鹿,是一個中間人的角色(也可理解為干活的秘書)撒踪,也是一個注入器,負責將生成的實例對象注入到依賴的需求者中大渤,同時管理多個 Module制妄。
我們將幾個核心注解的功能以講故事的方式在串一下,以小明要買玩具為例吧泵三!
首先小明家境很富裕耕捞,他老爸直接給配了個秘書(@Component),小明有啥事都可以讓秘書去做烫幕。小明看上了挖掘機的玩具(用 @Inject 修飾的引用)俺抽,于是就叫秘書去買把。但是玩具哪有賣的呢较曼?秘書說先去商店(@Module)逛一逛吧磷斧,假設該商店的一個貨架(Provides)上正好有這個玩具,秘書直接買回去給小明就是了。若商店沒有的話弛饭,商店老板告訴秘書我們這沒有冕末,我可以嘗試聯(lián)系一下玩具廠家(用@Inject 修飾的構造函數(shù)),看他們那有沒有孩哑,他那有的話可以從那去取栓霜,沒有的話就真的沒有了翠桦。
依賴規(guī)則
再補充一下依賴關系横蜒,假如既用 @Inject 聲明了構造函數(shù),也在 Module 提供了響應方法销凑,那么到底用哪個呢丛晌?Dagger2 的依賴規(guī)則是這樣的:
- 步驟1:查找Module中是否存在創(chuàng)建該類的方法。
- 步驟2:若存在創(chuàng)建類方法斗幼,查看該方法是否存在參數(shù)
- 步驟2.1:若存在參數(shù)澎蛛,則按從步驟1開始依次初始化每個參數(shù)
- 步驟2.2:若不存在參數(shù),則直接初始化該類實例蜕窿,一次依賴注入到此結束
- 步驟3:若不存在創(chuàng)建類方法谋逻,則查找Inject注解的構造函數(shù),看構造函數(shù)是否存在參數(shù)
- 步驟3.1:若存在參數(shù)桐经,則從步驟1開始依次初始化每個參數(shù)
- 步驟3.2:若不存在參數(shù)毁兆,則直接初始化該類實例,一次依賴注入到此結束
參考文獻
http://www.reibang.com/p/6eee326566b4
http://www.reibang.com/p/cd2c1c9f68d4
https://blog.csdn.net/bestone0213/article/details/47424255