Dagger 2 系列(五) -- 進階篇:@Scope 和 @Singleton

Dagger2

  • 該系列博客的最終目標: 搭建 MVP + Dagger2 框架
  • 該系列博客包含如下幾篇內容:
  1. Dagger 2 系列(一) -- 前奏篇:依賴注入的基本介紹
  2. Dagger 2 系列(二) -- 基礎篇:@Inject、@Component
  3. Dagger 2 系列(三) -- 基礎篇:@Module 和@Provides
  4. Dagger 2 系列(四) -- 基礎篇:@Named 和 @Qualifier
  5. Dagger 2 系列(五) -- 進階篇:@Scope 和 @Singleton

在這篇文章中你會看到什麼:java

  1. @Scope 是什麼
  2. @Singleton 是什麼
  3. @Scope@Component 如何協同做戰。

Dagger2 的學習曲線確實是比較陡的,我認爲陡的點一是對 依賴注入(控制反轉)概念的理解,因此有了Dagger 2 系列(一) -- 前奏篇:依賴注入的基本介紹,另一個就是 對 Scope 的理解,對於此我也是翻看了大量的博客,大部分博客看完以後的感覺依舊是雲裏霧裏的,本身也是通過了學習、擱淺、再學習的往復過程,同時也看了一些國外的博客,對 Scope 的概念有了基本的認識。git

1. @Scope

咱們首先看一下 froer_mcs 一文中談到 Scope 能給咱們帶來什麼github

  • In Dagger 2 scopes mechanism cares about keeping single instance of class as long as its scope exists. In practice it means that instances scoped in @ApplicationScope lives as long as Application object. @ActivityScope keeps references as long as Activity exists (for example we can share single instance of any class between all fragments hosted in this Activity).api

  • In short - scopes give us 「local singletons」 which live as long as scope itself.bash

  • Annotated dependencies are single-instances but related to component lifecycle (not the whole application).app

我的翻譯框架

Dagger2 中 Scope 機制保證在 Scope 的做用域內類會保持單例。在實際開發中這意味着在 @ApplicationScope 對應的做用域中類的實例對象的生命會像 Application 同樣長,在 @ActivityScope 的做用域內的類實例的生命週期和相應的 Activity 同樣長。(不要想固然的認爲 Dagger2 會根據 Scope 註解的字面意義實現相應的類實例的單例效果,實現這樣的效果是須要具體實現的。) 總的來講, Scope 機制會保證在 Scope 的生命週期內實現 "本地單例" 在 Component 的生命週期內,Scope 註解依賴會保證單例。(也就是說,此處的單例是 Component 生命週期內的單例,若是 Component 實例對象從新實例化的,則單例效果失效。)ide

經過以上的引用和翻譯不知道你是否從新認識了 Scope ,在上文中一個反覆強調的概念:post

在 Dagger2 中 Scope 機制能夠保證在 Scope 標記的 Component 做用域內 ,類會保持單例 。 (敲黑板,這句話很重要)學習

2. @Singleton

重申一遍:

**在 Dagger2 中 Scope 機制能夠保證在 Scope 標記的 Component 做用域內 ,類會保持單例 **

若是理解了這句話,那麼回過頭來看 @Singleton 這個註解,是否是有一種豁然開朗的感受。並非只有 @Singleton 註解標記的相關類生產的實例是單例的,是全部的 Scope(自定義 Scope) 標記的相關類生產的實例 都是單例 的,只不過這個單例是有條件的 -- 在 Scope 註解標記 Component 的做用域內生產的實例是單例的

Scope 機制下的單例其實和 @Singleton 的字面意義 沒有半毛錢關係,當初本身就是被這種錯誤的思想誤導了很長時間。其實若是你願意你,能夠把 @Singleton 換成任意單詞,什麼 @Dog@Cat@XXx 均可以,你只要保證這個註解標記的 Component 在 App 進程中爲單例的,而且獲得正確的實現(被正確的標記到 類構造器 或 Module 中的 @Provides 標記的方法),那麼它對應生成的類實例就是 單例 的。

@Singleton 之因此被默認實現,只是由於這可讓人根據它的字面意思,知道被他標記的相關生成的類實例爲單例,這符合了 Java 的命名規範。

3. 示例代碼

上面談到的全都是理論,那麼咱們就是用相應的代碼來驗證他們。

  • 自定義 Scope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface AnyOneScope {
}
複製代碼

這裏爲了代表最後的 單例Scope 的命名沒有任何關係,名字避免使用了容易給人形成疑惑的 ApplicationScopeActivityScope 等,而使用了 AnyOneScope,可是其實這些名字都是無所謂的 。

  • POJO -- AppleBean
public class AppleBean {

    private String color;
    private int weight;

    public AppleBean() {
        Log.e("TAG", "AppleBean");
    }
}
複製代碼
  • POJO -- OrgranBean
public class OrgranBean {

    private String color;
    private int weight;

    public OrgranBean() {
        Log.e("TAG", "OrgranBean");
    }
}
複製代碼
  • Module
@Module
public class FruitModule {

    @AnyOneScope
    @Provides
    AppleBean provideApple() {
        return new AppleBean();
    }

    @AnyOneScope
    @Provides
    OrgranBean provideOrgran() {
        return new OrgranBean();
    }
}
複製代碼

Module 提供 AppleBeanOrgranBean 實例對象的方法,兩個方法使用 @AnyOneScope 進行註解。

  • Component
@AnyOneScope
@Component(modules = {FruitModule.class})
public interface FruitComponent {
    void inject(FuriteScopeActivity mTestScopeActivity);
}
複製代碼
  • 目標類 (注入類)
public class FuriteScopeActivity extends AppCompatActivity {

    @Inject
    AppleBean mAppleBeanA;
    @Inject
    AppleBean mAppleBeanB;
    @Inject
    OrgranBean mOrgranBeanA;
    @Inject
    OrgranBean mOrgranBeanB;
    FruitComponent mFruitComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_scope);
        mFruitComponent = DaggerFruitComponent.builder().fruitModule(new FruitModule()).build();
        mFruitComponent.inject(this);// 完成注入,沒有這句話是不行的

        Log.e("TAG", "mFruitComponent1:" + mFruitComponent.toString());
        Log.e("TAG", "mAppleBeanA:" + mAppleBeanA.toString());
        Log.e("TAG", "mAppleBeanB:" + mAppleBeanB.toString());
        Log.e("TAG", "mOrgranBeanA:" + mOrgranBeanA.toString());
        Log.e("TAG", "mOrgranBeanB:" + mOrgranBeanB.toString());
    }
}
複製代碼

如代碼所示,在完成注入後分別對 AppleBeanOrgranBean 分別調用了兩次實例,按照上文中 咱們對 @Scope 的理解,那麼在這裏兩個類分別生成的類實例爲同一個,下面咱們運行代碼,查看日誌來驗證一下。

打印日誌以下:

E/TAG: mFruitComponent1:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@7c6e469
    mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f
    mOrgranBeanB:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f

複製代碼

能夠看到日誌分別打印了 FruitComponent 的實例 -- mFruitComponentAppleBean 的兩個實例 -- mAppleBeanAmAppleBeanBOrgranBean 的兩個實例 -- mOrgranBeanAmOrgranBeanB,發現 mAppleBeanAmAppleBeanB 的哈希值相同即爲 同一個對象mOrgranBeanAmOrgranBeanB 的哈希值相同即爲 同一個對象,驗證了咱們對 @Scope 的認識 -- 實現單例

同時,爲了驗證 單例 是有做用域的 -- Component 的做用域內,在 FuriteScopeActivity 添加如下方法:

public void jump(View view) {
        mFruitComponent = DaggerFruitComponent.builder().fruitModule(new FruitModule()).build();
        mFruitComponent.inject(this);// 完成注入,沒有這句話是不行的

        Log.e("TAG", "mFruitComponent2:" + mFruitComponent.toString());
        Log.e("TAG", "mAppleBeanA:" + mAppleBeanA.toString());
        Log.e("TAG", "mAppleBeanB:" + mAppleBeanB.toString());
        Log.e("TAG", "mOrgranBeanA:" + mOrgranBeanA.toString());
        Log.e("TAG", "mOrgranBeanB:" + mOrgranBeanB.toString());
    }
複製代碼

從新運行程序,觀察打印日誌:

  • 運行程序後觸發的日誌信息:
09-21 14:50:54.903 14184-14184/com.example.administrator.dagger2demo E/TAG: AppleBean
    OrgranBean
    mFruitComponent1:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@7c6e469
    mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@b7d6eee
    mOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f
09-21 14:50:54.904 14184-14184/com.example.administrator.dagger2demo E/TAG: mOrgranBeanB:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@648ef8f
複製代碼

生成的對象和哈希值的對應關係爲:

  • DaggerFruitComponent --> 7c6e469
  • AppleBean --> b7d6eee
  • OrgranBean --> 648ef8f
  • 觸發 jump() 方法後的日誌信息以下:
09-21 14:53:37.624 14184-14184/com.example.administrator.dagger2demo E/TAG: AppleBean
    OrgranBean
    mFruitComponent2:com.example.administrator.dagger2demo.practiceFifth.DaggerFruitComponent@8196f9e
    mAppleBeanA:com.example.administrator.dagger2demo.practiceFifth.AppleBean@50ada7f
    mAppleBeanB:com.example.administrator.dagger2demo.practiceFifth.AppleBean@50ada7f
    mOrgranBeanA:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@16bea4c
    mOrgranBeanB:com.example.administrator.dagger2demo.practiceFifth.OrgranBean@16bea4c
複製代碼

生成的對象和哈希值的對應關係爲:

  • FruitComponent --> 8196f9e
  • AppleBean --> 50ada7f
  • OrgranBean --> 16bea4c

很明顯 AppleBeanOrgranBean 從新生成了新的對象,難道不是單例了?難道上文的結論是錯誤的?其實不是這樣的,由於 FruitComponent 生成了新的對象,因此其做用域下的類從新生成了新的實例。 證實了:在 Scope 註解標記 Component 的做用域內生產的實例是單例的。

4. 總結

至此,對 Dagger2 中的 Scope 的理解就如上文所示了,我認爲內容比較容易內容理解,並且比較淺顯,不會增添你的疑惑,但願本身的這篇學習記錄能夠幫助到看到的全部同窗。

以上代碼你能夠在這裏看到 GitHub--Dagger2Demo


參考資料

Dependency injection with Dagger 2 - the API

Dependency injection with Dagger 2 - Custom scopes

相關文章
相關標籤/搜索