Dagger2利器系列二:懶/重加載+Component 的組織關係

目錄

一:懶/重加載

1.1 Dagger2 中的懶加載

1.2 Provider 強制重新加載

二:Component 的組織依賴關係

2.1 前言

2.2 Component 的組織關係

2.2.1 依賴關係

2.2.3 繼承關係

2.3 依賴關係 vs 繼承關係

2.4 SubComponent 的其他問題

2.4.1 抽象工廠方法定義繼承關係

2.4.2 重複的 Module

2.5 總結

參考文章:


一:懶/重加載

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 作用域,FriendComponentManComponent也要用 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);

依賴關係就跟生活中的朋友關係相當,注意事項如下:

  1. 被依賴的 Component 需要把暴露的依賴實例用顯式的接口聲明,如上面的Car car(),我們只能使用朋友願意分享的東西。

  2. 依賴關係中的 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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章