本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布
前言###
現(xiàn)在Dagger2在項(xiàng)目中的使用越來越多,Dagger2是Dagger的升級(jí)版本,Dagger沒有使用過,但是本篇說的是Dagger2,主要講解的是Dagger2是如何使用的。對(duì)了,忘了說Dagger其實(shí)是一個(gè)依賴注入的框架。
什么是依賴注入###
依賴注入是一種面向?qū)ο蟮木幊棠J剑某霈F(xiàn)是為了降低耦合性,所謂耦合就是類之間依賴關(guān)系,所謂降低耦合就是降低類和類之間依賴關(guān)系。可能有的人說自己之前并沒有使用過依賴注入,其實(shí)真的沒有使用過嗎?當(dāng)我們在一個(gè)類的構(gòu)造函數(shù)中通過參數(shù)引入另一個(gè)類的對(duì)象,或者通過set方法設(shè)置一個(gè)類的對(duì)象其實(shí)就是使用的依賴注入。
通常依賴注入有以下幾種方式###
- 通過接口注入
interface ClassBInterface {
void setB(ClassB b);
}
public class ClassA implements ClassBInterface {
ClassB classB;
@override
void setB(ClassB b) {
classB = b;
}
}
- 通過set方法注入
public class ClassA {
ClassB classB;
public void setClassB(ClassB b) {
classB = b;
}
}
- 通過構(gòu)造方法注入
public class ClassA {
ClassB classB;
public void ClassA(ClassB b) {
classB = b;
}
}
- 通過注解的方式注入
public class ClassA {
//此時(shí)并不會(huì)完成注入,還需要依賴注入框架的支持,如Dagger2
@inject
ClassB classB;
public ClassA() {
}
}
下面我們就來說說如何通過Dagger2來實(shí)現(xiàn)依賴注入吧。
引入Dagger2
添加apt插件
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2' //添加apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
添加依賴(在build.gradle中添加如下代碼)
apply plugin: 'com.android.application' //添加如下代碼,應(yīng)用apt插件
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
compile 'com.google.dagger:dagger:2.4' apt 'com.google.dagger:dagger-compiler:2.4' //java注解
compile 'org.glassfish:javax.annotation:10.0-b28'
...
}
使用Dagger2
添加完Dagger的依賴后我們?nèi)绾卧陧?xiàng)目中使用Dagger呢?
在項(xiàng)目中絕大多數(shù)的使用都是Dagger結(jié)合MVP架構(gòu)使用的,在MVP中使用是非常典型的降低耦合的使用。不懂MVP的可以看這里。
本篇文章中的示例是一個(gè)簡單的登陸功能的示例,代碼沿用上篇講解MVP的登陸代碼,看這里,該示例采用MVP架構(gòu)設(shè)計(jì)通過Dagger2進(jìn)行解耦合,下面就來看看如何使用吧。
在使用Dagger2前我們最好簡單的了解一下MVP,主要是為了理解本篇中的代碼。簡單了解MVP即使不會(huì)寫MVP也可以看的懂本篇的代碼。
為什么要選擇在MVP模式中使用Dagger2呢?
因?yàn)樵贛VP模式中Activity持有presenter的引用,同時(shí)presenter也持有view的引用,這樣便于更新UI界面,這樣Activity就和presenter僅僅的耦合在一起了,而Dagger2是依賴注入框架就是解耦合的,所以子MVP中使用Dagger2也就再好不過了。
在上篇文章講解MVP時(shí)我們可以明顯的看到如下代碼
public class LoginActivity extends AppCompatActivity implements ILoginView,View.OnClickListener{
private Button mLogin ;
private Button mClear ;
private EditText mName ;
private EditText mPassWord ;
ILoginPresenter loginPresenter ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLogin = (Button) findViewById(R.id.btn_login);
mClear = (Button) findViewById(R.id.btn_clear);
mName = (EditText) findViewById(R.id.et_name);
mPassWord = (EditText) findViewById(R.id.et_password);
mLogin.setOnClickListener(this);
mClear.setOnClickListener(this); //持有presenter的引用并且創(chuàng)建對(duì)象
loginPresenter = new LoginPresenterCompl(this) ;
}
........
}
在上述代碼中可以看到activity持有了presenter的引用并且創(chuàng)建了該對(duì)象,但是如果presenter的構(gòu)造函數(shù)發(fā)生改變則這里也需要改變,其實(shí)所有和presenter構(gòu)造函數(shù)相關(guān)的代碼都要改變。
但是如果我們使用Dagger2依賴框架該如何使用呢?
請(qǐng)看下面代碼activity中的代碼
public class LoginActivity extends AppCompatActivity implements ILoginView,View.OnClickListener{
..........
//注意此處使用了注解
@Inject LoginPresenterCompl loginPresenter ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLogin = (Button) findViewById(R.id.btn_login);
mClear = (Button) findViewById(R.id.btn_clear);
mName = (EditText) findViewById(R.id.et_name);
mPassWord = (EditText) findViewById(R.id.et_password);
mLogin.setOnClickListener(this);
mClear.setOnClickListener(this);
DaggerMainComponent.builder().mainModule(new MainModule(this)).build().inject(this);
}
.......
}
LoginPresenterCompl中的代碼
public class LoginPresenterCompl implements ILoginPresenter {
private ILoginView loginView ;
private User user ;
//注意此處使用了注解
@Inject public LoginPresenterCompl(ILoginView view){
loginView = view ;
user = new User("張三","123456") ;
}
......
}
只有上述兩個(gè)注解還無法完成依賴注入,還需要如下兩個(gè)新增類新增的MainModule類
@Modulepublic
class MainModule {
private final ILoginView view ;
public MainModule(ILoginView view){
this.view = view ;
}
@Provides
ILoginView provideILogView(){
return view ;
}
}
新增的MainComponent接口
@Component(modules = MainModule.class)
public interface MainComponent {
public void inject(LoginActivity activity) ;
}
通過直接注解和上述兩個(gè)接口類即可完成Dagger2的依賴注入。在LoginActivity中是通過
DaggerMainComponent.builder().mainModule(new MainModule(this)).build().inject(this)
完成依賴注入的。看完上面的代碼后,一臉的懵逼,WTF(what the fuck),這TM是什么,這么復(fù)雜,還不如之前的簡單呢,新增了兩個(gè)類還有這么多代碼,得不償失呀!
同志們,如果你們第一眼看到后是這樣想的話,說明和我想的一樣,呵呵。每一個(gè)剛接觸Dagger2的人可能都會(huì)這樣想,因?yàn)槲覀冎豢吹搅吮砻妗?br>
不錯(cuò),表面上我們是多了一個(gè)類和接口也多了很多代碼,但是這樣的組合其實(shí)是可以理解的。因?yàn)橥ǔ:唵蔚拇a具有耦合性,而要想降低這樣的耦合就需要其他的輔助代碼,其實(shí)少代碼量和低耦合這兩者并不能同時(shí)兼顧,古人云:魚和熊掌不可兼得。我們作為堂堂聰明絕頂?shù)某绦蛟吃趺纯赡軙?huì)輸給古人呢。
好!下面來認(rèn)真講解Dagger2是如何完成依賴注入的。
首先我們來看看LoginActivity代碼LoginActivity中有這么一段代碼
@Inject
LoginPresenterCompl loginPresenter ;
同樣在LoginPresenterCompl中也有這么一段代碼
@Inject
public LoginPresenterCompl(ILoginView view){
loginView = view ;
user = new User("張三","123456") ;
}
之所以挑出這兩段代碼是因?yàn)樗鼈兌继砑恿薂Inject注解。
在LoginActivity中其實(shí)只有這么一句提到loginPresenter,在接下來的代碼中并沒有對(duì)其進(jìn)行初始化。那loginPresenter是如何進(jìn)行初始化的呢(此處注意添加@Inject注解的變量不能被private修飾)?
直觀上我們可以這樣理解,被@Inject注解的代碼存在某種聯(lián)系,當(dāng)代碼執(zhí)行到@Inject的時(shí)候程序會(huì)自動(dòng)進(jìn)入到這個(gè)類的構(gòu)造方法中,如果正巧這個(gè)構(gòu)造方法也被@Inject修飾了,那么系統(tǒng)就會(huì)幫我們自動(dòng)創(chuàng)建對(duì)象。
這只是表面的理解,這其中肯定還有很多我們沒有看到的“貓膩”。這倆不會(huì)無緣無故的有聯(lián)系,肯定還有第三者,通過這個(gè)第三者這兩個(gè)被@Inject注解修飾的代碼才會(huì)產(chǎn)生聯(lián)系。
這個(gè)第三者是誰呢?
自然的我們就會(huì)想到我們添加的這個(gè)類和接口。
首先我們來分析MainComponent接口代碼如下
@Component(modules = MainModule.class)
public interface MainComponent {
public void inject(LoginActivity activity) ;
}
MainComponent是一個(gè)接口(也可以是一個(gè)抽象類),在這個(gè)接口中我們定義了一個(gè)inject()方法,其中參數(shù)是LoginActivity對(duì)象,同時(shí)MainComponent還被@Component注解著,注解中modules的值是MainModule.class,這個(gè)內(nèi)容會(huì)在接下來的地方進(jìn)行說明,暫時(shí)先放一放。
此時(shí)在Android studio中,如果我們r(jià)ebuild的一下項(xiàng)目就會(huì)有新的發(fā)現(xiàn)。在項(xiàng)目的build/generated/source/apt/debug/項(xiàng)目包名/dragger目錄下生成對(duì)應(yīng)的包其中包含DaggerMainComponent類,這個(gè)類名其實(shí)不是固定的,是根據(jù)我們上面寫的MainComponent,加了Dagger前綴生成的DaggerMainComponent。其實(shí)在這個(gè)時(shí)候我們就已經(jīng)完成了present的依賴注入。但是在
DaggerMainComponent.builder().mainModule(new MainModule(this)).build().inject(this)
中我們看到還有一個(gè)MainModule,這個(gè)是我們自己創(chuàng)建的一個(gè)類MainModule代碼如下
@Modulepublic
class MainModule {
private final ILoginView view ;
public MainModule(ILoginView view){
this.view = view ;
}
@Provides
ILoginView provideILogView(){
return view ;
}
}
我們可以看到這個(gè)類被@Module注解修飾,內(nèi)部有一個(gè)ILoginView的變量和一個(gè)構(gòu)造方法還有一個(gè)被@Provides修飾的provideILogView方法。
看到這還是一臉懵逼,這個(gè)類是干嘛的?
在MainComponent接口中我們看到這么一個(gè)注解@Component(modules = MainModule.class),這里用到了MainModule,可見MainComponent需要MainModule一起才能完成工作。其實(shí)這個(gè)類我們可以理解成提供參數(shù)的,也就是提供參數(shù)依賴的,如何理解呢?
在MainModule中我們?yōu)槭裁匆峁㊣LoginView類型的對(duì)象?為什么不是其他的呢?這是因?yàn)長oginPresenterCompl的構(gòu)造函數(shù)需要這么一個(gè)參數(shù),所以我們在這里提供這么一個(gè)相同的參數(shù),并通過被@Provides注解修飾的方法將其返回出去,如果LoginPresenterCompl還需要其他的參數(shù),同樣我們也可以在這里添加對(duì)應(yīng)類型的參數(shù)然后通過另一個(gè)被@Provides注解修飾的方法返回出去。在MainComponent接口中提供的inject()方法的參數(shù)是LoginActivity,這個(gè)參數(shù)的含義是LoginPresenter要在什么地方注入。
了解了各個(gè)類的功能后我們來總結(jié)一下
- @Inject 程序會(huì)將Dagger2會(huì)將帶有此注解的變量或者構(gòu)造方法參與到依賴注入當(dāng)中,Dagger2會(huì)實(shí)例化這個(gè)對(duì)象- @Module 帶有該注解的類需要對(duì)外提供依賴,其實(shí)就是提供實(shí)例化需要的參數(shù),Dagger2在實(shí)例化的過程中發(fā)現(xiàn)一些參數(shù),Dagger2就會(huì)到該類中尋找?guī)в蠤Provides注解的以provide開頭的需找對(duì)應(yīng)的參數(shù)
- @Component 帶有該注解的接口或抽象類起到一個(gè)關(guān)聯(lián)橋梁的作用,作用就是將帶有@Inject的方法或?qū)ο蠛蛶в蠤Module的類進(jìn)行關(guān)聯(lián),只有通過該接口或抽象類才可以在實(shí)例化的時(shí)候到帶有@Module中類中去尋找需要的參數(shù),也就是依賴注入。
OK,下面我們來捋捋思路。
- 1、在這個(gè)示例代碼中,LoginActivity中需要LoginPresenterCompl,所以在LoginActivity中定義了該對(duì)象并且通過@Inject將其注解,同時(shí)到LoginPresenterCompl的構(gòu)造方法中也通過@Inject將其注解,表明這些是需要依賴注入的。
- 2、因?yàn)樵贚oginPresenterCompl的構(gòu)造方法需要ILoginView類型的參數(shù),所以需要通過依賴將獲取這些參數(shù),所以就需要帶有@Module注解的類用于獲取需要的參數(shù),在@Module注解的類中通過被@Provides注解的以provide開頭的方法對(duì)外提供需要的參數(shù),一般而言有幾個(gè)參數(shù)就需要有幾個(gè)帶有@Provides的方法。
- 3、此時(shí)還需要一個(gè)橋梁將兩者聯(lián)系到一起,帶有@Component的接口或抽象類就起到這個(gè)橋梁的作用。注解中有一個(gè)module的值,這個(gè)值指向需要依賴的Module類,同時(shí)其中有一個(gè)抽象方法inject(),其中的參數(shù)就是我們需要在哪個(gè)類中實(shí)例化LoginPreserentCompl,因?yàn)槲覀冃枰贚oginActivity中實(shí)例化,所以參數(shù)類型就是LoginActivity類型。然后在Android studio中rebuild我們的項(xiàng)目,就會(huì)生成DaggerMainComponent類,通過
DaggerMainComponent.builder().mainModule(new MainModule(this)).build().inject(this);
完成我們需要的依賴注入。###總結(jié)可能我們通過上面的講解,知道了如何使用Dagger2了,也知道具體的流程了,但是可能還會(huì)有些疑惑,為什么?Dagger2是如何通過一些接口和類就完成依賴注入的?在此聲明,別著急,知道如何使用這只是第一步,在下一篇文章中將會(huì)講解Dagger2實(shí)現(xiàn)依賴注入的原理。敬請(qǐng)期待!!!