Dagger2 知識(shí)梳理(4) - @Scope 注解的使用

Dagger2 系列文章

Dagger2 知識(shí)梳理(1) - Dagger2 依賴注入的兩種方式
Dagger2 知識(shí)梳理(2) - @Qulifier 和 @Named 解決依賴注入迷失
Dagger2 知識(shí)梳理(3) - 使用 dependencies 和 @SubComponent 完成依賴注入
Dagger2 知識(shí)梳理(4) - @Scope 注解的使用


一、前言

對(duì)于@Scope注解,很多同學(xué)都疑惑,今天我們就來(lái)了解一下@Scope相關(guān)的知識(shí),這里將會(huì)分為兩部分介紹:

  • 單個(gè)Component情況下@Scope的作用
  • 組織多個(gè)Component情況下對(duì)于@Scope的限制

首先,我們需要了解@Scope其實(shí)是一個(gè) 元注解,它和我們?cè)?Dagger2 知識(shí)梳理(2) - @Qulifier 和 @Named 解決依賴注入迷失 一文中介紹的@Qualifier一樣,是用于 描述注解的注解,關(guān)于元注解更多的知識(shí)可以參考之前的這篇文章 Java&Android 基礎(chǔ)知識(shí)梳理(1) - 注解

@Scope所描述的注解用于兩個(gè)地方:

  • Component
  • Module中用于創(chuàng)建實(shí)例的provideXXX方法

而我們經(jīng)常看見(jiàn)的@Singleton注解其實(shí)就是用@Scope描述的注解,雖然它的表面意思是“單例”,但是我們后面會(huì)看到它和單例其實(shí)并沒(méi)有必然的關(guān)系。

二、單個(gè) Component 情況下 @Scope 的使用

@Scope描述的注解類似于下面這樣,這里的PerScopeActivity就是用@Scope描述的注解:

@Documented
@Retention(RUNTIME)
@Scope
public @interface PerScopeActivity {}

有可能會(huì)用到該注解的有兩個(gè)地方:

  • Component
@Component(dependencies = {ScopeAppComponent.class}, modules = {ScopeActivityModule.class})
@PerScopeActivity
public interface ScopeActivityComponent {
    public void inject(ScopeActivity scopeActivity);
    ScopeFragmentComponent scopeFragmentComponent();
}
  • Module中用于創(chuàng)建實(shí)例的provideXXX方法
@Module
public class ScopeActivityModule {

    @Provides
    @PerScopeActivity
    public ScopeActivitySharedData provideScopeActivityData() {
        return new ScopeActivitySharedData();
    }

    @Provides
    public ScopeActivityNormalData provideScopeActivityNormalData() {
        return new ScopeActivityNormalData();
    }
}

在單個(gè)Component情況下使用@Scope有以下幾點(diǎn)說(shuō)明:

  • 如果在ModuleprovideXXX方法上加上了@Scope聲明,那么在與他關(guān)聯(lián)的Component上也必須加上相同的@Scope聲明
  • 如果Component加上了@Scope聲明,provideXXX,那么和Component不加聲明的情況相同。
  • 當(dāng)ModuleprovideXXX方法和Component都加上了@Scope聲明,那么在Component實(shí)例的生命周期內(nèi),只會(huì)創(chuàng)建一個(gè)由provideXXX方法返回的實(shí)例。也就是說(shuō),該Component會(huì)持有之前通過(guò)provideXXX方法創(chuàng)建的實(shí)例的引用,如果之前創(chuàng)建過(guò),那么就不再調(diào)用ModuleprovideXXX去創(chuàng)建新的實(shí)例,而是直接返回它之前持有的那一份。

上面的例子中,我們通過(guò)ScopeActivityModule創(chuàng)建了兩種類型的數(shù)據(jù),provideScopeActivityData()方法上加上了@PerScopeActivity,而提供ScopeActivityNormalDataprovideScopeActivityNormalData()方法則沒(méi)有,后面我們將會(huì)看到,如果在目標(biāo)類中使用同一個(gè)ScopeActivityComponent注入,而有多個(gè)ScopeActivitySharedData變量的情況下它們指向的是同一塊內(nèi)存地址,而ScopeActivityNormalData則會(huì)指向不同的內(nèi)存地址。

三、組織多個(gè) Component 情況下對(duì)于 @Scope 的限制

對(duì)于單個(gè)Component還比較好理解,但是在組織多個(gè)Component的情況下就有些復(fù)雜了,這里的“組織”就是我們?cè)谇耙黄?Dagger2 知識(shí)梳理(3) - 使用 dependencies 和 @SubComponent 完成依賴注入 談到的 依賴方式繼承方式

  • 在依賴或者繼承的組織方式中,如果其中一個(gè)Component聲明了@Scope,那么其它的Component也需要聲明。
  • 在依賴關(guān)系中,被依賴的Component和需要依賴的Component@Scope不能相同
  • 在依賴關(guān)系中,需要依賴的Component@Scope不可以為@Singleton
  • 在組織關(guān)系中,子Component@Scope不可以和父Component@Scope相同:
  • 在組織關(guān)系中,如果父Component@Scope不為@Singleton,那么子Component@Scope可以為@Singleton

這些限制是由Dagger2在編譯時(shí)去檢查的,其目的是保證使用者不要對(duì)@Scope產(chǎn)生濫用的現(xiàn)象,因?yàn)?code>@Scope的目的是 在特定作用域內(nèi)控制被注入實(shí)例的復(fù)用

四、示例

為了讓大家更好的驗(yàn)證上面關(guān)于@Scope的解釋,下面用一個(gè)Demo來(lái)演示,完整代碼可以從 Dagger2Sample 的第四章獲取,這個(gè)Demo包括三個(gè)大部分:

(1) ScopeApp

對(duì)應(yīng)于我們平時(shí)的Application類,并提供了全局的ScopeAppData類,在其ScopeAppComponent上有@Singleton注解。

@Singleton
@Component(modules = {ScopeAppModule.class})
public interface ScopeAppComponent {
    public ScopeAppData getScopeAppData(); //如果它被其它的Component依賴,那么需要聲明getXXX方法。
}
@Module
public class ScopeAppModule {

    @Provides
    @Singleton
    public ScopeAppData provideScopeAppData() {
        return new ScopeAppData();
    }
}

(2) ScopeActivity

對(duì)應(yīng)于一個(gè)主頁(yè)面,其內(nèi)部包含了ScopeActivitySharedDataScopeActivityNormalData,前者在ScopeActivityComponent的生命周期內(nèi)保持唯一性,并帶有PerScopeActivity注解。

@Component(dependencies = {ScopeAppComponent.class}, modules = {ScopeActivityModule.class})
@PerScopeActivity
public interface ScopeActivityComponent {
    public void inject(ScopeActivity scopeActivity);
    ScopeFragmentComponent scopeFragmentComponent();
}
@Module
public class ScopeActivityModule {

    @Provides
    @PerScopeActivity
    public ScopeActivitySharedData provideScopeActivityData() {
        return new ScopeActivitySharedData();
    }

    @Provides
    public ScopeActivityNormalData provideScopeActivityNormalData() {
        return new ScopeActivityNormalData();
    }
}

(3) ScopeFragment

對(duì)于于Activity下的一個(gè)子界面,它和ScopeActivityComponent是繼承關(guān)系,并帶有@PerScopeFragment注解:

@Subcomponent(modules = {ScopeFragmentModule.class})
@PerScopeFragment
public interface ScopeFragmentComponent {
    public void inject(ScopeFragment scopeFragment);
}
@Module
public class ScopeFragmentModule {

    @Provides
    @PerScopeFragment
    public ScopeFragmentData provideScopeFragmentData() {
        return new ScopeFragmentData();
    }
}

以上三個(gè)部分的關(guān)系為:

  • ScopeActivityComponent依賴于ScopeAppComponent
  • ScopeFragmentComponent繼承于ScopeActivityComponent
  • 它們的Module上都有用@Scope描述的注解:@Singleton@PerScopeActivity@PerScopeFragment

示例驗(yàn)證

通過(guò)這個(gè)例子可以覆蓋到上面我們介紹的所有場(chǎng)景,大家可以直接在Github上查看,也可以clone下來(lái),進(jìn)行修改驗(yàn)證。

ActivityFragment中,我們打印出變量的地址來(lái)驗(yàn)證前面的結(jié)論:

  • App
public class ScopeApp extends Application {

    private ScopeAppComponent mScopeAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mScopeAppComponent = DaggerScopeAppComponent.builder().scopeAppModule(new ScopeAppModule()).build();
    }

    public ScopeAppComponent getAppComponent() {
        return mScopeAppComponent;
    }
}
  • Activity
public class ScopeActivity extends AppCompatActivity {

    private static final String TAG = ScopeActivity.class.getSimpleName();

    private ScopeActivityComponent mScopeActivityComponent;

    @Inject
    ScopeAppData mScopeAppData;

    @Inject
    ScopeActivitySharedData mScopeActivitySharedData1;

    @Inject
    ScopeActivitySharedData mScopeActivitySharedData2;

    @Inject
    ScopeActivityNormalData mScopeActivityNormalData1;

    @Inject
    ScopeActivityNormalData mScopeActivityNormalData2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scope);
        getScopeActivityComponent().inject(this);
        TextView tvData = (TextView) findViewById(R.id.tv_scope_activity);
        String result = "[ScopeActivity Space] \n mScopeAppData=" + mScopeAppData
                + "\n\n" + "mScopeActivitySharedData1=" + mScopeActivitySharedData1
                + "\n\n" + "mScopeActivitySharedData2=" + mScopeActivitySharedData2
                + "\n\n" + "mScopeActivityNormalData1=" + mScopeActivityNormalData1
                + "\n\n" + "mScopeActivityNormalData2=" + mScopeActivityNormalData2;
        tvData.setText(result);
    }

    public ScopeActivityComponent getScopeActivityComponent() {
        if (mScopeActivityComponent == null) {
            ScopeAppComponent scopeAppComponent = ((ScopeApp) getApplication()).getAppComponent();
            mScopeActivityComponent = DaggerScopeActivityComponent.builder().scopeAppComponent(scopeAppComponent).build();
        }
        return mScopeActivityComponent;
    }
}
  • Fragment
public class ScopeFragment extends Fragment {

    private ScopeActivity mScopeActivity;

    @Inject
    ScopeAppData mScopeAppData;

    @Inject
    ScopeActivitySharedData mScopeActivitySharedData;

    @Inject
    ScopeActivityNormalData ScopeActivityNormalData;

    @Inject
    ScopeFragmentData mScopeFragmentData;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mScopeActivity = (ScopeActivity) context;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_scope, container, false);
        mScopeActivity.getScopeActivityComponent().scopeFragmentComponent().inject(this);
        TextView tv = (TextView) rootView.findViewById(R.id.tv_scope_fragment);
        String result = "[ScopeFragment Space] \n mScopeAppData=" + mScopeAppData
                + "\n\n" + "mScopeActivitySharedData1=" + mScopeActivitySharedData
                + "\n\n" + "ScopeActivityNormalData=" + ScopeActivityNormalData
                + "\n\n" + "mScopeFragmentData=" + mScopeFragmentData;
        tv.setText(result);
        return rootView;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

結(jié)果為:



由上面例子中的現(xiàn)象,可以總結(jié)出以下幾點(diǎn):

  • ScopeAppData:該數(shù)據(jù)是由ScopeAppModule提供的,而它加上了@Singleton注解,并且我們調(diào)用的是同一個(gè)對(duì)象,因此在ActivityFragment中地址相同。
  • ScopeActivitySharedData:在它的provide方法上,我們加上了@PerScopeActivity注解,因此在ActivityFragment中,它的地址相同。
  • ScopeActivityNormalData:雖然在提供它的ScopeActivityModule中加上了@PerScopeActivity注解,但是在provide方法上沒(méi)有聲明,因此無(wú)論是在Activity,還是在Fragment中,都是指向不同的地址。
  • ScopeFragmentData:用于演示如何通過(guò)繼承的方式,來(lái)實(shí)現(xiàn)依賴注入。

更多文章,歡迎訪問(wèn)我的 Android 知識(shí)梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容