目錄
一:懶/重加載
1.1 Dagger2 中的懶加載
智能懶加載,是Dagger2實現高性能的重要舉措之一:在需要的時候纔對成員變量進行初始化,可以大幅縮短應用初始化的時間。
使用方法:用Lazy<T>修飾變量即可。Lazy 是泛型類,接受任何類型的參數。
@Inject
Lazy<Object> object;
拿《Dagger2利器系列一:入門到使用》3.2小節中的demo爲例,只要用Lazy<T>修飾需要被注入的對象即可。
public class Car {
/**
* @Inject:@Inject有兩個作用,一是用來標記需要依賴的變量,以此告訴Dagger2爲它提供依賴
*/
@Inject
Lazy<Engine> engine;
public Car() {
DaggerCarComponent.builder().build().inject(this);
}
public Engine getEngine() {
return this.engine;
}
public static void main(String ... args){
Car car = new Car();
System.out.println(car.getEngine());
}
}
1.2 Provider 強制重新加載
@Singleton 標註實現的單例可以讓我們每次獲取的都是同一個對象(暫不細究全局/局部單例),但有時,我們希望每次都創建一個新的實例,這種情況與 @Singleton 完全相反。Dagger2 通過 Provider 就可以實現。它的使用方法和 Lazy 很類似。
使用方法:用Provider<T>修飾變量即可。Provider是泛型類,接受任何類型的參數。
@Inject
Provider<Object> object;
還是拿《Dagger2利器系列一:入門到使用》3.2小節中的demo爲例,只要用Provider<T>修飾需要被注入的對象即可。
public class Car {
/**
* @Inject:@Inject有兩個作用,一是用來標記需要依賴的變量,以此告訴Dagger2爲它提供依賴
*/
@Inject
Provider<Engine> engine;
public Car() {
DaggerCarComponent.builder().build().inject(this);
}
public Engine getEngine() {
return this.engine;
}
public static void main(String ... args){
Car car = new Car();
System.out.println(car.getEngine());
}
}
但是,需要注意的是 Provider 所表達的重新加載是說每次重新執行 Module 相應的 @Provides 方法,如果這個方法本身每次返回同一個對象,那麼每次調用 get() 的時候,對象也會是同一個。
二:Component 的組織依賴關係
2.1 前言
Component 的組織依賴關係主要參考了Dagger 2 完全解析(三),Component 的組織關係與 SubComponent這篇文章,我覺得寫的很好,例子很棒,自己寫下來應該差不多,所以這一小節主要以該篇文章內容結構爲主體來寫,建議大家也看看。進入正文。
在實際項目中,有多個需要注入依賴的對象,也就是說會有多個 Component,它們之間會有相同的依賴,那麼該如何處理它們之間的關係呢?以下面這個場景爲例子:
public class Man {
@Inject
Car car;
public void goWork() {
...
car.go();
...
}
}
public class Friend {
@Inject
Car car; // 車是向 Man 借的
public void goSightseeing() {
...
car.go();
...
}
}
項目場景如下:Man 有一輛車,Friend 沒有車,但是他可以借 Man 的車出去玩,但提供 Car 實例的,是CarModule不變。
這種情況下我們
該怎麼設計 Component 呢?很多人第一時間會這麼設計:
// @ManScope 和 @FriendScope 都是自定義的作用域
@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
...
}
@FriendScope
@Component(modules = CarModule.class)
public interface FriendComponent {
...
}
這種做法最簡單,ManComponent和FriendComponent需要的car都在各自modules
中提供了,也就是說:Man和Friends都有CarModule去提供car。所以這時,發現問題了嘛?這個car已經不是Man他的car了!
問題:
(1)有時依賴實例需要共享,例如上面場景中,Friend 的 car 是向 Man 借的,所以 FriendComponent
應該使用ManComponent
中的 car 實例。
(2)Scope 作用域容易失效,例如 CarModule 的provideCar()
使用 @Singleton 作用域,FriendComponent
和ManComponent
也要用 Singleton 標註,但它們都會持有一個car 實例。
所以 FriendComponent 需要依賴 ManComponent 提供的 car 實例,這就是 Component 組織關係中的一種:依賴關係。
2.2 Component 的組織關係
在 Dagger 2 中 Component 的組織關係分爲兩種:
-
依賴關係:一個 Component 依賴其他 Compoent ,以獲得其中公開的依賴實例,用 Component 中的
dependencies
聲明。 -
繼承關係:一個 Component 繼承(擴展)其他的 Component, 以獲得其他的Component中的依賴,SubComponent 就是繼承關係的體現。
2.2.1 依賴關係
Friend 與 Man 場景中的依賴關係圖:
具體的實現代碼:
@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
void inject(Man man);
Car car(); //必須向外提供 car 依賴實例的接口,表明 Man 可以借 car 給別人
}
@FriendScope
@Component(dependencies = ManComponent.class)
public interface FriendComponent {
void inject(Friend friend);
}
注:因爲 FriendComponent 和 ManComponent 是依賴關係,所以其中一個聲明瞭作用域的話,另外一個也必須聲明。而且它們的 Scope 不能相同,ManComponent 的生命週期 >= FriendComponent 的。FriendComponent 的 Scope 不能是 @Singleton,因爲 Dagger 2 中 @Singleton 的 Component 不能依賴其他的 Component。
編譯時生成的代碼中 DaggerFriendComponent 的 Provider<Car>
實現中會用到manComponent.car()
來提供 car 實例,如果 ManComponent 沒有向外提供 car 實例的接口的話,DaggerFriendComponent 就會注入失敗。
依賴注入:
ManComponent manComponent = DaggerManComponent.builder()
.build();
FriendComponent friendComponent = DaggerFriendComponent.builder()
.manComponent(manComponent)
.build();
friendComponent.inject(friend);
依賴關係就跟生活中的朋友關係相當,注意事項如下:
-
被依賴的 Component 需要把暴露的依賴實例用顯式的接口聲明,如上面的
Car car()
,我們只能使用朋友願意分享的東西。 -
依賴關係中的 Component 的 Scope 不能相同,因爲它們的生命週期不同。
2.2.3 繼承關係
繼承關係跟面向對象中的繼承的概念有點像,SubComponent 稱爲子 Component,類似於平常說的子類。下面先看看下面這個場景:
public class Man {
@Inject
Car car;
...
}
public class Son {
@Inject
Car car;
@Inject
Bike bike;
}
Son 可以開他爸爸 Man 的車 car,也可以騎自己的自行車 bike。依賴關係圖:
上圖中 SonComponent 在 ManComponent 之中,SonComponent 子承父業,可以訪問 parent Component 的依賴,而 ManComponent 只知道 SonComponent 是它的 child Component,可以訪問 SubComponent.Builder,卻無法訪問 SubComponent 中的依賴。
@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
void inject(Man man); // 繼承關係中不用顯式地提供暴露依賴實例的接口
}
@SonScope
@SubComponent(modules = BikeModule.class)
public interface SonComponent {
void inject(Son son);
@Subcomponent.Builder
interface Builder { // SubComponent 必須顯式地聲明 Subcomponent.Builder,parent Component 需要用 Builder 來創建 SubComponent
SonComponent build();
}
}
@SubComponent
的寫法與@Component
一樣,只能標註接口或抽象類。與依賴關係一樣,SubComponent 與 parent Component 的 Scope 不能相同,只是 SubComponent 表明它是繼承擴展某 Component 的。
怎麼表明一個 SubComponent 是屬於哪個 parent Component 的呢?只需要在 parent Component 依賴的 Module 中的subcomponents
加上 SubComponent 的 class,然後就可以在 parent Component 中請求 SubComponent.Builder。
@Module(subcomponents = SonComponent.class)
public class CarModule {
@Provides
@ManScope
static Car provideCar() {
return new Car();
}
}
@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
void injectMan(Man man);
SonComponent.Builder sonComponent(); // 用來創建 Subcomponent
}
SubComponent 編譯時不會生成 DaggerXXComponent,需要通過 parent Component 的獲取 SubComponent.Builder 方法獲取 SubComponent 實例。
ManComponent manComponent = DaggerManComponent.builder()
.build();
SonComponent sonComponent = manComponent.sonComponent()
.build();
sonComponent.inject(son);
繼承關係和依賴關係最大的區別就是:繼承關係中不用顯式地提供依賴實例的接口,SubComponent 繼承 parent Component 的所有依賴。
2.3 依賴關係 vs 繼承關係
相同點:
-
兩者都能複用其他 Component 的依賴
-
有依賴關係和繼承關係的 Component 不能有相同的 Scope
區別:
-
依賴關係中被依賴的 Component 必須顯式地提供公開依賴實例的接口,而 SubComponent 默認繼承 parent Component 的依賴。
-
依賴關係會生成兩個獨立的 DaggerXXComponent 類,而 SubComponent 不會生成 獨立的 DaggerXXComponent 類。
在 Android 開發中,Activity 是 App 運行中組件,Fragment 又是 Activity 一部分,這種組件化思想適合繼承關係,所以在 Android 中一般使用 SubComponent。
2.4 SubComponent 的其他問題
2.4.1 抽象工廠方法定義繼承關係
除了使用 Module 的subcomponents
屬性定義繼承關係,還可以在 parent Component 中聲明返回 SubComponent 的抽象工廠方法來定義:
@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
void injectMan(Man man);
SonComponent sonComponent(); // 這個抽象工廠方法表明 SonComponent 繼承 ManComponent
}
這種定義方式不能很明顯地表明繼承關係,一般推薦使用 Module 的subcomponents
屬性定義。
2.4.2 重複的 Module
當相同的 Module 注入到 parent Component 和它的 SubComponent 中時,則每個 Component 都將自動使用這個 Module 的同一實例。也就是如果在 SubComponent.Builder 中調用相同的 Module 或者在返回 SubComponent 的抽象工廠方法中以重複 Module 作爲參數時,會出現錯誤。(前者在編譯時不能檢測出,是運行時錯誤)
@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
ComponentTwo componentTwo(RepeatedModule repeatedModule); // 編譯時報錯
ComponentThree.Builder componentThreeBuilder();
}
@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentTwo { ... }
@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentThree {
@Subcomponent.Builder
interface Builder {
Builder repeatedModule(RepeatedModule repeatedModule);
ComponentThree build();
}
}
DaggerComponentOne.create().componentThreeBuilder()
.repeatedModule(new RepeatedModule()) // 運行時報錯 UnsupportedOperationException!
.build();
2.5 總結
Component 之間共用相同依賴時,可以有兩種組織關係:依賴關係與繼承關係。在 Android 開發中,一般使用繼承關係,以 AppComponent 作爲 root Component,AppComponent 一般還會使用 @Singleton 作用域,而 ActivityComponent 爲 SubComponent。
參考文章:
dagger2從入門到放棄-Component的繼承體系、局部單例
Dagger 2 完全解析(三),Component 的組織關係與 SubComponent