MVP的弊端和解决办法

1、初识mvp模式

此文是写给懂mvp模式的人,尤其是应用过mvp模式的人,因为mvp模式如果只是懂理论没实践过的话不理解它的弊端,此篇文章主要是提出mvp弊端的解决办法;如果不理解mvp模式的先看其他人的扫盲文章,下面也给提供的几篇

全称:Model-View-Presenter ;MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
网上关于mvp模式的解读文章很多,但是都没有解决mvp的弊端,甚至几乎都是避而不谈,或者不知道
google mvp demo : https://github.com/android/architecture-samples/tree/todo-mvp
比如这位:https://blog.csdn.net/qq_31852701/article/details/52946127
还有一些封装的比较好的写法,写了很多泛型
这位还用了弱引用,解决了网络请求内存泄漏的问题:https://blog.csdn.net/zhangqunshuai/article/details/82562801

2、问题和解决办法

下面问题来了,1、如果页面很多的情况下,每个页面都是一个activity,每个activity都要写一个对应的present和model吗?如果每个activity里面功能和业务都比较复杂,这样做是值得的,但是如果有很多那种一个页面只有一个网络请求,只是数据展示,交互很简单;而且这种页面又很多,那我们的代码量不是徒增几倍吗?而且每个页面代码量又很小又,这样就很不值得;
2、如果只写一个Present和一个model呢? 这样也是有问题的,导致Present里面代码过多;所以还是要找到折中的办法,如果只是那种没有灵魂的外包项目这样做也是可以,跟对接口去封装present和model;良好的代码应该是针对业务去设计代码结构;相同业务的代码就放在一起;比如和登录相关的业务有:登录接口、注册接口、验证码接口、检查手机号是否注册过接口。这四个接口肯定是用一个present和一个model比较合适;但是View又不是一个,所以我们在定义mvp模式的时候,先写一个login模块的Contract;比如这样:

public interface LoginContract {

    interface Presenter extends EasyPresent {
        void doLogin(String name,String pass);

        void register(String phoneNum, String pass);

        void verificationCode(String phoneNum);
    }

    interface LoginView extends EasyView {//定义登录页面两个结果业务逻辑

        void loginSuccess(User user);//登录成功

        void loginFailed(int state, String message);//登录失败
    }

    interface RegisterView extends EasyView {
        void registerSuccess(String userName, String passWord);
    }

    interface GetVerificationCode extends EasyView {
        void GetVerificationCodeSuccess(String verificationCode);
    }
}

业务可能不全,能理解就行,这个根据具体情况自己具体去定义;此处我写了三个View,登录、注册、验证码
EasyView里面定义一些通用的View层的方法比如这样写:

/**
 * description:定义通用的view方法,让BaseActivity去实现此接口,在Contract
 * 里面写的View接口都要继承此接口
 * author: tianhonglong
 * new date: 2021/7/6
 * version: v 1.0
 */
public interface EasyView {

    void showMessage();

    void showMessage(String msg);

    void showLoadingDialog();

    void showLoadingDialog(String msg);

    void dismissDialog();
}

下面是BaseActivity的代码,我自己定义名字叫EasyActivity

/**
 * description:
 * author: tianhonglong
 * new date: 2021/7/6
 * version: v 1.0
 */
public class EasyActivity extends AppCompatActivity implements EasyView {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EVM.register(this);//这里是重点,往下看
    }

    public void onBackListener(View view) {
        finish();
    }

    @Override
    public void showMessage() {

    }

    @Override
    public void showMessage(String msg) {
    // 比如显示一些文字的显示框,dialog或者Toast,此处只是举例
    }

    @Override
    public void showLoadingDialog() {
    // to do 此处实现显示loadingDialog的代码,自定义统一的样式
    }

    @Override
    public void showLoadingDialog(String msg) {
    // to do 此处实现显示loadingDialog的代码,自定义统一的样式
    }

    @Override
    public void dismissDialog() {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EVM.unregister(this);//此处也是重点
    }
}

下面就是解决要写N多个Present的解决办法
我写的EVM类,用的是和EventBus一个思想,只是性能要远远超过EventBus;但是使用起来肯定也要比EventBus差一些,但是也够用了,上代码:

/**
 * description:
 * author: tianhonglong
 * new date: 2021/7/9
 * version: v 1.0
 */
public class EVM {

    private EVM() {
    }

    private Map<String, WeakReference<EasyView>> views = new HashMap<>();

    public static void register(EasyView easyView) {
        EVM.ins().managerView(easyView, true);
    }

    private <T extends EasyView> T getView(Class<T> clazz) {
        EasyView easyView = views.get(clazz.getSimpleName()).get();
        if (easyView == null) {
            try {
                return clazz.newInstance();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
        return (T) easyView;
    }

    public static <T extends EasyView> T get(Class<T> clazz) {
        return EVM.ins().getView(clazz);
    }

    public static void unregister(EasyView easyView) {
        EVM.ins().managerView(easyView, false);
    }

    private void managerView(EasyView easyView, boolean registerOrNot) {
        Class[] classes = easyView.getClass().getInterfaces();
        for (Class clazz : classes) {
            if (EasyView.class.isAssignableFrom(clazz)) {
                if (registerOrNot) {
                    views.put(clazz.getSimpleName(), new WeakReference<>(easyView));
                } else {
                    views.remove(clazz.getSimpleName());
                }
            }
        }
    }

    private static class InnerClass {
        private static EVM easyPresent = new EVM();
    }

    private static EVM ins() {
        return InnerClass.easyPresent;
    }
}

在贴上LoginActivity的写法

/**
 * description:
 * author: tianhonglong
 * new date: 2021/7/6
 * version: v 1.0
 */
public class LoginActivity extends EasyActivity implements LoginContract.LoginView {

    private EditText userName, passWord;
    private LoginPresent loginPresent;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        loginPresent = new LoginPresent();//创建LoginPresent
        userName = findViewById(R.id.userName);
        passWord = findViewById(R.id.passWord);
    }

    public void doLogin(View view) {
        String password = passWord.getText().toString();
        String username = userName.getText().toString();
        loginPresent.doLogin(username, password);
    }

    @Override
    public void loginSuccess(User user) {
        Intent intent = new Intent(LoginActivity.this, MainActivity.class);
        startActivity(intent);
    }

    @Override
    public void loginFailed(int state, String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}

使用起来也很方便,只要在BaseActivity里面的onCreate()和onDestroy里分别执行register和unregister就可以了;
只要是启动的Activity继承了View接口,然后执行注册,所有的View都会在EVM里面注册;下面贴LoginPresent代码


/**
 * description:
 * author: tianhonglong
 * new date: 2021/7/9
 * version: v 1.0
 */
public class LoginPresent implements LoginContract.Presenter {

    private LoginModel loginModel = new LoginModel();

    @Override
    public void doLogin(String userName, String password) {
        EVM.get(LoginContract.LoginView.class).showLoadingDialog();
        User user = loginModel.doUser(userName, password);//假装此处执行了耗时网络请求操作
        loginView.dismissDialog();
        if(user.isLoginState()) {
            EVM.get(LoginContract.LoginView.class).loginSuccess(user);
        } else {
            EVM.get(LoginContract.LoginView.class).loginFailed(user.getFailureStatus(),"用户密码不对");
        }
    }

    @Override
    public void register(String phoneNum, String pass) {
    //此处执行注册操作
    }

    @Override
    public void verificationCode(String phoneNum) {
    // 此处执行获取验证码的方法
    }
}

这里可以看到,我们从model拿到数据之后,通过EVM.get(LoginContract.LoginView.class)就能拿到LoginView接口对象,然后就执行相应的方法就可以了,这样就解决了要写多个Present的问题,相近的业务我就可以写在同一个present里面,然后我想把结果发送给View的时候也比较自由,只要用EVM.get(“任意定义的VIew.class”),拿到View对象后想执行什么方法就执行什么方法,原理也很简单,就是解耦了View和Present的关联关系,引入中间键EVM类;统一管理View层,这样不管任何一个Present都可以自由的给任何一个View去发送结果。所有View层都要在activity执行完onCreate后统一注册到EVM类里面供未知的Present 去调用,不再是一对一关系

正常的MVP模式是这样
在这里插入图片描述
改良后的mvp是这样
在这里插入图片描述

实际上就是通过EVM拨号中间键,把任意的View和Present都能自由的连接,然后再解决一下可能产生的内存泄漏、和空指针问题,mvp的弊端就彻底解决了,这样你想定义几个View就定义几个View,想定义几个Present就定义几个Present都行,互相之间没有引用关系就可以互相调用,是不是很nice? 像EventBus、组件化都有用到此想法;重点是性能好,像EventBus设置注解,底层要遍历activity的所有方法,检查注解的存在,我们这个只获取父类是EasyView的接口,一般一个类实现的接口都是很少的,远没有方法多,所以性能问题可以忽略不计

3内存泄露问题

内存泄露都是对象之间有强引用关系,而且present有耗时操作;所以会影响activity的内存回收,用现在的办法后这个内存回收问题也没有了

然后是我写的案例,在github上,喜欢的话就点个赞吧
https://github.com/honeyed/mvpdemo

最后总结:不管mvc模式、mvp、mvvm模式,这些都只是骨架,良好的代码结构是要有充分的分块对接思想,就像现实中,我们的手机充电头和数据线都是分开的,已经看不到一体的充电器了,这就是分块对接思想;一些个复杂的页面光是一个mvp或者mvvm是远远不够的,要看页面的具体业务逻辑去具体问题具体分析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值