Dagger2 知識梳理(4) @Scope 註解的使用

澤毛發表於2017-12-21

一、前言

對於@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有以下幾點說明:

  • 如果在ModuleprovideXXX方法上加上了@Scope宣告,那麼在與他關聯的Component上也必須加上相同的@Scope宣告
    Dagger2 知識梳理(4)   @Scope 註解的使用
  • 如果Component加上了@Scope宣告,provideXXX,那麼和Component不加宣告的情況相同。
  • ModuleprovideXXX方法和Component都加上了@Scope宣告,那麼在Component例項的生命週期內,只會建立一個由provideXXX方法返回的例項。也就是說,該Component會持有之前通過provideXXX方法建立的例項的引用,如果之前建立過,那麼就不再呼叫ModuleprovideXXX去建立新的例項,而是直接返回它之前持有的那一份。

上面的例子中,我們通過ScopeActivityModule建立了兩種型別的資料,provideScopeActivityData()方法上加上了@PerScopeActivity,而提供ScopeActivityNormalDataprovideScopeActivityNormalData()方法則沒有,後面我們將會看到,如果在目標類中使用同一個ScopeActivityComponent注入,而有多個ScopeActivitySharedData變數的情況下它們指向的是同一塊記憶體地址,而ScopeActivityNormalData則會指向不同的記憶體地址。

三、組織多個 Component 情況下對於 @Scope 的限制

對於單個Component還比較好理解,但是在組織多個Component的情況下就有些複雜了,這裡的“組織”就是我們在前一篇 Dagger2 知識梳理(3) - 使用 dependencies 和 @SubComponent 完成依賴注入 談到的 依賴方式繼承方式

  • 在依賴或者繼承的組織方式中,如果其中一個Component宣告瞭@Scope,那麼其它的Component也需要宣告。
  • 在依賴關係中,被依賴的Component和需要依賴的Component@Scope不能相同
    Dagger2 知識梳理(4)   @Scope 註解的使用
  • 在依賴關係中,需要依賴的Component@Scope不可以為@Singleton
    Dagger2 知識梳理(4)   @Scope 註解的使用
  • 在組織關係中,子Component@Scope不可以和父Component@Scope相同:
    Dagger2 知識梳理(4)   @Scope 註解的使用
  • 在組織關係中,如果父Component@Scope不為@Singleton,那麼子Component@Scope可以為@Singleton

這些限制是由Dagger2在編譯時去檢查的,其目的是保證使用者不要對@Scope產生濫用的現象,因為@Scope的目的是 在特定作用域內控制被注入例項的複用

四、示例

為了讓大家更好的驗證上面關於@Scope的解釋,下面用一個Demo來演示,完整程式碼可以從 Dagger2Sample 的第四章獲取,這個Demo包括三個大部分:

Dagger2 知識梳理(4)   @Scope 註解的使用

(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

對應於一個主頁面,其內部包含了ScopeActivitySharedDataScopeActivityNormalData,前者在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下來,進行修改驗證。

ActivityFragment中,我們列印出變數的地址來驗證前面的結論:

  • 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();
    }
}
複製程式碼

結果為:

Dagger2 知識梳理(4)   @Scope 註解的使用
由上面例子中的現象,可以總結出以下幾點:

  • ScopeAppData:該資料是由ScopeAppModule提供的,而它加上了@Singleton註解,並且我們呼叫的是同一個物件,因此在ActivityFragment中地址相同。
  • ScopeActivitySharedData:在它的provide方法上,我們加上了@PerScopeActivity註解,因此在ActivityFragment中,它的地址相同。
  • ScopeActivityNormalData:雖然在提供它的ScopeActivityModule中加上了@PerScopeActivity註解,但是在provide方法上沒有宣告,因此無論是在Activity,還是在Fragment中,都是指向不同的地址。
  • ScopeFragmentData:用於演示如何通過繼承的方式,來實現依賴注入。

更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章