Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 帶著疑惑走進Dagger2

帶著疑惑走進Dagger2

編輯:關於Android編程

Dagger2是一款最初由Square公司研發,後交由Google進行維護管理的依賴注入(Dependency Injection DI)框架。

我想之所以其越來越受歡迎,一是其自身的優異。二是當我們了解了對它的使用之後,就會發現它和Android現在盛行的MVP架構可以說是天生一對。

於是當我們看到越來越多的地方開始提及Dagger2這個東西,難免自己就會想要去嘗試一下。那麼,當我們看著一大堆的關於Dagger2的學習資料時:

難免就會被一些類似“Dagger2從入門到放棄”的關鍵詞吸引,很顯然這從一定程度上說明了:想要熟練的掌握Dagger2並不輕松;並且在學習過程中很可能會遇到無數的困惑。然而,對於“入門到放棄”這樣的的觀點,小弟只想說四個字:。。。。。。深!有!體!會。

所以,本文將試圖站在一個“純菜鳥”的視角上,介紹一些關於Dagger2的使用以及學習過程中遇到的疑惑。讓我們開始吧!

簡析依賴注入

我們前文已經說到Dagger2是一個依賴注入框架,所以在學習它的使用之前,我們顯然有必要弄清楚依賴注入的概念。

老套的例子

看下面這樣一個老套的例子:

這裡寫圖片描述vcq9o6zKx82ouf3Wsb3T1NpBwODW0Mq508NuZXfAtM3qs8m1xKGj1eK/z7aoysfX7rLutcS3vcq9oaPS8s6qo6xCwOC1xLm51Oy6r8r90ru1qbjEtq+jrEHA4L2r1rG908rctb3Ho8GsoaM8YnIgLz4NCs6qwcux3MPi1eLW1sfpv/a3osn6o6y+zdHcyfrBy8v5zr21xNLAwLXXosjroaPW8LK9td29+KOsz9bU2qOsztLDx8rXz8i909fFv7TSu9bWsci9zyZsZHF1bzvUrcq8JmxkcXVvO7XE16LI67e9yr2jujwvcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160903/20160903091902197.png" title="\" />

現在調整了代碼,改為使用向構造函數傳參來持有B類對象,這樣A類和B類就完成了解耦。這時B類的構造器無論如何變動,至少A類代碼不會再受影響。
但這種解耦其實也只是相對於“某種程度”上來說的。因為這個時候的耦合只是由A類轉移到了第三方,即A類的調用者,比如說C類。看代碼:

這裡寫圖片描述

這個時候我們難免就會考慮,有沒有一種方式可以更大程度完成解耦?“沒有買賣就沒有殺害”,那麼現在有了買賣,於是也就出現了依賴注入框架。

進一步解耦,Dagger2的最基礎使用

現在,我們通過一個簡單的例子看一下怎麼使用Dagger2對之前的代碼進行解耦。在這個過程中,我們將學會兩個常用的注解@Inject與@Component。

我們首先來看一下如何通過Dagger2來改造A類:

這裡寫圖片描述

從上述的代碼中可以看到,我們是分別對變量b以及A的構造函數加上了注解@Inject。這個注解可以說是Dagger2中最容易理解的一個:
對變量b加上注解意味著告訴Dagger2,A類依賴了B,但b對象需要你幫我注入到A類當中來,我就不管了。而對構造器聲明該注解的意義暫且不提。

接著,是看一下B類是如何來改造的。同理,對B的構造函數加上@Inject的意義我們也暫且不提:

這裡寫圖片描述

最後,是改造過後的C類的面目:

這裡寫圖片描述

我們可以發現,這裡就是對其需要的A類對象變量添加了注解@Inject。現在我們來回憶一下,就知道添加在A類與B類構造器上的@Inject注解的意義了。
之前我們說到在依賴的變量上添加@Inject的意義在於,告訴Dagger2這個變量的對象需要你負責幫我注入。那麼,Dagger2怎麼確定如何幫你注入呢?
我們可能會想,Dagger2你自己去找構造器啊?沒錯,如果你需要注入的類永遠只有一個構造器,那麼這樣做看上去就是行得通的。但這顯然不可能。
所以,我們還需要為所依賴的變量對象的類的構造器也添加上@Inject,這就等於告訴Dagger2,這裡依賴的對象我希望你通過這種構造器來生成注入。

OK,那麼通過Dagger2來設置依賴注入的工作是不是就完成了呢?實際上不難想到是還沒有的。這樣考慮:
為對象變量添加@Inject注解代表著這是依賴的需求方;而添加在依賴對象所屬類的構造器上的@Inject代表這是依賴的提供方。從而不難看到:
現在,我們顯然還需要一個“橋梁“把依賴方和提供方給聯系起來。在Dagger2當中,這個橋梁就是@Component。所以我們還需要這樣的東西:

這裡寫圖片描述

也就是說,我們需要定義一個接口,然後為其添加上@Component注解,然後聲明一個叫做inject()的方法,顧名思義,這個方法的意義很好理解。
可以這樣認為:我們說過了component是橋梁,通過其調用inject()的意義就好比:現在要把提供(對象)注入給依賴(變量a)了,而注入的目的地,就是C

OK,現在准備工作就完成了。重新編譯一下項目,Dagger2會自動為我們生成一個剛才定義的Component的實際實現,其默認以Dagger開頭。
於是,我們在C的構造器中添加以下一行代碼,目的就是在構造C類的對象的時候就進行一系列相關的依賴注入工作:

這裡寫圖片描述

現在,我們就完成了整個依賴注入工作了。現在我們可以寫一個測試類來測試調用c的doSomething方法,看看會發生什麼。

這裡寫圖片描述

以上就是測試的日志輸出結果,從中我們很容易簡單的分析出整個依賴注入的過程:

首先,我們通過C類對象調用其實例方法doSomething,於是C類的構造器率先執行了。 而調用C的doSomething方法,由於其依賴A類,所以A類的構造器又執行了。 同理,A的doSomething方法又依賴於B類,所以B類的構造器又執行了。 這個時候,所有的對象就都注入完成了,於是正式調用B的doSomething方法,從而打印出對應內容。

好的,有可能這個時候,就會第一次冒出“從入門到放棄”的念頭了。因為我們發現之前我們使用的很熟練的代碼,加入Dagger2後,開始有點懵逼了!
其實這很正常,因為通常人都害怕“改變”。之前的代碼我們寫過無數,非常熟悉非常親切。猛的一下換種方式難免會抗拒,何況這種方式看上去並不那麼容易理解。所以這個時候,我們急需看到Dagger2帶給我們的好處,抵消一下“哥想放棄”的怨念。想象一種情況:
現在因為需求的改變,B類需要依賴另一個新的類D了。那麼如果以我們最初的方式,就會出現類似改動:

    public B(D d){
        this.d = d;
        Log.d("Dagger2","Class B construct..");
    }

顯然,這樣做的話,就又會影響其他需要依賴B對象的地方。而因為我們使用了Dagger2,則可以使用如下改動:

這裡寫圖片描述

由此我們可以發現,本次改動除了B類自身添加依賴,其它的A類,C類都不會受到任何的影響。
好吧,我們發現Dagger2讓我們進一步進行了解耦。嘗到了一點甜頭,我們暫時決定放下“放棄”的念頭。一起繼續學習!

@Module與@Provides

不知道大家注意到沒有,到目前為止,我們涉及到的依賴都有一個共同點,那就是其構造方式在我們控制范圍內。現在試想一種新的情況:
如果我們依賴的某個類,來自於外部或者我們控制不了其構造,那麼應該怎麼辦呢?於是現在就該@Module與@Provides上場了。
我們舉個最簡單的例子,在Android裡面有一個我們熟悉的朋友:Context。那麼,現在我們修改一下A類,變為了如下所示:

這裡寫圖片描述

好的,現在我們看到的情況是A類的doSomeThing方法需要依賴於Context對象,所以我們可以通過如上圖所示的方法來注入context。
但是,如果我們這裡要統一為用Dagger2注入呢?應該如何去做?首先當然還是為依賴的Context對象變量添加@Inject注解:

這裡寫圖片描述

接下來的工作就與之前有所不同了。因為,很顯然我們不會去給Context類的構造器加上@Inject,現在意味著我們需要一種新的方式來提供Context的依賴。所以,我們需要新建一個下面這樣的類:

這裡寫圖片描述

如圖所示,這裡我們新建了一個叫做ClassAModule的類,加上了@Module注解。這意味著此類是一個專門用來為A類提供依賴的Module類。
接著,我們看到一個被@Provides注解的方法provideContext。顧名思義,它就是用來提供A類所依賴的Context對象的。那麼,現在就又回到了熟悉的步驟,編寫“橋梁”- Component:

這裡寫圖片描述

從上圖的代碼中可以看到,與之前的不同之處在於,這次在@Component注解裡聲明了,本次向A類注入所需要的依賴的工作,不再是通過構造器,而是通過ClassAModule這個Module類來提供相關依賴對象。同樣的,再次編譯項目後就得到了對應的Component實際實現。當然調用也發生了輕微改變:

這裡寫圖片描述

我們總結一下可以發現,通過@Module+@Provides與在構造器上設置@Inject的本質是相同的,都是用來提供依賴;差別只是提供方式不同而已。
分析一下這個過程也就是說:如果我們的某個類中依賴於某些類,同時這些類的構造方式又不是我們能控制的。那麼,就可以新建一個@Module類用以提供這些依賴。而@Module類提供這些依賴的方式則是聲明對應返回類型的provideXXX方法。但是:這個時候我就更想要”從入門到放棄”了!
因為回憶一下,之前說到@Inject的使用時,我們能很明顯的體會到Dagger2帶來的好處。但這裡顯然就不是了,我們對比一下兩種注入方式:

定義處:

這裡寫圖片描述

調用處:

這裡寫圖片描述

我們可以看到這兩處怎麼看都是使用Dagger2過後寫的代碼更多一點。並且!使用Dagger2過後,我們還需要維護額外的Module與Component類。
試想現在A類又添加了新的依賴,那麼兩種方式同樣都會涉及到A類和MainActivity的代碼改動;而Dagger2還會涉及Module與Component的改動。
一臉懵逼的我想來想去,覺得似乎除了是要統一依賴注入的方式(Dagger2)之外;唯一能想到的好處似乎就是把類的依賴全部抽離到了單獨的Module類中。當然,這只是個人看法,說不得正確,還希望有精通依賴注入和Dagger2的大腿能夠指點迷津。

@Qulifier的使用

現在,我們接著考慮一種新的情況。假設現在有類A,B,C,類A和類B當中都依賴於C,但C有兩種構造器,A和B分別是依賴於不同的構造器。那麼,有了之前的使用基礎,我們最初可能會簡單的這樣考慮:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

那麼,這裡拋開Dagger2如何去區分類A,B到底是需要哪種構造器構造依賴對象不同。編譯項目首先會遇到如下的錯誤:

這裡寫圖片描述

很顯然,編譯器已經告訴你為C類定義了多個@Inject修飾的構造器,這是不合法的。這個時候該怎麼辦?我們想起了還有另一種提供依賴的方式:

這裡寫圖片描述

但是這裡就要注意了,回憶一下:之前說A類依賴於Context時,我們在Module類裡通過provideContext方法來提供這個依賴。
但我們需要明白的是,這裡的方法命名只是一種規范,而非規則。也就是說可以隨意定義這個方法名,那麼Dagger是如何確定這就是提供依賴的地方呢?
很顯然,既然不是通過方法命名,自然就是通過返回類型了。但這裡我們看到兩個方法的返回類型都是C。顯然我們還是沒有完全解決我們的問題。
這個時候,就需要使用到Dagger2的另一個注解@Qulifier了。說白了這個注解就是用來起到一個標識的作用的。它的使用方式如下:

這裡寫圖片描述

這裡寫圖片描述

可以看到這個注解本身就是用來修飾注解的,我們這裡定義了兩個標示注解DefaultConstructor與ConstructorWithString。
之後的步驟我想大家都可以猜到怎麼做了,沒錯,先分別給我們Module裡的兩個provide方法加上標示:

這裡寫圖片描述

最後以A類為例,假設A類依賴的是C類無參的默認構造器,那麼修改A類的代碼:

這裡寫圖片描述

最終經過調用,運行程序就得到了我們想要的日志打印信息:

這裡寫圖片描述

@Scope

最後,我們一起來看Dagger2中最難理解,最讓人想要“從入門到放棄”的一個注解:@Scope的使用。我們首先看這樣的代碼:

這裡寫圖片描述

然後是Activity的代碼:

這裡寫圖片描述

也就是說,這裡我們在MainActivity中引用了兩個A的對象a1和a2。在經過注入過後,我們打印這兩個對象的信息,輸出如圖:

這裡寫圖片描述

從輸出結果我們可以看到,這裡的分別構建了兩個全新的對象。那麼,有的時候我們希望控制某個對象的生命周期。比如說:
現在我們希望在當前Activity只存在唯一一個A類對象,並且其生命周期隨Activity消亡而消亡。那麼就需要借助@Scope來實現了:

這裡寫圖片描述

有了之前@Qulifier的經驗,我相信上面的代碼並不難理解。之後的工作也很簡單,我們分別在兩處需要的地方加上這個注解就搞定了:

這裡寫圖片描述

這裡寫圖片描述

隨後,我們再次運行程序,得到如下輸出結果:

這裡寫圖片描述

現在我們起碼確定了A類的對象在MainActivity中是單例的,那麼其生命周期究竟是否與Activity是保持一致的呢?我們進一步驗證:

這裡寫圖片描述

我們新建了一個SecondActivity,然後進行由MainActivity跳轉到SecondActivity的操作,得到如下輸出:

這裡寫圖片描述

可以看到,Activity發生跳轉後,就注入了新的A類對象。如果你是第一次接觸@Scope,我相信你和我一樣的疑惑。
因為我們好像就簡單的通過@Scope定義了一個注解@ActivityScope,居然就實現了如此強悍的功能?但懵逼的是:
為什麼這麼輕易就實現了?似乎我們在使用@Scope注解時也沒有指定任何有關於生命周期的信息啊?
不用著急,我們接著看。假設現在我們更進一步,想要在整個應用內,A類對象的依賴都是唯一的。也就是所謂的全局單例,那麼又該怎麼做?

首先,我們再定義一個@Scope注解,用來代表全局生命周期:

這裡寫圖片描述

接著,新建一個用於注入全局依賴的@Component接口:

這裡寫圖片描述

然後,當然是將其依賴的Module裡的ProvideA()方法也設置Scope:

這裡寫圖片描述

最後,當然自定義我們自己的Application類,並做相關設置:

這裡寫圖片描述

可以看到,以上的大部分東西我們都是比較熟悉的,唯一的不同在於:

Component接口中沒有再提供inject方法,相反是有一個返回類型為A的方法。 在MyApplication的onCreate方法中調用注入時,因為沒有inject()方法,所以是直接通過build方法返回了定義的AppCompent對象。

我相信稍微機智的朋友到了這裡就很容猜出出現這種不同的意義了,沒錯,核心就在於:我們通過MyApplication類裡的appComponent對象調用getA方法,就可以返回Dagger2為我們注入到MyApplication的a了。也就是所謂的全局單例。那麼,在Activity中我們就通過如下的方式去獲取全局對象了:

這裡寫圖片描述

但是細心一點的朋友可能會說,既然是Dagger2,對象還是通過@inject來注入好點吧!沒錯,那麼現在我們怎麼辦呢?很簡單,修改MainActivityComponent:

這裡寫圖片描述

沒錯,這裡我們看到原來Component也是可以進行依賴的,方式就是通過設置dependencies。現在Activity中就可以通過正規軍的方式依賴A了:

這裡寫圖片描述

好了,現在回歸正題。我將結合自己的理解,試圖以一種最簡單易懂的方式來講述@Scope注解為什麼能實現這樣的作用。我們分析一下:
不知道大家注意到沒有,我們在AppComponentClassAModule中的provideA()方法上,添加的注解是@ApplicationScope
然後,在MainActivityComponent上添加的注解時@ActivityScope注解。為什麼這樣做呢?我們可以測試一下:

假設,我們把AppComponent上的注解改為@ActivityScope,那麼將受到如下異常:

這裡寫圖片描述

其實異常信息描述的很清晰:為AppComponent設定的Scope與其自身依賴的Module中的provideA方法設定的Scope是不一致的。
所以請大家一定記住:@Component與其依賴的@Module的@Scope一定要是保持一致的。Dagger2實際就是通過這個來決定對象的生命周期的。
可以這樣理解:當我們為Component設定了scope之後,當我們通過build()方法構建出這個“橋梁”後,它就進入了一段生命周期當中。同時:
與它關聯的Module內,只要是同樣設定了scope,那麼它在這段生命周期范圍內就是唯一的,並且生命周期將跟隨scope。那麼:
因為我們只在自定義的Application中build了一次AppComponent,自然的,設定了scope的依賴對象自然就成為了“全局唯一”的對象。
這樣說可能仍然感覺很亂,我們通過一個例子能夠更好的理解這個東西,修改MainActivity的代碼:

這裡寫圖片描述

上面的代碼有了之前的基礎,相信大家都能明白用意。現在再次運行程序,將收到如下的輸出信息:

這裡寫圖片描述

我們看到,這裡得到了兩個不同的對象。也就是說,現在雖然appComponent依舊是用所謂的@ApplicationScope進行注解的。
但是,上述代碼中的對象a2現在的生命周期(域)實際上卻是@ActivityScope,也就是與MainActivity相關聯的了。

接著,假設我們做另一種嘗試,把MainActivityComponent改為用注解@ApplicationScope。那麼就將看到另一種異常出現:

這裡寫圖片描述

有了之前的基礎,這個異常就更容易理解了。簡單的說就是:
MainActivityComponent的Scope設定與AppComponent沖突了,而它們本身就不屬於同一個等級范圍。
換句話說,MainActivityComponent的Scope設定,應該與它本身依賴的Module內的scope保持一致。

相信大家現在就明白了,所謂的@Scope並沒有那麼神奇。它的作用實際上就是統一管理Component和Module內提供依賴對象的方法。
當設定了Scope之後,Component被build出來後,這裡面設定了Scope的依賴對象對於該Component注入的依賴來說就是唯一的。

@Singleton

最後,簡單的說一下@Singleton。如果你能理解我之前的解釋,那麼理解@Singleton就將變得非常容易。舉個例子:
我們之前用自己定義的@ApplicationScope來設定AppComponent和provideA方法。那麼,我們直接使用@Singleton也是一樣的。
因為所謂的@Singleton其實就只是一個@Scope的默認實現而已,一下就是@Singleton的源碼:

這裡寫圖片描述

總結

到了這裡,關於dagger2的使用就聊到這裡。我只能再次感歎,學習這個東西的確容易讓人懵逼和抓狂。希望本文能給您帶來一點點的幫助。

  1. 上一頁:
  2. 下一頁:
Copyright © Android教程網 All Rights Reserved