Dagger2 知识梳理(4) - @Scope 注解的使用

作者: 泽毛 | 来源:发表于2017-09-19 23:55 被阅读415次

    Dagger2 系列文章

    Dagger2 知识梳理(1) - Dagger2 依赖注入的两种方式
    Dagger2 知识梳理(2) - @Qulifier 和 @Named 解决依赖注入迷失
    Dagger2 知识梳理(3) - 使用 dependencies 和 @SubComponent 完成依赖注入
    Dagger2 知识梳理(4) - @Scope 注解的使用


    一、前言

    对于@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声明
    • 如果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不能相同
    • 在依赖关系中,需要依赖的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

    对应于一个主页面,其内部包含了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();
        }
    }
    

    结果为:



    由上面例子中的现象,可以总结出以下几点:

    • ScopeAppData:该数据是由ScopeAppModule提供的,而它加上了@Singleton注解,并且我们调用的是同一个对象,因此在ActivityFragment中地址相同。
    • ScopeActivitySharedData:在它的provide方法上,我们加上了@PerScopeActivity注解,因此在ActivityFragment中,它的地址相同。
    • ScopeActivityNormalData:虽然在提供它的ScopeActivityModule中加上了@PerScopeActivity注解,但是在provide方法上没有声明,因此无论是在Activity,还是在Fragment中,都是指向不同的地址。
    • ScopeFragmentData:用于演示如何通过继承的方式,来实现依赖注入。

    更多文章,欢迎访问我的 Android 知识梳理系列:

    相关文章

      网友评论

      • 相忘_2f42:感觉这个框架好像涵盖了两个方面的内容。
        一个是创建者模式,创建的对象是原型还是单例。在Scope这个注解可以使用。
        二是解决手动依赖的弊端,不用再去自己写一堆set方法注入了,自己注入对象就可以了。
      • xieshengqi:作者,这样写的好处是啥,我表示看不懂,能不能解释下,谢谢了:joy:
        红领巾程序猿:使用dagger2写的好处就是 完成最大限度的解耦。减小类与类一之间的依赖关系。

      本文标题:Dagger2 知识梳理(4) - @Scope 注解的使用

      本文链接:https://www.haomeiwen.com/subject/rrewsxtx.html