- 該系列部落格的最終目標: 搭建 MVP + Dagger2 框架
- 該系列部落格包含以下幾篇內容:
- Dagger 2 系列(一) -- 前奏篇:依賴注入的基本介紹
- Dagger 2 系列(二) -- 基礎篇:@Inject、@Component
- Dagger 2 系列(三) -- 基礎篇:@Module 和@Provides
- Dagger 2 系列(四) -- 基礎篇:@Named 和 @Qualifier
- Dagger 2 系列(五) -- 進階篇:@Scope 和 @Singleton
在這篇文章中你會看到什麼:
@Scope
是什麼@Singleton
是什麼@Scope
和@Component
如何協同作戰。
Dagger2
的學習曲線確實是比較陡的,我認為陡的點一是對 依賴注入(控制反轉)概念的理解,所以有了Dagger 2 系列(一) -- 前奏篇:依賴注入的基本介紹,另外一個就是 對 Scope 的理解,對於此我也是翻看了大量的部落格,大部分部落格看完之後的感受依舊是雲裡霧裡的,自己也是經過了學習、擱淺、再學習的往復過程,同時也看了一些國外的部落格,對 Scope
的概念有了基本的認識。
1. @Scope
我們首先看一下 froer_mcs 在 一文中談到 Scope 能給我們帶來什麼
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).In short - scopes give us “local singletons” which live as long as scope itself.
Annotated dependencies are single-instances but related to component lifecycle (not the whole application).
個人翻譯
Dagger2 中 Scope 機制保證在 Scope 的作用域內類會保持單例。在實際開發中這意味著在 @ApplicationScope 對應的作用域中類的例項物件的生命會像 Application 一樣長,在 @ActivityScope 的作用域內的類例項的生命週期和相應的 Activity 一樣長。(不要想當然的認為 Dagger2 會根據 Scope 註解的字面意義實現相應的類例項的單例效果,實現這樣的效果是需要具體實現的。) 總的來說, Scope 機制會保證在 Scope 的生命週期內實現 "本地單例" 在 Component 的生命週期內,Scope 註解依賴會保證單例。(也就是說,此處的單例是 Component 生命週期內的單例,如果 Component 例項物件重新例項化的,則單例效果失效。)
通過以上的引用和翻譯不知道你是否重新認識了 Scope ,在上文中一個反覆強調的概念:
在 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
的命名沒有任何關係,名字避免使用了容易給人造成疑惑的 ApplicationScope
、ActivityScope
等,而使用了 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
提供 AppleBean
、OrgranBean
例項物件的方法,兩個方法使用 @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());
}
}
複製程式碼
如程式碼所示,在完成注入後分別對 AppleBean
、OrgranBean
分別呼叫了兩次例項,按照上文中 我們對 @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 的例項 -- mFruitComponent
,AppleBean 的兩個例項 -- mAppleBeanA
、mAppleBeanB
,OrgranBean 的兩個例項 -- mOrgranBeanA
、mOrgranBeanB
,發現 mAppleBeanA
和 mAppleBeanB
的雜湊值相同即為 同一個物件、mOrgranBeanA
、mOrgranBeanB
的雜湊值相同即為 同一個物件,驗證了我們對 @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
很明顯 AppleBean
、OrgranBean
重新生成了新的物件,難道不是單例了?難道上文的結論是錯誤的?其實不是這樣的,因為 FruitComponent
生成了新的物件,所以其作用域下的類重新生成了新的例項。
證明了:在 Scope 註解標記 Component 的作用域內生產的例項是單例的。
4. 總結
至此,對 Dagger2
中的 Scope
的理解就如上文所示了,我認為內容比較容易內容理解,而且比較淺顯,不會增添你的疑惑,希望自己的這篇學習記錄可以幫助到看到的所有同學。
以上程式碼你可以在這裡看到 GitHub--Dagger2Demo
參考資料
Dependency injection with Dagger 2 - the API
Dependency injection with Dagger 2 - Custom scopes