Dagger2系列:
Dagger版本:2.11
繼續(xù)填坑痛悯,這篇簡單講下@Scope吧跌榔,
1.Scope
首先看一下Scope類本身领舰,它是javax.inject包下的一個注解類猪半,先來看它doc文檔中的一些說明:
Identifies scope annotations. A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type. By default, if no scope annotation is present, the injector creates an instance (by injecting the type's constructor), uses the instance for one injection, and then forgets it. If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection. If multiple threads can access a scoped instance, its implementation should be thread safe. The implementation of the scope itself is left up to the injector.
In the following example, the scope annotation @Singleton ensures that we only have one Log instance:
大致意思是Scope是一個識別作用域的注解搀突,例如默認情況下捻爷,沒有標注@Scope注解時吟逝,每次注入依賴時都會創(chuàng)建新的實例沦疾,然后就不管它了(就好比飆車依賴老司機侵蒙,開車前先請一個老司機回來造虎,把他塞到車里,但卻沒加微信纷闺,那么下次飆車就又得重新請一個老司機了)算凿,如果標注了Scope注解,那么注入器可能就會保持這個實例犁功,下次注入需要這個依賴時就可以重用了(留下了老司機的微信氓轰,飆車直接call他,還是這個原汁原味的老司機)浸卦,然后是線程安全的問題署鸡,至于怎么實現(xiàn)是注入框架的事情了
下面是其他的一些說明
- 不能標注多個
- 自定義Scope的用法:
- 只能標注在@interface上
- 要需要標注@Retention(RUNTIME)(表示這是一個運行時的注解)
- 不能有屬性
- 還有兩點沒看懂,大概說的繼承和可標注處的事情吧
- 這個注解可以幫助注入器檢測到依賴標明了作用域而但注入器卻沒標明的情況,對此保守的注入器會拋出錯誤(放在dagger上應該就是Scope不符合的情況下直接會編譯錯誤吧)
文檔中提到了@Singleton靴庆,這是一個標注了@Scope的注解
/**
* Identifies a type that the injector only instantiates once. Not inherited.
*
* @see javax.inject.Scope @Scope
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
從Scope文檔中的示例可以看出时捌,這個Singleton的寫法并沒有什么特殊的,僅僅是在文檔注釋寫了句標記單例的類型
實際上炉抒,Scope就是作為一種類型的標記而已奢讨,而這種標記的目的是為了更好地區(qū)分作用域
2.通過@Scope注解生成的代碼
回到Dagger上,照例上代碼焰薄,這次加上@Singleton拿诸,或者其他的Scope都行
@Singleton
class Member @Inject constructor()
class Target {
@Inject
lateinit var member: Member
}
@Singleton
@Component
interface TargetComponent {
fun inject(target: Target)
}
再看結果
fun test() {
val target1 = Target()
val target2 = Target()
val targetComponent = DaggerTargetComponent.builder().build()
targetComponent.inject(target1)
targetComponent.inject(target2)
Log.e("dagger2", "target1:${target1.member}")
Log.e("dagger2", "target2:${target2.member}")
}
接下來是生成的代碼,和之前的生成的一樣塞茅,唯一不同的是Component亩码,這里我們對比一下
ps:就算換個Scope,生成的也是一樣的凡桥,例如
@Scope
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class MyScope
主要的變化就是原來的Member_Factory.create()
創(chuàng)建出來的Provider被放到DoubleCheck.provider()
轉變?yōu)榱硗庖环NProvider了,那現(xiàn)在看看DoubleCheck.provider()
:
/** Returns a {@link Provider} that caches the value from the given delegate provider. */ public static <T> Provider<T> provider(Provider<T> delegate) { checkNotNull(delegate); if (delegate instanceof DoubleCheck) { /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped * binding, we shouldn't cache the value again. */ return delegate; } return new DoubleCheck<T>(delegate); }
這里創(chuàng)建了一個DoubleCheck的對象缅刽,它實現(xiàn)了Provider接口,構造函數(shù)參數(shù)是原來的Provider蠢络,實際上也就是創(chuàng)建代理
那么看看它的get方法(就是用來提供依賴實例的方法衰猛,來自Provider接口)
public T get() { Object result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) { result = provider.get(); /* Get the current instance and test to see if the call to provider.get() has resulted * in a recursive call. If it returns the same instance, we'll allow it, but if the * instances differ, throw. */ // 由于是使用代理創(chuàng)建的實例,防止遞歸的情況下返回不同的實例刹孔,得做出判斷啡省,不是同一實例的直接拋出異常 Object currentInstance = instance; if (currentInstance != UNINITIALIZED && currentInstance != result) { throw new IllegalStateException("Scoped provider was invoked recursively returning " + "different results: " + currentInstance + " & " + result + ". This is likely " + "due to a circular dependency."); } instance = result; /* Null out the reference to the provider. We are never going to need it again, so we * can make it eligible for GC. */ // 實例創(chuàng)建出來了,Provider沒卵用了髓霞,可以滾蛋了 provider = null; } } } return (T) result; }
嗯卦睹,這就是一個典型的雙重鎖單例,但條件判斷不是用null判斷的方库,這應該說明provider本身允許產生null
最后這個Provider(DoubleCheck)給了Injector结序,注入時調用它的get方法拿出的實例就是同一個實例,于是就實現(xiàn)了復用
但需要注意的是纵潦,這個單例只針對同一個Component實例的情況下徐鹤,畢竟Component本身也是能重復創(chuàng)建的,每個Component都有injector對象邀层,所以說injector里的Provider能復用對象也同樣無法保證單例
基于這個情況返敬,就可以明白了其他dagger2文章里講述的關于生命周期同步(例如和Activity同步,和Fragment同步)是什么情況了寥院,實際上就是控制Componet的生命周期
3.所謂的作用域
下面借鑒一下Dependency injection with Dagger 2 - Custom scopes的例子劲赠,懶得再自己寫了,例子中是java寫的
在這個例子中,把app簡單的分為幾個維度
- Application scope:全局的凛澎,跟隨應用的生命周期
- UserScope:一個用戶從登錄到注銷霹肝,跟隨用戶的行為
- ActivityScope:更隨每個Activity的生命周期
他們之間的包含關系是Application scope->UserScope(不一定有)->ActivityScope
再啰嗦一句,這些維度只是按標注這么區(qū)分而已预厌,代碼中不管理好Compoent生命周期的話仍舊沒卵用
@Singleton
@Component(modules = {AppModule.class, GithubApiModule.class})
public interface AppComponent {
// 省略一些代碼阿迈,就按上面那幾個維度舉例,提供一個UserScope級別和一個ActivityScope級別的組件轧叽,AppComponent 本身就是應用級別的
// 之前的SubComponent寫法苗沧,將會繼承AppComponent的依賴關系
UserComponent plus(UserModule userModule);
// Splash頁面不隸屬與UserComponent的(應用第一頁不用登錄)
SplashActivityComponent plus(SplashActivityModule splashActivityModule);
}
@UserScope
@Subcomponent(modules = {UserModule.class})
public interface UserComponent {
// 之前的SubComponent寫法,將會繼承UserComponent 的依賴關系
// 忘了是啥頁面了炭晒,反正是需要登錄后才能查看的待逞,于是這些子組件就隸屬于UserComponent了
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}
這里得補充一下,UserComponent做為AppComponent的子組件网严,@Scope是可以不一樣的识樱,也就是作用域是不一樣的,但得和前面定義(或者說我們希望)的作用域是相符的震束,就是說子組件的作用域要比父組件小(UserScope用戶登錄到退出的作用域小于ApplicationScope應用全局的作用域)怜庸,從Subcomponet實例的獲取方式也能看出,父組件的實例創(chuàng)建出來后才可能獲得子組件垢村。不明白的請看上一篇文章Dagger2解析-3
接著來看一下組件實例化的地方割疾,例子中放在了GithubClientApplication中
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
// 省略...
@Override
public void onCreate() {
// 省略...
initAppComponent();
}
private void initAppComponent() {
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
// 省略...
}
- 可以看到AppComponent在onCreate就初始化了,初始化方法是私有的嘉栓,也就是只調用一次宏榕。這種管理比較簡單,找個全局單例的工具類侵佃,把這個組件放在里面也是可以的麻昼,方式有多種,只要保證AppComponent在app生命周期內只存在一個實例就行
- UserComponet是提供了一個初始化方法給外部調用(登錄后調用的)馋辈,還提供了release方法用于注銷登錄時調用抚芦,這就是UserScope的生命周期管理了,登錄后調用了
createUserComponent
才存在首有,而注銷后調用了releaseUserComponent
清空引用燕垃,被gc回收后消失,是和登錄注銷行為掛鉤的
最后看一下Activity內部的井联,以RepositoriesListActivity
為例
public class RepositoriesListActivity extends BaseActivity {
// 省略一堆代碼...
// presenter作為activity的依賴
@Inject
RepositoriesListActivityPresenter presenter;
@Override
protected void setupActivityComponent() {
GithubClientApplication.get(this).getUserComponent()
.plus(new RepositoriesListActivityModule(this))
.inject(this);
}
// 省略一堆代碼...
}
從前面的UserComponent里的
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
看出卜壕,在RepositoriesListActivity
里的執(zhí)行注入的是RepositoriesListActivityComponent
ps:RepositoriesListActivity
的依賴以RepositoriesListActivityPresenter presenter
為例,其他的一樣的就不提了
這個組件僅僅是調用inject方法注入presenter后就完事了烙常,并沒保存組件實例轴捎,所以這個組件里的依賴也并沒有緩存presenter鹤盒,也就是它們是跟隨Activity生命周期的,Activity銷毀后侦副,GC回收時會順帶回收presenter(前提是presenter沒泄露出去被其他地方持有)
上面例子的源碼在這GithubClient侦锯,和文章Dependency injection with Dagger 2 - Custom scopes里的有些不同
參考:
-
[Dependency injection with Dagger 2 - Custom scopes]
http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/