美文网首页android
Android 关于MVP的一些思考与总结

Android 关于MVP的一些思考与总结

作者: Horrarndoo | 来源:发表于2017-10-25 18:21 被阅读249次

    关于MVP的概念,或者MVP相对传统MVC的好处,这些这里就不多讲了,网上的资料随便一搜就是一大把。最近刚好项目重构,参考网上一些文章之后,结合自身的理解,本次简单的总结一下我个人对MVP的一些理解。

    为了最直观的比较,本次通过三个demo示例实现一个登录demo逻辑,来简单的演示一下MVC、MVP以及实际MVP使用的异同。

    先看一下布局代码,就是很简单的一个textView,用来显示登录结果,三个demo的布局代码都是一样的。

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:orientation="vertical">
    
        <TextView
            android:id="@+id/tv_login_result"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="Loading Success"
            android:textSize="50sp"
            android:visibility="invisible"/>
    </FrameLayout>
    

    由于只是一个demo,这里稍微偷了下懒,默认一打开demo就会自动的“登录”,然后两秒后返回登录结果。

    /**
     * Created by Horrarndoo on 2017/4/25.
     * 模拟登录请求
     */
    
    public class LoginRequest {
        public static void login(String name, String password, final LoginListener loginListener) {
            Log.w("tag", "name = " + name);
            Log.w("tag", "password = " + password);
            //假设下面是正常的登录请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        loginListener.onFailed();
                    }
                    loginListener.onSuccess();
                }
            }).start();
        }
    
        public interface LoginListener {
            void onSuccess();
    
            void onFailed();
        }
    }
    

    实现效果如下:


    这里写图片描述

    MVC-demo

    先看看我们最熟悉的MVC方式如何实现。

    public class MvcActivity extends AppCompatActivity {
        @BindView(R.id.tv_login_result)
        TextView tvLoginResult;
    
        private ProgressDialog mWaitPorgressDialog;
        private Handler mHandler = new Handler(Looper.getMainLooper());
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_mvc);
            ButterKnife.bind(this);
            mWaitPorgressDialog = new ProgressDialog(this);
            login();
        }
    
        private void login() {
            showProgressDialog("login...");
            LoginRequest.login("Horrarndoo", "mvczzzzzz", new LoginRequest.LoginListener() {
                @Override
                public void onSuccess() {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            tvLoginResult.setText("login success.");
                            tvLoginResult.setVisibility(View.VISIBLE);
                            hideProgressDialog();
                        }
                    });
                }
    
                @Override
                public void onFailed() {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            tvLoginResult.setText("login failed.");
                            tvLoginResult.setVisibility(View.VISIBLE);
                            hideProgressDialog();
                        }
                    });
                }
            });
        }
    
        private void showProgressDialog(String msg) {
            mWaitPorgressDialog.setMessage(msg);
            mWaitPorgressDialog.show();
        }
    
        private void hideProgressDialog() {
            if (mWaitPorgressDialog != null) {
                mWaitPorgressDialog.dismiss();
            }
        }
    }
    

    这么看MVC框架其实也没什么问题,代码逻辑也很清晰。但是实际的项目中自然不可能说只有这么几句简单的逻辑,往往一个页面,十几个甚至几十个“登录”逻辑。这时候我们再想想如果还是按照MVC模式来写的话,所有的界面处理、逻辑等等,全部都堆在Activity,Activity的代码量会有多大,维护起来会多么痛苦。

    MVP-Simple-demo

    出现了问题,自然就要想着如何解决这个问题,下面我们看看一个MVP框架的一个简单demo示例。
    先看看代码结构:


    这里写图片描述

    Model层

    Model层有一个接口ILoginModel,还有一个实现了ILoginModel接口的实现类。

    public interface ILoginModel {
        void requestLogin(String userName, String userPassword, LoginModel.LoginListener loginListener);
    }
    

    ILoginModel接口很简单,只有一个方法,就是请求登录的方法。

    public class LoginModel implements ILoginModel {
        @Override
        public void requestLogin(String userName, String userPassword, final LoginListener loginListener) {
            LoginRequest.login(userName, userPassword, new LoginRequest.LoginListener() {
                @Override
                public void onSuccess() {
                    loginListener.loginSuccess();
                }
    
                @Override
                public void onFailed() {
                    loginListener.loginFailed();
                }
            });
        }
    
        public interface LoginListener {
            void loginSuccess();
    
            void loginFailed();
        }
    }
    

    LoginModel实现ILoginModel的接口方法,并且通过一个接口对外回调登录结果。

    View层

    View包下,只有一个view的接口,根据前面MVC框架写法,我们抽出了几个接口。其实严格意义上来讲,Activity应该也是归到View包下。

    public interface ILoginView {
        void showLoginSuccess();
        void showLoginFailed();
        void showDialog();
        void hideDialog();
        String getUserName();
        String getUserPassword();
    }
    

    接下来呢,我们让Activity实现ILoginView 的接口方法。

    public class MvpActivity extends AppCompatActivity implements ILoginView{
        @BindView(R.id.tv_login_result)
        TextView tvLoginResult;
    
        LoginPresenter mPresenter;
        private ProgressDialog mWaitPorgressDialog;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_mvp);
            ButterKnife.bind(this);
            mWaitPorgressDialog = new ProgressDialog(this);
        }
    
        @Override
        public void showLoginSuccess() {
            tvLoginResult.setText("login success.");
            tvLoginResult.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void showLoginFailed() {
            tvLoginResult.setText("login failed.");
            tvLoginResult.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void showDialog() {
            showProgressDialog("login...");
        }
    
        @Override
        public void hideDialog() {
            hideProgressDialog();
        }
    
        @Override
        public String getUserName() {
            return "Horrarndoo";
        }
    
        @Override
        public String getUserPassword() {
            return "mvpzzzzzz";
        }
    
        private void showProgressDialog(String msg) {
            mWaitPorgressDialog.setMessage(msg);
            mWaitPorgressDialog.show();
        }
    
        private void hideProgressDialog() {
            if (mWaitPorgressDialog != null) {
                mWaitPorgressDialog.dismiss();
            }
        }
    }
    

    Presenter层

    Presenter层相当于沟通Model层和View层的一个中间层,主要做一些业务逻辑的判断处理。Presenter层持有Model和View的引用,Presenter将Model层处理完的数据通过接口回调给View层,View层做界面上的显示处理。也就是说View和Model的交互,全部通过presenter。

    public class LoginPresenter {
        ILoginModel mILoginModel;
        ILoginView mILoginView;
        Handler mHandler = new Handler(Looper.getMainLooper());
        public LoginPresenter(ILoginView iLoginView){
            mILoginModel = new LoginModel();
            mILoginView = iLoginView;
        }
    
        public void login(){
            String userName = mILoginView.getUserName();
            String userPassword = mILoginView.getUserPassword();
            mILoginView.showDialog();
            mILoginModel.requestLogin(userName, userPassword, new LoginModel.LoginListener() {
                @Override
                public void loginSuccess() {
                    if(mILoginView != null) {
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mILoginView.hideDialog();
                                mILoginView.showLoginSuccess();
                            }
                        });
                    }
                }
    
                @Override
                public void loginFailed() {
                    if (mILoginView != null) {
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mILoginView.hideDialog();
                                mILoginView.showLoginFailed();
                            }
                        });
                    }
                }
            });
        }
    }
    

    最后Activity通过一个LoginPresenter对象,调用登录方法。

    LoginPresenter mPresenter;
    mPresenter = new LoginPresenter(this);
    mPresenter.login();
    

    最终实现的效果和MVC实现的效果是一样的,这时候就有人要问了,这代码量比MVC还要大啊,而且一堆接口乱起八糟的好烦啊。确实很烦,但是实际上我们这样做将所有的业务逻辑和数据处理都从View层剥离开来,View只用做最简单的显示工作,数据上面的工作都有Model层去处理,业务逻辑也都丢给Presenter去处理,不管是从测试还是说维护的方面来讲,好处都是很大的。

    MVP-demo

    以上是简单的MVPdemo示例,考虑到实际应用场景,我们来对这个简单的MVP-Simple进行一些修改和封装。先看看我们的代码结构:


    这里写图片描述

    和上面的MVP-Simple相比,这里将Model,View,Presenter都抽取了一个基类,针对IView和IModel,也抽取了一个基类。

    Base

    BasePresneter

    首先看看BasePresneter,考虑到Presenter需要获取IModel和IView的引用,所以做如下封装。

    public abstract class BasePresenter<M, V> {
        public M mIModel;
        public V mIView;
    
        /**
         * 返回presenter要持有的Model引用
         *
         * @return
         */
        public abstract M getModel();
    
        /**
         * 绑定IModel和IView的引用
         *
         * @param v view
         */
        public void attachMV(V v) {
            this.mIModel = getModel();
            this.mIView = v;
            this.onStart();
        }
    
        /**
         * 解绑IModel和IView
         */
        public void detachMV() {
            mIView = null;
            mIModel = null;
        }
    
        /**
         * IView和IModel绑定完成立即执行
         * <p>
         * 实现类实现绑定完成后的逻辑,例如数据初始化等
         */
        public abstract void onStart();
    }
    

    BaseModel&IBaseModel

    由于我们这里并没有公共方法和变量可以抽取出来,所以这里先空着,如果有实际需求,可以在这里做一些处理。

    public class BaseModel {
        //初始化一些公共数据,声明全局变量等
    }
    
    public interface IBaseModel {
    }
    

    IBaseView

    IBaseView接口主要定义了2个方法,showToast提供给所有BaseView(BaseActivity或者BaseFragment)实现,继承自BaseActivity或者BaseFragment的子类就不用一个个去实现showToast接口。而考虑到每个Activity或者Fragment需要持有一个presenter引用,所有再定义一个setPresenter,由继承BaseActivity或者BaseFragment的子类实现presenter的初始化。

    public interface IBaseView {
        void showToast(String msg);
    
        void setPresenter();
    }
    

    BaseMvpActivity

    BaseMvpActivity主要是做了一些基本的封装,由于具体的presenter和IModel由子类决定,所以这里定义2个继承BasePresenter和IBaseModel的泛型。

    public abstract class BaseMvpActivity<P extends BasePresenter> extends
            AppCompatActivity implements IBaseView {
    
        protected ProgressDialog mWaitPorgressDialog;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getlayoutId());
            ButterKnife.bind(this);
            initData();
        }
    
        protected abstract int getlayoutId();
    
        /**
         * presenter 具体的presenter由子类确定
         */
        protected P mPresenter;
    
        /**
         * 初始化数据
         * <p>
         * 子类可以复写此方法初始化子类数据
         */
        protected void initData() {
            mWaitPorgressDialog = new ProgressDialog(this);
            if (this instanceof IBaseView) {
                this.setPresenter();
                if (mPresenter != null) {
                    mPresenter.attachMV(this);
                    Log.d("tag", "attach M V success.");
                }
            } else {
                Log.e("tag", "attach M V failed.");
            }
        }
    
        @Override
        protected void onDestroy() {
            if (mPresenter != null) {
                mPresenter.detachMV();
                Log.d("tag", "detach M V success.");
            }
            super.onDestroy();
        }
    
        @Override
        public void showToast(String msg) {
            Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
        }
    
        protected void showProgressDialog(String msg) {
            mWaitPorgressDialog.setMessage(msg);
            mWaitPorgressDialog.show();
        }
    
        protected void hideProgressDialog() {
            if (mWaitPorgressDialog != null) {
                mWaitPorgressDialog.dismiss();
            }
        }
    }
    

    contract

    上面是Base封装的一些封装思路介绍,看看MyContract,前面我们的ILoginView和ILoginModel是单独定义的,这里我们通过一个协议接口MyContract来定义ILoginView和ILoginModel,这样也确定了LoginPresenter的IModel和IView的具体类型。由于Presenter、IView和IModel都定义在MyContract,这样一来,presenter方法、view接口方法、model接口方法全部都一目了然。后面如果有功能拓展的话,直接按照这个模式往后面添加就可以了。

    public interface MyContract {
        abstract class LoginPresenter extends BasePresenter<ILoginModel, ILoginView> {
            public abstract void login();
        }
    
        interface ILoginModel extends IBaseModel {
            void requestLogin(String userName, String userPassword, LoginModel.LoginListener
                    loginListener);
        }
    
        interface ILoginView extends IBaseView {
            void showLoginSuccess();
            void showLoginFailed();
            void showDialog();
            void hideDialog();
            String getUserName();
            String getUserPassword();
        }
    }
    

    实现类

    接口和接口方法都定义好了,实现类的话和前面的MVP-Simple大体是一样的。

    LoginModel

    先看看LoginModel,对比前面的LoginModel,这里的LoginModel继承了BaseModel,然后实现的MyContract中定义的ILoginModel 接口方法。

    public class LoginModel extends BaseModel implements MyContract.ILoginModel {
        @Override
        public void requestLogin(String userName, String userPassword, final LoginListener loginListener) {
            LoginRequest.login(userName, userPassword, new LoginRequest.LoginListener() {
                @Override
                public void onSuccess() {
                    loginListener.loginSuccess();
                }
    
                @Override
                public void onFailed() {
                    loginListener.loginFailed();
                }
            });
        }
    
        public interface LoginListener {
            void loginSuccess();
    
            void loginFailed();
        }
    }
    
    

    LoginPresenter

    再看看最后的LoginPresenter,和前面MVP-Simple不同的地方在于LoginPresenter继承MyContract.LoginPresenter,也就是说所有presenter要实现的方法,在MyContract中就已经确定好了。

    public class LoginPresenter extends MyContract.LoginPresenter {
        Handler mHandler = new Handler(Looper.getMainLooper());
    
        @Override
        public void login() {
            String userName = mIView.getUserName();
            String userPassword = mIView.getUserPassword();
            mIView.showDialog();
            mIModel.requestLogin(userName, userPassword, new LoginModel.LoginListener() {
                @Override
                public void loginSuccess() {
                    if(mIView != null) {
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mIView.hideDialog();
                                mIView.showLoginSuccess();
                                mIView.showToast("hahaha, this is new mvp.");
                            }
                        });
                    }
                }
    
                @Override
                public void loginFailed() {
                    if (mIView != null) {
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mIView.hideDialog();
                                mIView.showLoginFailed();
                            }
                        });
                    }
                }
            });
        }
    
        @Override
        public LoginModel getModel() {
            return new LoginModel();
        }
    
        @Override
        public void onStart() {
            Log.w("tag", "LoginPresenter onStart.");
        }
    }
    
    

    Activity

    最后看看我们的Activity实现,由于继承BaseMvpActivity,所有butterKnife的绑定,onCreate等方法都不用实现了,直接实现抽象方法getLayoutId()确定Activity要显示的布局就可以了。

    public class MvpNewActivity extends BaseMvpActivity<MyContract.LoginPresenter> implements MyContract.ILoginView {
        @BindView(R.id.tv_login_result)
        TextView tvLoginResult;
    
        @Override
        protected int getlayoutId() {
            return R.layout.activity_mvp_new;
        }
    
        @Override
        public void setPresenter() {
            mPresenter = new LoginPresenter();
        }
    
        @Override
        protected void initData() {
            super.initData();
            mPresenter.login();
        }
    
        @Override
        public void showLoginSuccess() {
            tvLoginResult.setText("login success.");
            tvLoginResult.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void showLoginFailed() {
            tvLoginResult.setText("login failed.");
            tvLoginResult.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void showDialog() {
            showProgressDialog("login...");
        }
    
        @Override
        public void hideDialog() {
            hideProgressDialog();
        }
    
        @Override
        public String getUserName() {
            return "Horrarndoo";
        }
    
        @Override
        public String getUserPassword() {
            return "mvpzzzzzz";
        }
    }
    
    

    为了区别前面的mvp和验证IBaseView的实际效果,我们这个最终的demo在登录完成后加了一条toast提示。


    这里写图片描述

    三个demo对比看一下的话,还是能看出挺大差别的,个中好处,还是需要自己去领会。
    OK,话不多说,最后附上完整demo地址:https://github.com/Horrarndoo/MvpDemo

    相关文章

      网友评论

      本文标题:Android 关于MVP的一些思考与总结

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