這篇文章主要談一下本人在學習Dagger2的心得,若有錯漏,敬請諒解。json
依賴注入就是把下面這樣的代碼:數組
class A { public A() { } class B { A a; public B() { a = new A(); } } class C { A a; B b; public C() { a = new A(); b = new B(); b.a = a; } }
main() { C c = new C(); }
變成:框架
class A { A() { } } class B { A a; B(A a) { this.a = a; } } class C { A a; B b; C(A a, B b) { this.a = a; this.b = b; } }
main() { A a = new A(); B b = new B(a); C c = new C(a, b); }
這種把對象之間的依賴生成的責任交給外界的作法,叫作依賴注入。ide
咱們有類和它們之間的依賴關係,便很天然地會用圖來表示這種狀態。如上例子所示,可用下面這樣一個圖來表示:學習
+-----+ | | +----> | A | <----+ | | | | | +-----+ | | | | | +---+---+ +---+---+ | | | | | B | <---------+ C | | | | | +-------+ +-------+
箭頭表示依賴的對象。ui
咱們想要這樣的一種依賴注入框架:當咱們須要一個B對象時,框架按照依賴遍歷這個圖,生成A,而後將其注入B,最後返回一個已經生成好的B對象。大概是:this
B b = Injector.create(B.class)設計
另外,若是要求A對象是單例(這裏不解釋什麼是單例)或對象的生成符合某種指定的規則,框架應自動識別並做出處理。code
咱們面對兩個主要問題:如何表示依賴圖和如何生成對象。component
咱們需定義一種聲明依賴的方法。能夠用xml,json,甚至DSL來完成這個任務。這裏咱們採用比較流行和簡便的註解(annotation)來表示依賴關係。
假設咱們要的效果以下所示:
@dependence class A { } @dependence(A.class) class B { } @dependence({A.class, B.class}) class C { }
能夠看到,咱們用@dependence註解來表示上面例圖中的箭頭。各個類之間的依賴關係十分清晰。
若是要求A是單例,咱們能夠這樣:
@singleton @dependence() class A { }
創建了依賴圖之後,須要經過某種方式生成咱們須要的對象。咱們但願是這樣的:
B b = Injector.create(B.class)
或者經過註解實現自動注入
class Main { @Inject B b; main() { Injector.inject(this); } }
咱們來看一下Dagger2是如何實現上述兩個目標的。
Dagger2中,是經過@Inject註解或者@Module和@Provide這兩個註解創建依賴圖,以下所示:
首先定義好類:
public class A { public A(){ } } public class B { A a; public B(A a) { this.a = a; } } public class C { A a; B b; public C(A a, B b) { this.a = a; this.b = b; } }
而後咱們用第一種方法來聲明依賴:
public class A { @Inject public A() { } } public class B { A a; @Inject public B(A a) { this.a = a; } } public class C { A a; B b; @Inject public C(A a, B b) { this.a = a; this.b = b; } }
能夠看到咱們爲每個類的方法添加了@Inject聲明,表示該類是依賴圖中的一個節點。若是該初始化方法含有參數,那麼這些從參數也應是依賴圖中的節點。
第二種方法是經過一個module類來聲明依賴,以下所示:
@Module public class ABCModule { @Provides public A provideA() { return new A(); } @Provides public B provideB(A a) { return new B(a); } @Provides public C provideC(A a, B b) { return new C(a, b); } }
@Module註解表示這個ABCModule的做用是聲明「依賴圖」的。@Provides註解表示當前方法的返回值是圖中的一個節點,方法的參數是依賴的對象,即前文中箭頭指向的目標。
再強調一次,Dagger要求圖中的每個節點都要聲明,即每個節點都要在module中有@Provides註解的方法或者@Inject註解的初始化方法。
能夠看到第二種方式(module)無需修改原來的對象。爲了讓模塊儘可能少地依賴第三方庫,通常採用第二種方式來聲明依賴圖。
Dagger2中,從依賴圖中獲取對象需經過component。component是依賴圖和被注入對象之間的橋樑。以下所示:
@Component(module=ABCModule.class) public interface ABCComponent { public A provideA(); public B provideB(); public C provideC(); void inject(Activity mainActivity); }
@Component註解表示ABCComponent這個接口是一個Component。Component的方法隱含着以下兩條規則:
不帶參數的方法爲「provider」方法,該方法的返回值是從依賴圖中取得的對象。以下所示(僞代碼):
class Main { C c; public void init() { c = Component.provideC(); } }
帶參數的方法,參數爲「注入對象」。一般於@Inject標籤同時使用。以下所示(僞代碼):
class Main { @Inject C c; public void init() { Component.inject(this); } }
即調用Component.inject(foorbar)的時候,框架自動爲用@Inject標籤標註的屬性注入依賴。要求@Inject的屬性的類必須是依賴圖中的節點。
注意:component的方法必需至少符合以上兩條規則中的一條。
注意:provider方法的名字通常爲「provider」,inject方法的名字通常爲「inject」,但名字不影響這兩個方法的功能。
當Component聲明好之後,框架會在編譯時生成一個DaggerComponent名字的類,咱們能夠用它來實施依賴注入,以下所示:
ABCComponent abcComponent = DaggerABCComponent.create(); A a = abcComponent.provideA(); B b = abcComponent.provideB(); C c = abcComponent.provideC();
或者:
class Main { @Inject A a; @Inject B b; @Inject C c; public static void main() { ABCComponent abcComponent = DaggerABCComponent.create(); abcComponent.inject(this); } }
Component標籤的module屬性能夠是一個數組,即一個Component實施多個module的注入。引入類D和DModule:
class D { public D() { } } @Module public class DModule { @Provides public D provideD() { return new D(); } }
修改ABCComponent,以下:
@Component(module={ABCModule.class, DModule.class}) public interface ABCComponent { public A provideA(); public B provideB(); public C provideC(); public D provideD(); void inject(Activity mainActivity); }
如上便可實現D對象的注入。
真正實施工程的時候,會將對象以功能分類。例如network相關,DB相關,Util相關的類集中在一塊兒管理。Dagger2爲方便咱們達到這一個目的,在component中引入了dependence這個功能。
例如咱們有以下component
@Component(modules = DModule.class) public interface DComponent { D provideD(); }
假設DComponent負責提供一個對象D。這種能力是項目無關的,咱們把這個Component獨立出來。而後咱們能夠經過@Component的dependence屬性來爲其餘Component引入DComponent的能力。例如:
@Component(modules = ABCModule.class, dependencies = DComponent.class) public interface ABCComponent { A provideA(); B provideB(); C provideC(); D provideD(); void inject(Main main); }
能夠看到,聲明瞭dependencies=DComponent.class之後,provideD方法能夠順利拿到D對象。inject方法也能夠注入D對象。
public class Main { @Inject D d; // inject D by ABCComponent public Main() { DComponent dComponent = DaggerDComponent.create(); D d1 = dComponent.provideD(); // inject D by DComponent ABCComponent abcComponent = DaggerABCComponent .builder() .dComponent(dComponent) .build(); D d2 = abcComponent.provideD(); abcComponent.inject(this); } }
DComponent不知道ABCComponent的存在,故能夠像普通Component那樣子使用。但在使用ABCComponent時,咱們須要顯式地爲ABCComponent注入DComponent對象:
ABCComponent abcComponent = DaggerABCComponent .builder() .dComponent(dComponent) .build();
如上面例子所示,若是要求D對象爲單例,能夠經過@Singleton註解來實現。首先咱們須要在依賴圖中聲明對象是單例的:
@Module public class DModule { @Provides @Singleton public D provideD() { return new D(); } }
DComponent接口也須要聲明:
@Singleton @Component(modules = DModule.class) public interface DComponent { D provideD(); }
如此,當咱們注入D對象時,可保證每次注入的是同一個D對象:
DComponent dComponent = DaggerDComponent.create(); D d1 = dComponent.provideD(); D d2 = dComponent.provideD(); // d1 == d2
這篇文章只是簡單地介紹了Dagger2的基本用法,下一篇打算講Dagger2中的Scope和Subcomponent還在一些別的東西,敬請期待。