Dagger 是為 Java 和 Android 平臺(tái)提供的一個(gè)完全靜態(tài)的,在編譯時(shí)進(jìn)行依賴注入的框架。Dagger 由 Square 公司出品,Dagger2 是 Google 在 Dagger 基礎(chǔ)上的二次開發(fā)。
Dagger2 官方資料
Github: https://github.com/google/dagger
官方文檔: https://google.github.io/dagger/
API: https://google.github.io/dagger/api/latest/
Dagger2 優(yōu)點(diǎn)
為什么要使用這個(gè)框架呢?
- 提升開發(fā)效率,省去重復(fù)的簡(jiǎn)單體力勞動(dòng),同時(shí)使代碼更加優(yōu)雅。比如 new 一個(gè)實(shí)例的過(guò)程就是簡(jiǎn)單的重復(fù)的勞動(dòng),Dagger2 完全可以勝任此工作,使開發(fā)人員將精力放在關(guān)鍵業(yè)務(wù)上。
- 更好的管理類實(shí)例。ApplicationComponent 管理整個(gè) app 的全局類實(shí)例,所有的全局類實(shí)例都統(tǒng)一交給 ApplicationComponent 管理,并且它們的生命周期與 app 的生命周期一樣。每個(gè)頁(yè)面對(duì)應(yīng)自己的 Component,頁(yè)面 Component 管理著自己頁(yè)面所依賴的所有類實(shí)例。因?yàn)?Component,Module,整個(gè) app 的類實(shí)例結(jié)構(gòu)變的很清晰。
- 解耦。假如某類的構(gòu)造函數(shù)發(fā)生變化,那些設(shè)計(jì)到的類都要進(jìn)行修改,解耦避免了代碼的僵化性。
依賴注入
Dagger2 是一個(gè)依賴注入框架,那么依賴注入又是什么呢?我們先把這個(gè)詞拆開看,一個(gè)是依賴,一個(gè)是注入。
class Player{
MediaFile media;
public Player() {
media = new MediaFile();
}
}
看上述代碼,在 Player 類中,依賴一個(gè) MediaFile 類的對(duì)象。依賴就是有聯(lián)系,有地方使用到它就是對(duì)它有依賴,一個(gè)系統(tǒng)不可能完全的避免依賴。如果一個(gè)類在任何地方都沒使用到,那么這個(gè)類就可以刪掉了。注入就是將 MediaFile 的一個(gè)實(shí)例對(duì)象賦值給 media 引用。其實(shí)注入的方法有很多種,構(gòu)造函數(shù)只是其中一種,我們還可以通過(guò) setter 方法注入,如
public void setMedia(MediaFile media) {
this.media = media;
}
通過(guò) 接口方式注入,如
class Player implements IPlayer {
MediaFile media;
public void play(IMediaFile file) {
media = file;
}
}
現(xiàn)在來(lái)看依賴注入,就是在有依賴需要的地方傳入一個(gè)實(shí)例對(duì)象啊。那么依賴注入這個(gè)詞到底是怎么來(lái)的呢?我們還是先看一下依賴倒置原則吧!
DIP,依賴倒置原則
DIP,英文全稱 Dependence Inversion Principle,由軟件開發(fā)大師 Robert C. Martin 提出,具體描述為:
- 高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象。
- 抽象不應(yīng)該依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴于抽象。
直接講原則有點(diǎn)枯燥,假設(shè)我們想用播放器播放一段音頻,看一段普通代碼實(shí)現(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 "";
}
}
}
注意:實(shí)際開發(fā)過(guò)程中不要為了實(shí)現(xiàn)某一原則而過(guò)度設(shè)計(jì)。
IOC ,控制反轉(zhuǎn)
控制反轉(zhuǎn),Inversion of Control,是 Martin Flower 在2004 年總結(jié)提煉出來(lái),也是面向?qū)ο笾匾呢?fù)責(zé)之一,旨在減小程序中不同部分的耦合問(wèn)題。
在 DI 類中,我們只能生成固定的播放器和媒體文件,假如我想換一個(gè)播放器播放視頻咋辦,用哪個(gè)播放器播放音頻或者視頻,是否能夠根據(jù)需求動(dòng)態(tài)配置呢? IOC 就解決了這個(gè)問(wèn)題。我們假設(shè)共有兩款播放器:QQ 和 百度,兩種媒體文件:音頻和視頻。我們可以把這種具體需求交給一個(gè)配置文件 config.properties(文件名、后綴名可隨便寫),然后讓程序運(yùn)行的時(shí)候去讀取配置文件的內(nèi)容去生成具體的實(shí)例??创a(僅核心部分代碼),
public static void main(String[] args) {
// 采用哪種播放器,播放哪個(gè)媒體文件可以轉(zhuǎn)給第三方框架控制,由第三方框架去讀取配置文件(可行方式一),再?zèng)Q定生產(chǎn)哪個(gè)播放器實(shí)例和媒體文件的實(shí)例。
// 控制權(quán) 由起先的用戶 轉(zhuǎn)給了第三方框架。
Properties properties = new Properties();
try {
/**
* 根據(jù)配置文件的位置進(jìn)行讀取數(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();
}
/**
*將配置文件中的信息進(jìn)行分割,返回成一個(gè)數(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 文件內(nèi)容如下,運(yùn)行程序輸出結(jié)果為“QQPlayer+video”。配置文件內(nèi)容以鍵值對(duì)的形式存在,player 后對(duì)應(yīng)是具體的播放器,media 后對(duì)應(yīng)的是具體的媒體類型,這個(gè)可以按需修改。
player:qqq
media:video
IOC 和 DI 的關(guān)系:
所以控制反轉(zhuǎn) IoC(Inversion of Control)是說(shuō)創(chuàng)建對(duì)象的控制權(quán)進(jìn)行轉(zhuǎn)移,以前創(chuàng)建對(duì)象的主動(dòng)權(quán)和創(chuàng)建時(shí)機(jī)是由自己把控的,而現(xiàn)在這種權(quán)力轉(zhuǎn)移到第三方,比如轉(zhuǎn)移交給了 IoC 容器,它就是一個(gè)專門用來(lái)創(chuàng)建對(duì)象的工廠,你要什么對(duì)象,它就給你什么對(duì)象,有了 IoC 容器,依賴關(guān)系就變了,原先的依賴關(guān)系就沒了,它們都依賴 IoC 容器了,通過(guò) IoC 容器來(lái)建立它們之間的關(guān)系。
DI(依賴注入)其實(shí)就是 IOC 的另外一種說(shuō)法,DI是由Martin Fowler 在2004年初的一篇論文中首次提出的。他總結(jié):控制的什么被反轉(zhuǎn)了?就是:獲得依賴對(duì)象的方式反轉(zhuǎn)了。
簡(jiǎn)單使用
若想在工程中使用 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
接下來(lái)就可以使用了,下文將會(huì)介紹最簡(jiǎn)單的用法并將Dagger 的整個(gè)框架串一下,高級(jí)用法及原理會(huì)在以后的文章中介紹。
case-1
假設(shè)我們有一輛下車,名字是蘭博基尼,我們?cè)?Activity 中將其名字顯示出來(lái)。此用例只需用到兩個(gè)注解,@Inject 和 @Component,@Inject 用在 Car 的構(gòu)造函數(shù)上和被依賴的地方。@Component 相當(dāng)于一個(gè)中間人的角色,負(fù)責(zé)牽線,將需要依賴的地方和提供依賴的地方連接起來(lái)。
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());
}
}
顯示結(jié)果如下圖,證明 mCar 經(jīng)過(guò)了初始化,要不然會(huì)有空指針異常。

case-2
case-1 有一個(gè)局限性,就是 @Inject 必須要修飾一個(gè)類的構(gòu)造方法,若我們是應(yīng)用的第三方工具包,我們是無(wú)法修改其源碼的,這時(shí)候我們可以用 @Module 、@Provides來(lái)幫忙。
- 第一步,用 @Module 聲明一個(gè)類,代表這個(gè)類是擁有對(duì)外提供實(shí)例的功能。
- 第二步,用 @Provides 修飾一個(gè)方法,該方法返回具體的實(shí)例對(duì)象。
- 第三步,修改用 @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);
}
其余代碼不變,運(yùn)行程序,仍能看到上圖顯示結(jié)果。
相關(guān)注解
看上述代碼,分析可以發(fā)現(xiàn),要實(shí)現(xiàn)依賴注入至少需要三個(gè)角色:
- 依賴需求者,類似于 MainActivity 中 用 @Inject 修飾的引用。
- 依賴提供者,類似用 @Inject 修飾的構(gòu)造函數(shù)和及 Module。
- 中間人,將二者串聯(lián)起來(lái)的角色,一個(gè)小手牽依賴的需求者,一個(gè)小手牽依賴的提供者。
我們將其中用到的注解解釋下:
- @Inject,注意下,這個(gè)注解類的全名稱是javax.inject.Inject,是 Java 擴(kuò)展包定義的注解。@Inject 可以修飾一個(gè)引用,代表此處可通過(guò)依賴注入傳入一個(gè)實(shí)例對(duì)象,需要注意的是引用的訪問(wèn)修飾符不能是 private 和 protected,默認(rèn)包訪問(wèn)權(quán)限即可;還可以修飾一個(gè)類的構(gòu)造函數(shù),作為一個(gè)標(biāo)記,代表框架可能會(huì)根據(jù)該標(biāo)調(diào)用此構(gòu)造函數(shù)生成實(shí)例對(duì)象。
- @Module、@Provides,主要是為了解決第三方類庫(kù)問(wèn)題而生的,Module 中可以定義多個(gè)創(chuàng)建實(shí)例的方法,這些方法用 @Provides 標(biāo)注。
- @Component,是一個(gè)中間人的角色(也可理解為干活的秘書),也是一個(gè)注入器,負(fù)責(zé)將生成的實(shí)例對(duì)象注入到依賴的需求者中,同時(shí)管理多個(gè) Module。
我們將幾個(gè)核心注解的功能以講故事的方式在串一下,以小明要買玩具為例吧!
首先小明家境很富裕,他老爸直接給配了個(gè)秘書(@Component),小明有啥事都可以讓秘書去做。小明看上了挖掘機(jī)的玩具(用 @Inject 修飾的引用),于是就叫秘書去買把。但是玩具哪有賣的呢?秘書說(shuō)先去商店(@Module)逛一逛吧,假設(shè)該商店的一個(gè)貨架(Provides)上正好有這個(gè)玩具,秘書直接買回去給小明就是了。若商店沒有的話,商店老板告訴秘書我們這沒有,我可以嘗試聯(lián)系一下玩具廠家(用@Inject 修飾的構(gòu)造函數(shù)),看他們那有沒有,他那有的話可以從那去取,沒有的話就真的沒有了。
依賴規(guī)則
再補(bǔ)充一下依賴關(guān)系,假如既用 @Inject 聲明了構(gòu)造函數(shù),也在 Module 提供了響應(yīng)方法,那么到底用哪個(gè)呢?Dagger2 的依賴規(guī)則是這樣的:
- 步驟1:查找Module中是否存在創(chuàng)建該類的方法。
- 步驟2:若存在創(chuàng)建類方法,查看該方法是否存在參數(shù)
- 步驟2.1:若存在參數(shù),則按從步驟1開始依次初始化每個(gè)參數(shù)
- 步驟2.2:若不存在參數(shù),則直接初始化該類實(shí)例,一次依賴注入到此結(jié)束
- 步驟3:若不存在創(chuàng)建類方法,則查找Inject注解的構(gòu)造函數(shù),看構(gòu)造函數(shù)是否存在參數(shù)
- 步驟3.1:若存在參數(shù),則從步驟1開始依次初始化每個(gè)參數(shù)
- 步驟3.2:若不存在參數(shù),則直接初始化該類實(shí)例,一次依賴注入到此結(jié)束
參考文獻(xiàn)
http://m.itdecent.cn/p/6eee326566b4
http://m.itdecent.cn/p/cd2c1c9f68d4
https://blog.csdn.net/bestone0213/article/details/47424255