Dagger2解析4-Scope

Dagger2系列:

  1. Dagger2解析-1
  2. Dagger2解析2-Component的依賴關系
  3. Dagger2解析3-SubComponent

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
左邊是帶@Singleton標注的蟀伸,右邊的是不帶的

主要的變化就是原來的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寫的

摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

在這個例子中,把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里的有些不同

參考:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秦驯,隨后出現(xiàn)的幾起案子尺碰,更是在濱河造成了極大的恐慌,老刑警劉巖译隘,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亲桥,死亡現(xiàn)場離奇詭異,居然都是意外死亡固耘,警方通過查閱死者的電腦和手機题篷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厅目,“玉大人番枚,你說我怎么就攤上這事∷鸱螅” “怎么了葫笼?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拗馒。 經(jīng)常有香客問我渔欢,道長,這世上最難降的妖魔是什么瘟忱? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮苫幢,結果婚禮上访诱,老公的妹妹穿的比我還像新娘。我一直安慰自己韩肝,他們只是感情好触菜,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哀峻,像睡著了一般涡相。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剩蟀,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天催蝗,我揣著相機與錄音,去河邊找鬼育特。 笑死丙号,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播犬缨,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喳魏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怀薛?” 一聲冷哼從身側響起刺彩,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枝恋,沒想到半個月后创倔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡鼓择,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年三幻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呐能。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡念搬,死狀恐怖,靈堂內的尸體忽然破棺而出摆出,到底是詐尸還是另有隱情朗徊,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布偎漫,位于F島的核電站爷恳,受9級特大地震影響,放射性物質發(fā)生泄漏象踊。R本人自食惡果不足惜温亲,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杯矩。 院中可真熱鬧栈虚,春花似錦、人聲如沸史隆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泌射。三九已至粘姜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熔酷,已是汗流浹背孤紧。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纯陨,地道東北人坛芽。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓留储,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咙轩。 傳聞我的和親對象是個殘疾皇子获讳,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內容