dagger2從入門到放棄-Component的繼承體系、區域性單例
前言
dagger2有一個比較重要的特性,就是可以指定依賴在某個相同的生命週期內被注入的是同一個物件。這個和一般的單例不太一樣,普通的單例的生命週期是到應用被kill為止,而dagger2中的單例的生命週期可以和Application、Activity、Fragment...各種不同物件的生命週期保持一致,所以也叫區域性單例,這篇文章就來聊一聊區域性單例
Component的繼承/依賴體系
dagger2中依賴的生命週期是由DaggerXXXComponent進行管理的,所以先來看看Component的繼承/依賴體系
Component繼承/依賴體系的作用
- Component繼承體系是為了讓每一個層級的Component可以只處理當前層級的依賴,而不用關心下層的依賴,當使用到下層依賴時會從父Component中去找
- Dagger雖然沒有硬性規定要如何構建Component的繼承體系,但是按照Application-Activity-Fragment這樣的結構來更加自然和方便,同時也可以更好的和Android元件的生命週期匹配
- 當然如果要為一個Fragment進行依賴注入,你也可以只定義一個Component,將Application,Activity,Fragment作為builder的引數提供給該Component,但是這樣不僅程式碼會較為繁瑣,而且失去了dagger的生命週期管理的能力
Component繼承的三種方式
方式1
使用@Subcomponent註解,在父Component中顯式暴露獲取SubComponent例項的介面
@Subcomponent public interface ActivityComponent2 { void inject(SubComponentActivity2 activity); } @Component public interface AppComponent { //顯式宣告獲取SubComponent的介面 ActivityComponent2 getActivityComponent2(); } //使用父Component獲取SubComponent getAppCompoent().getActivityComponent2().inject(this);
方式2
使用@Subcomponent註解,在父Component使用的Module中用subcomponents屬性指定該Subcomponent
需要在SubComponent中顯式宣告 Subcomponent.Builder
@Subcomponent public interface ActivityComponent3 { void inject(SubComponentActivity3 activity); //在module中指定subcomponents的Component必須顯式地宣告 Subcomponent.Builder @Subcomponent.Builder interface Builder { ActivityComponent3 build(); } } //在@Module的subcomponents屬性中指定SubComponent @Module(subcomponents = ActivityComponent3.class) public class AppModule { }
獲取SubComponent有兩種方式
- 在父Component中顯式的宣告Subcomponent,Builder(這樣其實和方式1差別不大,還多了一步)
@Component(modules = { AppModule.class}) public interface AppComponent { ActivityComponent3.Builder getActivityComponent3Builder(); } //使用父Component獲取Subcomponent.Builder getAppCompoent().getActivityComponent3Builder().build().inject(this);
-
將Subcomponent.Builder看做是父Component提供的一個依賴來處理
- 主要用來實現Activity-Multibinding,後面章節會有介紹,參考文章(ofollow,noindex">在Dagger 2中Activities和Subcomponents的多繫結 )
先寫個比較簡單的將Subcomponent.Builder作為依賴的用法
public class RealApplication{ //直接在Application中注入ActivityComponent3.Builder @Inject ActivityComponent3.Builder mBuilder; ... public ActivityComponent3.Builder getBuilder() { return mBuilder; } } //從Application中獲取Builder完成注入 ((RealApplication)getApplication()).getBuilder().build().inject(this);
官方的例子寫的更好一些:(subcomponents-for-encapsulation )
方式3
子Component使用@Component標記並通過dependencies屬性指定父Component,在父Component顯示宣告子Component中需要的依賴的介面
@Component(dependencies = AppComponent.class) public interface ActivityComponent1 { void inject(SubComponentActivity1 activity); } public interface AppComponent { //如果有component使用dependencies,則需要顯式宣告可以提供的物件 Integer versionCode(); } //將父Component作為引數 DaggerActivityComponent1.builder() .appComponent(getAppCompoent()) .build().inject(this);
這種方式比較不推薦使用,應為底層Component提供的依賴需要手動暴露出來上層才能用,這樣比較麻煩
有個值得的注意的點是顯式暴露的依賴不能多層傳遞,即Component1依賴Component2依賴Component3,都使用dependencies指定父Component;Component3顯式聲明瞭獲取依賴假設是Application application();
,如果Component1需要用到Application,則需要在Component2中也顯式宣告獲取Application的方法
不過這是三種方式中唯一一種從上至下定義依賴關係的方式(在子Component中指定父Component),其他的都是從下至上的(父Component或者父Component的Module中指定子Component),在多個模組的專案中,這是上層模組中Component依賴下層模組中Componnet唯一方式,而需要手動暴露提供依賴的介面也是出於許可權控制的考慮
這種方式有個額外的好處是上層的Componnet可以依賴多個底層Component
@Scope
@Scope是一個標記註解的註解,用來定義生命週期相關的註解
@Scope需要Component和依賴提供者配合才能起作用,對於@Scope註解的依賴,Component會持有第一次建立的依賴,後面注入時都會複用這個依賴的例項,實質上@Scope的目的就是為了讓生成的依賴例項的生命週期與 Component 繫結
如果Component重建了,持有的@Scope的依賴也會重建,所以為了維護區域性單例需要自己維護Component的生命週期
dagger2預設提供了Singleton註解
@Scope @Documented @Retention(RUNTIME) public @interface Singleton {}
參照著寫了ActivityScope,AppScope,FragmentScope,當然可以自己加什麼ViewScope,ViewModelScope等等,只是改個名字而已
//一個例子 @Scope @Documented @Retention(RUNTIME) public @interface ActivityScope { }
@Scope的用法
- 用在@Inject註解構造器的類上,而不是構造器上
@SimpleScope public class SimpleActivityBean extends BaseBean { Activity mSimpleActivity; @Inject public SimpleActivityBean(Activity simpleActivity) { mSimpleActivity = simpleActivity; } }
- 用在@Providers註解的方法上
@Provides @SimpleScope public SimpleModuleBean provideSimpleModuleBean() { return new SimpleModuleBean(); }
-
用在@ContributesAndroidInjector註解的方法上(dagger.android相關,後面章節會提到)
-
用在Component上,與要實現區域性單例的依賴進行繫結
區域性單例的其他用法
不與Componnet繫結的依賴的複用
上面說的區域性單例都是繫結到Componnet中實現複用的,如果只是單純的想減少依賴建立的次數而不關心和哪個
Component繫結,可以使用@Reusable註解
可釋放的區域性單例
使用@Scope註解時,Component 會間接持有依賴例項的引用,使得依賴和Componnet具有相同的生命週期,在android中需要儘可能的減少記憶體佔用,這種情況下可以使用@CanReleaseReferences標記@Scope註解。
總結
如果理解了Component的繼承/依賴體系,其實@Scope比較好理解
- 一個Componnet只能維護一個生命週期,即該Componnet提供的依賴想要具備區域性單例的能力,必須標記和Component相同的@Scope註解
- 具有繼承/依賴關係的不同Component需要使用不同的@Scope註解
- Scope 作用域的本質:Component 間接持有依賴例項的引用,把例項的作用域與 Component 繫結,它們不是同年同月同日生,但是同年同月同日死。
- 實現區域性單例需要在對應的生命週期裡只建立一個Component,例如在Application中AppComponent只建立一次,其他的子Component的建立都基於這一個AppComponent例項完成