Android MVP 多个 Presenter 依赖注入

作者: Android架构师丨小熊 | 来源:发表于2019-08-12 21:59 被阅读109次

    基于上篇(Android MVP 架构 MVP 泛型 Model 的配置)我们讲诉的是一对一的 Presenter 和 Model 的关系,利用反射来实例化我们的 Model 层,从而解放了我们每次创建 Model 时都要去 new 的麻烦事儿。明显,代码的逼格更高了一个档次,但是还不够,我们这篇文章中要解决的是 View 与 Presener 层的一对多的关系,这个很正常,当业务相对比较多的时候,我们的 Presenter 层并不是只有唯一的一个,这种情况就是我们的一个 View 层对应多个 Presenter 的现象。

    从我们的前几次版本封装的代码来看,我们在 BaseActivity 基类中是提供了一个抽象方法给它的子类调用,它的返回类型是一个泛型的 Presenter 实现类,而我们 Activity 继承 BaseActivity 的时候,都是直接 new 一个 Presenter 实例的,先来看看代码吧,这样更能够体现这个问题。

    BaseActivity 基类,这里我只抽了部分代码

    public abstract class BaseActivity<P extends IBasePresenter> extends AppCompatActivity implements IBaseView {
     
        private P mPresenter;
     
        protected abstract void initLayout(@Nullable Bundle savedInstanceState);
     
        protected abstract P setPresenter();
     
        protected abstract void initViews();
     
        protected abstract void initData();
     
     
        @SuppressWarnings("SameParameterValue")
        protected <T extends View> T $(@IdRes int viewId) {
            return findViewById(viewId);
        }
    

    看代码中的 setPresenter() 方法,子类必须传入一个泛型的实现类。所以,子类代码如下:

    MainActivity 部分代码:

    public class MainActivity extends BaseActivity<MainContract.IMainPresenter> implements MainContract.IMainView {
     
        private TextView tv;
     
        @Override
        protected void initLayout(@Nullable Bundle savedInstanceState) {
            setContentView(R.layout.activity_main);
        }
     
        @Override
        protected void initViews() {
            tv = $(R.id.tv);
        }
     
        @Override
        protected void initData() {
            getPresenter().handlerData();
        }
     
        @Override
        protected MainContract.IMainPresenter setPresenter() {
            return new MainPresenter();
        }
    

    这里返回的是一个实现了 IMainPresenter 接口的实现类,这里为什么能够 new 实现类呢?其实就是 Java 三大特性中的多态。当然,重点不在这,而是我们这个 MainActivity 需要多个 Presenter 怎么办?

    第一种,最简单的就是 new 一个你需要的不同的 Presenter 实现类,只要关联上 View 层,都可以使用。这种办法也是在没有其他方法的情况下,勉为其难的接受。

    第二种,是使用 Dagger 框架进行依赖注入,那你又没学过怎么办?除了去学习,当然,我们还可以自己写一个依赖注入嘛,依赖注入的方式有很多种,比如:使用接口的方式、构造函数、方法等,还有就是我们的反射的方式。

    所以,由于这里的 Presenter 是泛型的类,而不是具体实现类,我们也就只有考虑用反射来获取对象并实例化了。从上篇文章种的动态创建 Model 就是利用的反射的方式,既然学过了,这里应该也不难吧。来吧,直接上代码:

    修改 BaseActivity 代码:

    package com.test.mvp.mvpdemo.mvp.v5.basemvp;
     
    import android.content.Context;
    import android.os.Bundle;
    import android.support.annotation.IdRes;
    import android.support.annotation.Nullable;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
     
    import com.test.mvp.mvpdemo.mvp.v5.inject.InjectPresenter;
     
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.List;
     
    public abstract class BaseActivity<P extends IBasePresenter> extends AppCompatActivity implements IBaseView {
     
        private P mPresenter;
     
        /**
         * 保存使用注解的 Presenter ,用于解绑
         */
        private List<BasePresenter> mInjectPresenters;
     
        protected abstract void initLayout(@Nullable Bundle savedInstanceState);
     
        protected abstract P setPresenter();
     
        protected abstract void initViews();
     
        protected abstract void initData();
     
     
        @SuppressWarnings("SameParameterValue")
        protected <T extends View> T $(@IdRes int viewId) {
            return findViewById(viewId);
        }
     
        @SuppressWarnings({"unchecked", "TryWithIdenticalCatches"})
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
     
            initLayout(savedInstanceState);
     
            /**
             * 实例化和绑定 P 层
             */
            this.mPresenter = setPresenter();
            if (mPresenter != null) {
                this.mPresenter.attach(this);
            }
     
            mInjectPresenters = new ArrayList<>();
     
            //获得已经申明的变量,包括私有的
            Field[] fields = this.getClass().getDeclaredFields();
            for (Field field : fields) {
                //获取变量上面的注解类型
                InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
                if (injectPresenter != null) {
                    try {
                        Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
                        BasePresenter mInjectPresenter = type.newInstance();
                        mInjectPresenter.attach(this);
                        field.setAccessible(true);
                        field.set(this, mInjectPresenter);
                        mInjectPresenters.add(mInjectPresenter);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    }catch (ClassCastException e){
                        e.printStackTrace();
                        throw new RuntimeException("SubClass must extends Class:BasePresenter");
                    }
                }
            }
     
            initViews();
            initData();
        }
     
        @Override
        protected void onDestroy() {
            super.onDestroy();
            /**
             * 解绑,避免内存泄漏
             */
            this.mPresenter.detach();
            this.mPresenter = null;
            for (BasePresenter presenter : mInjectPresenters) {
                presenter.detach();
            }
            mInjectPresenters.clear();
            mInjectPresenters = null;
        }
     
        @Override
        public Context getContext() {
            return this;
        }
     
        public P getPresenter() {
            return mPresenter;
        }
    }
    

    因为 Presenter 的实例化是在 Activity 中进行的,所以我们得从 BaseActivity 中入手。我们只有一个目的:就是实例化不同的 Presenter 实现类。在泛型的类型面前,我们只能做的也就是通过反射来实例化。看看我们的反射部分关键代码:

        //获得已经申明的变量,包括私有的
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields) {
            //获取变量上面的注解类型
            InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
            if (injectPresenter != null) {
                try {
                    Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
                    BasePresenter mInjectPresenter = type.newInstance();
                    mInjectPresenter.attach(this);
                    field.setAccessible(true);
                    field.set(this, mInjectPresenter);
                    mInjectPresenters.add(mInjectPresenter);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }catch (ClassCastException e){
                    e.printStackTrace();
                    throw new RuntimeException("SubClass must extends Class:BasePresenter");
                }
            }
        }
    

    首先,这里的 this 指的就是继承了 BaseActivity 的子类,也就是我们的 MainActivity 类,我们得先获取到 MainActivity 中所有的成员变量,这里要注意的是,必须使用 getDeclaredFields() 方法进行获取,因为有可能某些成员变量会被定义为私有的。

    我们先跳过这个注解,下面来说明。接着,我们在循环遍历被这个注解过的变量,然后进行一个类型的强制转换。通过 newInstance 实例话 BasePresenter 对象。因为,这里我们进行了实例化,所以在这必须要和我们的 View 关联起来,保证可以持有 View 的引用。最后的 set 方法,要将 this 和我们反射创建的实例化对象进行赋值。这里有必要解释一下,this 代表当前 MainActivity 的对象,它意味这去调用 setter() 方法将反射创建的 mInjectPresenter 对象赋值给 MainActivity 通过注解的 MainPresenter 对象,所以呢,这里很清楚的解释了,为什么一个注解符号就可以实例化对象的原因,其实它的背后已经做了大量你看不到的工作。

    最后,我们去获取那些被 InjectPresenter 注解的变量,这里的 InjectPresenter 是我们自己定义的一个注解,它必须是一个接口类型,看代码:

    新建 InjectPresenter 注解类

    package com.test.mvp.mvpdemo.mvp.v5.inject;
     
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
     
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface InjectPresenter {
    }
    

    这里我们使用 @Target 指定它的注解类型为变量,指定它被保留在运行时期。这里的代码呢,没什么难度。然后,我们在 View 层需要引用的 Presenter 实现类变量头上加个 @InjectPresenter 注解就可以了。当然,你也可以添加多个不同 Presenter,这就解决了我们的 View 与 Presenter 一对多的问题,下面来看看代码吧:

    修改 MainActivity 类

    package com.test.mvp.mvpdemo.mvp.v5.view;
     
    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.os.Handler;
    import android.support.annotation.Nullable;
    import android.widget.TextView;
    import android.widget.Toast;
     
    import com.test.mvp.mvpdemo.R;
    import com.test.mvp.mvpdemo.mvp.v5.MainContract;
    import com.test.mvp.mvpdemo.mvp.v5.basemvp.BaseActivity;
    import com.test.mvp.mvpdemo.mvp.v5.inject.InjectPresenter;
    import com.test.mvp.mvpdemo.mvp.v5.presenter.MainPresenter;
     
    /**
     * MVP 的写法,Version 5: 依赖注入,解决多个 Presenter 的问题
     *
     * @author 神探丶威威猫
     * @blog https://blog.csdn.net/smile_running
     * @warning 点个赞哦,评个论哦
     */
    public class MainActivity extends BaseActivity<MainContract.IMainPresenter> implements MainContract.IMainView {
     
        private TextView tv;
     
        @InjectPresenter
        private MainPresenter mPresenter;
     
        @Override
        protected void initLayout(@Nullable Bundle savedInstanceState) {
            setContentView(R.layout.activity_main);
        }
     
        @Override
        protected void initViews() {
            tv = $(R.id.tv);
        }
     
        @Override
        protected void initData() {
    //        getPresenter().handlerData();
            mPresenter.handlerData();
        }
     
        @Override
        protected MainContract.IMainPresenter setPresenter() {
            return null;
        }
     
        @Override
        public void showDialog() {
            ProgressDialog dialog = new ProgressDialog(getContext());
            dialog.show();
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    dialog.dismiss();
                }
            }, 1500);
        }
     
        @Override
        public void succes(String content) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getContext(), "" + content, Toast.LENGTH_SHORT).show();
                    tv.setText(content);
                }
            });
        }
    }
    

    从上面的代码中,我们的 MainPresenter 头上加了个 @InjectPresenter 注解,这就意味这它实例化成功了,我们可以尽情的去调它的方法。为了测试正确性,我们把 getPresenter() 给注释了,并且 setPresenter() 方法都没有返回值,说明我们当前用的就是注解的对象。运行一下,可以成功!

    这里还存在一个问题放到最后来讲,因为我们在反射的时候要进行关联 View 层,所以在那个时候已经绑定了。既然已经绑定了,那必须要有解绑的操作,否则就会出现问题。因为,这里可能有多个 Presenter 要进行绑定,也有多个 Presenter 进行解绑。所以,就来一个集合存放不同的 Presenter,在反射时绑定并它添加到集合中去,在 View 的 onDestory() 方法中,摧毁视图时进行解绑,这样就不会出现问题了。代码非常简单,就是集合添加和遍历操作。代码已经在上面了,所以这里就不再贴出来了。

    解决这多个 Presenter 的问题情况,那么这个框架也变得越来越具有封装性,代码也更加的难以理解了。

    最后

    最后我准备了一些面试的知识汇总,数据结构,计算机网络等等都有。自己整理和分类的,还请尊重知识产出。
    分享给大家的资料包括高级架构技术进阶脑图、Android开发面试专题资料,还有高级进阶架构资料包括但不限于【高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术】希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的!
    资料免费领取方式:私信回复“架构资料”即可获取

    image

    相关文章

      网友评论

        本文标题:Android MVP 多个 Presenter 依赖注入

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