Android單排上王者系列之Dagger2使用解析

本篇文章已授權(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)期待!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容