最简MVP框架

作者: ed407c8602e0 | 来源:发表于2016-08-08 16:21 被阅读9934次

    前言

    听到一些童鞋抱怨MVP,所有搞了个辅助实现MVP的小东西,叫MvpFrame。还不了解MVP的先看《Google原味mvp实践》。主要的功能如下

    • 省代码。不能偷懒的框架都是耍流氓,当然像Rx系列这样可以简化逻辑的也是正经人。
    • 不依赖其他库。不跟Retrofit,Rxjava等等耦合,只是纯粹的辅助MVP的实现。
    • 小,只有8k。可以在任意最小业务单元使用,即使之前业务没使用,或者之后不想使用都没关系。
    • M,V,P中各个层级的实例托管。
    • 维护M,V,P中对其他层的引用,并保证实例可回收。
    • 在工程任意位置获取MVP中的实例。

    开源地址是

    https://github.com/wolearn/MvpFrame

    怎么用

    可以把上面工程中的mvpframelib作为Android Lib引入,或者直接复制java文件也可以。我简单解释下还是我常用那个登陆的例子。先看目录结构。


    初始化

    在Application中调用

    Mvp.getInstance().init(this);

    规划好view和presenter的接口。

    public class LoginContract {
        public interface View extends BaseView {
            String getAccount();
            String getPassword();
            void loginSuccess();
            void loginError(String errorMsg);
        }
    
        public interface Presenter extends BasePresenter {
            void login();
        }
    }
    

    用一个契约类来定义需要的方法。之前有童鞋问我,这个接口写好烦,能不能不写。肯定不能。我能想到的理由有三点

    • 依赖倒置原则。高层模块和底层模块之间不能直接依赖,而是应该依赖其接口
    • 保证其可测性
    • 面向接口编程的前期设计感

    View层

    可以支持Activity和Fragment,Activity继承MvpActivity,Fragment继承MvpFragment。

    public class LoginActivity extends MvpActivity<LoginPresenter> implements LoginContract.View, View.OnClickListener {
        private EditText edtAccount, edtPassword;
        private Button btnLogin;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
    
            initViews();
        }
    
        @Override
        public BaseView getBaseView() {
            return this;
        }
    
        @Override
        public void onClick(View v) {
            mPresenter.login();
        }
    
        private void initViews() {
            edtAccount = (EditText) findViewById(R.id.edt_account);
            edtPassword = (EditText) findViewById(R.id.edt_password);
            btnLogin = (Button) findViewById(R.id.btn_login);
    
            btnLogin.setOnClickListener(this);
        }
    
        @Override
        public void loginError(String msg) {
            Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void loginSuccess() {
            Toast.makeText(LoginActivity.this, getResources().getString(R.string.login_success), Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public String getAccount() {
            return edtAccount.getText().toString();
        }
    
        @Override
        public String getPassword() {
            return edtPassword.getText().toString();
        }
    }
    
    • 注意MvpActivity<LoginPresenter>,在继承的时候要通过泛型确定Presenter层的具体类型,一定要写。
    • getBaseView方法返回的是IView类型,如果是当前Activity实现的话,直接返回this即可。
    • mPresenter可以直接使用,不用声明和实例化

    Presenter层

    Presenter继承MvpPresenter实现即可。

    public class LoginPresenter extends MvpPresenter<LoginHttp, LoginContract.View> implements LoginContract.Presenter {
        private static final String TAG = "LoginPresenter";
    
        @Override
        public void login() {
            if (!checkParameter()) return;
    
            String account = getIView().getAccount();
            String password = getIView().getPassword();
    
            mModel.login(account, password);
    
            //模拟登陆成功
            getIView().loginSuccess();
        }
    
        /**
         * 登录参数校验
         *
         * @return
         */
        private boolean checkParameter() {
            try {
                if (TextUtils.isEmpty(getIView().getAccount())) {
                    getIView().loginError(mContext.getString(R.string.toast_account_empty));
                    return false;
                }
    
                if (TextUtils.isEmpty(getIView().getPassword())) {
                    getIView().loginError(mContext.getString(R.string.toast_password_empty));
                    return false;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
    }
    
    • 注意MvpPresenter<LoginHttp, LoginContract.View>的泛型,要确定Model的具体实现和IView。
    • IView的回调接口对象通过getIView()方法获取
    • mModel指向泛型中确认的LoginHttp对象,可以直接使用,不用声明和实例化
    • mContext是一个ApplicationContext的引用

    Model层

    数据的来源一般有三个:DB,NET,Cache。看个图



    之前有童鞋说,业务过程很烦,要搞个UseCase文件,然后又要搞个Repository文件,最后搞个HttpLogin,因为登录本来就只是要跟服务端确认,前面2个文件基本都是透传。是不是一定要这么死板呢?当然不是,结构可以根据业务的复杂度做调整的。比如我这里登录就是直接让HttpLogin继承MvpModel, 直接跟P层交互。

    public class LoginHttp extends MvpModel {
        private static final String TAG = "LoginHttp";
    
        /**
         * 密码登陆
         *
         * @param account
         * @param password
         */
        public void login(String account, String password) {
    //        execute(api.login(account, password), callback);
        }
    }
    

    如果你当前业务是只跟DB打交道,也可以让LoginDB继承MvpModel,然后在P层的泛型中确认类型即可。

    其他任意位置获取M,V,P实例

    默认通过以下API获取的唯一实例,传入为实例的class类型

    Mvp.getInstance().getPresenter();
    Mvp.getInstance().getModel();
    Mvp.getInstance().getView();

    getPresenter 和 getModel 的实例默认会创建,getView 要确定Activity或者Fragment已经创建,否则可能为null。

    后记

    建议以文件的形式引入,方便依据工程业务做定制化。喜欢请帮忙戳喜欢,有问题欢迎评论。

    日志(记录一些issue和优化点)

    1.关于P层Login方法参数是通过IView来获取还是直接login(String account, String password),就当前登录的例子来说,直接通过方法传递比较好。

    关注我(微信扫一扫)

    相关文章

      网友评论

      • winelx:在fragment中 mPresenter为空,getBaseView中该返回什么呢,直接this,还是类名.this都不走mvpFragment类。
      • aee30df11e01:有没有想过去掉model层把model层的处理放在presenter感觉好多项目不用这么复杂这样就搞定 了
      • Jlanglang:其实封装一个这样的框架,如果不带网络库,不带其他的

        几个类就搞定了.这modle意义其实都不大.可有可无.

        那还要这个框架干嘛呢?

      • gongxm: public V getIView() {
        return mViewRef.get();
        }
        使用中遇到个问题 mViewRef 为null
        请问大神遇到过吗
        gongxm:@wolearn 不大明白什么意思
        ed407c8602e0: @gongxm 不要直接用getIView给成员变量赋值,尽量在方法内部使用
      • Avalon1:还是不大理解契约类的作用呢:flushed:…。
        ed407c8602e0:@Avalon1 ʕ•̫͡•ོʔ•̫͡•ཻʕ•̫͡•ʔ•͓͡•ʔ
      • oden:在一个activity中,有多个fragment通过导航栏切换的情况下,fragment中使用mvp,该框架适用吗?是不是只维持最新的mvp的实例?
        oden:@wolearn
        在Mvp.getGenericType报错
        Caused by: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
        oden:@wolearn 再请教下,getBaseView方法在fragment中应该返回什么?
        ed407c8602e0:@b9761676685c 可以用,每个类型fragment都有自己的单个实例
      • 68768b474bfc:如果一个Presenter需要一个以上的UseCase呢?将Model添加到Presenter是不是不太好
      • 马铃薯蜀黍:iOS一脸懵逼的路过
      • stay4it:这个MVP阐述的并不好。P的负担太重。
        比如checkParameter最好放在V层。
        比如login()方法应该替换成login(String account, String password)
        P只处理核心业务逻辑
        在P中应该尽量减少 调用V层状态的方法,这样不仅要增加V的接口负担,测试还得添加MockView,这是不合理的。

        用MVP时,应尽量考虑P层的可复用与可测试性。不然换成MVP就没意义了,还白增加一些约束。
        ed407c8602e0:@stay4it :smile:
        stay4it:@wolearn的小舟 这样最简还能更简 :smile: 我也是最近刚实践了MVP,不然还是停留在表面。
        ed407c8602e0:@stay4it 谢stay4it评论,你的视频和文章我有关注。1.checkParameter放在V层还是P我觉得还是见仁见智,正式的代码中有专门的validate类来处理,不会让P的代码很臃肿。 2.login(String account, String password)这个问题我在之前的文章里也讨论过,这些参数是否要从IView中获取应该考虑方法的参数数量和这个参数获取的复杂程度和可测试性。我认可login确实是login(String account, String password)这样好点,从之前的例子里copy过来忘了改,汗。3.减轻P的负担完全认同。
      • ed407c8602e0:”而MVP架构的P是单向连接M的,如果在M中加上P的回调,就违反了MVP模式“,这个理解有问题。
        myz7656:请看这篇 http://www.jianshu.com/p/50131586a75c
        androidjp:个人理解,model层主要实现数据持久化的逻辑、网络操作的逻辑、各种工具类的实现。
        androidjp:@wolearn的小舟 可能是我理解错了吧,看到基本上所有有关MVP的图解都是一个“单向箭头”presenter指向model
      • androidjp:关于presenter和model的具体分工,还有点疑惑~ :grin:
      • androidjp:这里的“异步”,比如说我要下载10张图片,那我如果不用库而直接HttpURLConnection+HandlerThread/AsyncTask 的方式去跑,那,这里这部分逻辑代码,要写在Presenter还是在Model呢?应该是Presenter吧。
        因为如果放在model中,而MVP架构的P是单向连接M的,如果在M中加上P的回调,就违反了MVP模式,但如果不加,那么M内部做的事情成功与否,P 和V两者好像都不知道吧
      • scorpionfeng:model 没有考虑异步的情况?
        ed407c8602e0:@scorpionfeng 我是这样考虑的,model的异步是由继承MvpModel的类自己实现的。因为很多网络框架已经实现了异步,数据库也是,而且异步要考虑业务场景。我不能通用的提供一个线程安全的类,或者一个异步的方法,这个自己实现即可。
        liguiyun:@scorpionfeng 这个肯定要考虑异步

      本文标题:最简MVP框架

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