一、前言
對於@Scope
註解,很多同學都疑惑,今天我們就來了解一下@Scope
相關的知識,這裡將會分為兩部分介紹:
- 單個
Component
情況下@Scope
的作用 - 組織多個
Component
情況下對於@Scope
的限制
首先,我們需要了解@Scope
其實是一個 元註解,它和我們在 Dagger2 知識梳理(2) - @Qulifier 和 @Named 解決依賴注入迷失 一文中介紹的@Qualifier
一樣,是用於 描述註解的註解,關於元註解更多的知識可以參考之前的這篇文章 Java&Android 基礎知識梳理(1) - 註解。
@Scope
所描述的註解用於兩個地方:
Component
類Module
中用於建立例項的provideXXX
方法
而我們經常看見的@Singleton
註解其實就是用@Scope
描述的註解,雖然它的表面意思是“單例”,但是我們後面會看到它和單例其實並沒有必然的關係。
二、單個 Component 情況下 @Scope 的使用
@Scope
描述的註解類似於下面這樣,這裡的PerScopeActivity
就是用@Scope
描述的註解:
@Documented
@Retention(RUNTIME)
@Scope
public @interface PerScopeActivity {}
複製程式碼
有可能會用到該註解的有兩個地方:
Component
類
@Component(dependencies = {ScopeAppComponent.class}, modules = {ScopeActivityModule.class})
@PerScopeActivity
public interface ScopeActivityComponent {
public void inject(ScopeActivity scopeActivity);
ScopeFragmentComponent scopeFragmentComponent();
}
複製程式碼
Module
中用於建立例項的provideXXX
方法
@Module
public class ScopeActivityModule {
@Provides
@PerScopeActivity
public ScopeActivitySharedData provideScopeActivityData() {
return new ScopeActivitySharedData();
}
@Provides
public ScopeActivityNormalData provideScopeActivityNormalData() {
return new ScopeActivityNormalData();
}
}
複製程式碼
在單個Component
情況下使用@Scope
有以下幾點說明:
- 如果在
Module
的provideXXX
方法上加上了@Scope
宣告,那麼在與他關聯的Component
上也必須加上相同的@Scope
宣告 - 如果
Component
加上了@Scope
宣告,provideXXX
,那麼和Component
不加宣告的情況相同。 - 當
Module
的provideXXX
方法和Component
都加上了@Scope
宣告,那麼在Component
例項的生命週期內,只會建立一個由provideXXX
方法返回的例項。也就是說,該Component
會持有之前通過provideXXX
方法建立的例項的引用,如果之前建立過,那麼就不再呼叫Module
的provideXXX
去建立新的例項,而是直接返回它之前持有的那一份。
上面的例子中,我們通過ScopeActivityModule
建立了兩種型別的資料,provideScopeActivityData()
方法上加上了@PerScopeActivity
,而提供ScopeActivityNormalData
的provideScopeActivityNormalData()
方法則沒有,後面我們將會看到,如果在目標類中使用同一個ScopeActivityComponent
注入,而有多個ScopeActivitySharedData
變數的情況下它們指向的是同一塊記憶體地址,而ScopeActivityNormalData
則會指向不同的記憶體地址。
三、組織多個 Component 情況下對於 @Scope 的限制
對於單個Component
還比較好理解,但是在組織多個Component
的情況下就有些複雜了,這裡的“組織”就是我們在前一篇 Dagger2 知識梳理(3) - 使用 dependencies 和 @SubComponent 完成依賴注入 談到的 依賴方式 和 繼承方式。
- 在依賴或者繼承的組織方式中,如果其中一個
Component
宣告瞭@Scope
,那麼其它的Component
也需要宣告。 - 在依賴關係中,被依賴的
Component
和需要依賴的Component
的@Scope
不能相同 - 在依賴關係中,需要依賴的
Component
的@Scope
不可以為@Singleton
。 - 在組織關係中,子
Component
的@Scope
不可以和父Component
的@Scope
相同: - 在組織關係中,如果父
Component
的@Scope
不為@Singleton
,那麼子Component
的@Scope
可以為@Singleton
。
這些限制是由Dagger2
在編譯時去檢查的,其目的是保證使用者不要對@Scope
產生濫用的現象,因為@Scope
的目的是 在特定作用域內控制被注入例項的複用。
四、示例
為了讓大家更好的驗證上面關於@Scope
的解釋,下面用一個Demo
來演示,完整程式碼可以從 Dagger2Sample 的第四章獲取,這個Demo
包括三個大部分:
(1) ScopeApp
對應於我們平時的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
對應於一個主頁面,其內部包含了ScopeActivitySharedData
和ScopeActivityNormalData
,前者在ScopeActivityComponent
的生命週期內保持唯一性,並帶有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
對於於Activity
下的一個子介面,它和ScopeActivityComponent
是繼承關係,並帶有@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();
}
}
複製程式碼
以上三個部分的關係為:
ScopeActivityComponent
依賴於ScopeAppComponent
ScopeFragmentComponent
繼承於ScopeActivityComponent
- 它們的
Module
上都有用@Scope
描述的註解:@Singleton
、@PerScopeActivity
,@PerScopeFragment
。
示例驗證
通過這個例子可以覆蓋到上面我們介紹的所有場景,大家可以直接在Github
上檢視,也可以clone
下來,進行修改驗證。
在Activity
和Fragment
中,我們列印出變數的地址來驗證前面的結論:
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();
}
}
複製程式碼
結果為:
由上面例子中的現象,可以總結出以下幾點:ScopeAppData
:該資料是由ScopeAppModule
提供的,而它加上了@Singleton
註解,並且我們呼叫的是同一個物件,因此在Activity
和Fragment
中地址相同。ScopeActivitySharedData
:在它的provide
方法上,我們加上了@PerScopeActivity
註解,因此在Activity
和Fragment
中,它的地址相同。ScopeActivityNormalData
:雖然在提供它的ScopeActivityModule
中加上了@PerScopeActivity
註解,但是在provide
方法上沒有宣告,因此無論是在Activity
,還是在Fragment
中,都是指向不同的地址。ScopeFragmentData
:用於演示如何通過繼承的方式,來實現依賴注入。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/