引言

Android经过这几年的不断发展壮大,APP的功能越来越强大,UI也越来越复杂,对于Android开发者来说UI层在程序开发过程中担任了越来越多的职责。通常一个APP是由多种数据模型(Model)和多种视图(View)组成,如果我们直接使用Model-View设计模型,那这将使得我们的程序代码变得复杂、耦合度高、不利于单元测试和代码重构。

一、MVP概述


MVP的全称为Model-View-Presenter,MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的人机交互都发生在Presenter内部,简而言之,View和Model层不直接交互,所有交互都是同P层来实现,在MVP模式下的Presenter要拥有“绝对权力”。如果没有它,
Model与View就是两个孤岛,尽管各有各的地盘(完全解耦),但不能发挥基本的作用。通过MVP中的View就真的代码精简了蛮多,View只要从相应的IView接口下实现相应的属性和一些简单方法就完事了,而最终调用IView接口下的那个视图实例则完全交给了Presenter。引用微软官方的一张图直观体会下MVP

二、MVP中四种重要角色及联系


虽然MVP的全称为Model-View-Presenter,看起来只有三种角色ModePresenterView,实质上是四种角色——ViewView interfaceModelPresenter

1、View层

负责绘制UI元素,直接承担与用户进行交互的职能,在Android中通常体现为Activity、Fragment、View、Dialog等,在实际开发中也可以根据整个项目的业务对于所有View共性的可以抽象到一个基类。简单理解View 层只承担负责调用Presenter层职责

2、View Interface层

实质上是对View层的逻辑抽象,是需要View必须要实现的接口View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试,在实际开发中也可以根据具体业务进行抽象到一个基类,一般具体的View interface 最好只对应一个View ,虽然理论上是可以对应多个View的,建议最好不要,于是比例应该是View:View Interface=1:1

3、Presenter层

实质上是作为View(实际上是View Interface)的逻辑代理实现(View层的具体逻辑实现应该放在Presenter层),自然是应该与View 一一对应,虽然我们从结构上能让一个Presenter为多个View提供逻辑支持。但是这样会导致Presenter里面的逻辑对到底实现哪个View产生一定的混乱,为了简单明了(个人观点)除了需要与View 交互,同时还需要去与Model交互,简而言之,Presenter层充当的的连接Model与View的桥梁作用,那么在代码上自然需要持有View Interface层和Model的引用,还是个人建议,为了简捷明了比例应该是Presenter:View Interface=1:1。

4、Model层

负责实现具体业务功能,Model层里才是功能的本质,其他三种角色最终通信的目的就是调用Model层的,主要负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)等。这里值得再注明的是M不直接和Activity等直接关联,也不一定和V层、Presenter 层一一对应,一种比较好的习惯是按照功能模块来封装Model层,通常Model层除了真正的功能实现之外,还可以通过一些回调来反馈结果。结合以上的理解,完整的MVP模式比例应该是V:P:M=1:1:n(个人建议,仅供参考)总而言之,MVP最根本的目的就是使M和V层解耦,同时降低Activity、Fragment等View层代码的臃肿程度。

三、MVP模式的优点和不足

1、MVP模式的优点

  • 有效降低了耦合度,实现了Model和View真正的完全分离

  • 各功能模块职责划分明显,层次清晰

  • 项目结构更加灵活,增强了代码可复用性

  • 隐藏数据

  • 降低耦合度,,可以修改View而不影响Modle

  • Model可以被复用

  • 降低了单元测试的成本

  • View可以进行组件化。在MVP当中,View不直接依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它要做的只是调用P。这样就可以做到高度可复用的View组件。

  • 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM

2、MVP模式的不足

  • Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较臃肿,维护起来会比较麻烦。

  • 额外的代码复杂度及学习成本

四、Android MVP实现的基本形式

1、定义View Interface 层

通常个人建议建议,可以把所有需要与P、V交互的操作抽象到View Interface 层。

ILoginView.java

package com.crazymo.mvpdemo.view.activity;/** * Auther: Crazy.Mo * DateTime: 2017/8/30 17:06 * Summary: */public interface ILoginView {    void showLoading();    void hideLoading();    void toMainActivity();    void showUsernameErro();//仅仅在逻辑上去判断    void showPwdErro();    void onUsernameErro();//Model回调    void onLoginowPwdErro();}

2、实现View 层

通常View 层除了需要完成绘制界面的任务之外,还必须要承担另外两份职责——实现View Interface 层接口持有对应的Presenter引用并初始化以及在不使用的时候释放引用,因为MVP模式与传统的M-V模式不同,V不直接和M交互,而是V通过P间接完成与M的交互,所以要想通过调用P就必须得持有对应的引用。

LoginActivity.java

package com.crazymo.mvpdemo.view.activity;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ProgressBar;import com.crazymo.mvpdemo.R;import com.crazymo.mvpdemo.presenter.LoginPresenterImpl;import butterknife.Bind;import butterknife.ButterKnife;import butterknife.OnClick;public class LoginActivity extends AppCompatActivity implements ILoginView {    @Bind(R.id.edt_user)    EditText edtUser;    @Bind(R.id.edt_pwd)    EditText edtPwd;    @Bind(R.id.progress_login)    ProgressBar progressLogin;    @Bind(R.id.btn_login)    Button btnLogin;    private LoginPresenterImpl presenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_login);        presenter=new LoginPresenterImpl(this);        ButterKnife.bind(this);    }    @Override    public void showLoading() {        progressLogin.setVisibility(View.VISIBLE);    }    @Override    public void hideLoading() {        progressLogin.setVisibility(View.VISIBLE);    }    @Override    public void toMainActivity() {        MainActivity.showMainActivity(this);        finish();    }    @Override    public void showUsernameErro() {        edtUser.setError("Username can't be empty or Username is too short");    }    @Override    public void showPwdErro() {        edtPwd.setError("Password can't be empty !");    }    @Override    public void onUsernameErro() {        edtUser.setError("Username is not exits!");    }    @Override    public void onLoginowPwdErro() {        edtPwd.setError("Username or password is erro!");    }    @OnClick({R.id.btn_login})    void onClick(View view){        presenter.login(edtUser.getText().toString(),edtPwd.getText().toString());    }    @Override    protected void onDestroy() {        super.onDestroy();        presenter.detachView();    }}

3、实现Presenter层

通常Presenter层作为M层和V层的桥梁,P层自然发挥承上启下的作用,所以P层的基本实现一般都会是持有对应的M并且初始化M,然后在V中调用P的时候实现在P内部通过M的引用调用M,这可以认为是启下的作用,而通常V层都需要根据M层的反馈结果做出不同的响应,所以还需要持有V的引用以及给M使用的回调。

ILoginPresenter.java

package com.crazymo.mvpdemo.presenter;/** * Auther: Crazy.Mo * DateTime: 2017/8/30 17:06 * Summary: */public interface ILoginPresenter {    void login(String user, String pwd);//此例是以登录为例,而通常登录这个操作需要与M交互,因此可以把这个操作抽象到P层    void detachView();//这个是释放对应的view ,可以封装到一个基类中}

IOnLoginFinishListener.java

package com.crazymo.mvpdemo.presenter;/** * Auther: Crazy.Mo * DateTime: 2017/8/30 17:18 * Summary: */public interface IOnLoginFinishListener {    void onLoginSuccess();    void onLoginFailed(String type);}

LoginPresenterImpl.java

package com.crazymo.mvpdemo.presenter;import com.crazymo.mvpdemo.model.ILoginModel;import com.crazymo.mvpdemo.model.LoginModelImpl;import com.crazymo.mvpdemo.view.activity.ILoginView;/** * Auther: Crazy.Mo * DateTime: 2017/8/30 18:03 * Summary: */public class LoginPresenterImpl implements ILoginPresenter ,IOnLoginFinishListener{    public static final String USER_NOT_EXITS = "user_not_exits";    public static final String USER_PWD_ERRO = "user_pwd_erro";    private ILoginView loginView;//P需要与V 交互,所以需要持有V的引用    private ILoginModel loginModel;    public LoginPresenterImpl(ILoginView view) {        this.loginView = view;        this.loginModel = new LoginModelImpl(this);    }    @Override    public void login(String user, String pwd) {        if(loginView!=null) {            loginView.showLoading();            if ("".equals(user)) {                loginView.showUsernameErro();                loginView.hideLoading();                return;            }            if ("".equals(pwd)){                loginView.showPwdErro();                loginView.hideLoading();                return;            }            loginModel.login(user,pwd);        }    }    @Override    public void detachView() {        //为了避免内存泄漏,在不用的时候及时释放所持有的View 引用        loginView=null;    }    @Override    public void onLoginSuccess() {        loginView.toMainActivity();    }    @Override    public void onLoginFailed(String type) {        loginView.hideLoading();        if(USER_NOT_EXITS.equals(type)){            loginView.onUsernameErro();        }else if(USER_PWD_ERRO.equals(type)){            loginView.onLoginowPwdErro();        }    }}

4、实现Model 层

Model 层作为真正的业务功能实现层,由于需要把结果反馈到V层,所以需要通过P层的回调来实现。

ILoginModel.java

package com.crazymo.mvpdemo.model;/** * Auther: Crazy.Mo * DateTime: 2017/8/30 16:56 * Summary: */public interface ILoginModel {    void login(String user,String name);}

LoginModelImpl.java

package com.crazymo.mvpdemo.model;import android.util.Log;import com.crazymo.mvpdemo.presenter.IOnLoginFinishListener;import com.crazymo.mvpdemo.presenter.LoginPresenterImpl;/** * Auther: Crazy.Mo * DateTime: 2017/8/31 9:20 * Summary: */public class LoginModelImpl implements ILoginModel {    public static final String DEFAULT_USER = "crazymo_";    public static final String DEFAULT_PWD = "cmo";    private IOnLoginFinishListener finishListener;    public LoginModelImpl(IOnLoginFinishListener listener){        this.finishListener=listener;    }    @Override    public void login(String user,String pwd) {        int k=0;        for(int i=0;i<100000;i++){            k++;        }        Log.e("CrazyMo_",Thread.currentThread().getName().toString());        if(DEFAULT_USER.equals(user)&& DEFAULT_PWD.equals(pwd)){            finishListener.onLoginSuccess();        }else if(!(DEFAULT_USER.equals(user))){            finishListener.onLoginFailed(LoginPresenterImpl.USER_NOT_EXITS);        }else if(DEFAULT_USER.equals(user)&&!(DEFAULT_PWD.equals(pwd))){            finishListener.onLoginFailed(LoginPresenterImpl.USER_PWD_ERRO);        }    }}

五、Android MVP实现的结合泛型和弱引用的形式

1、View层(View Interface层代码不变参见源码)

package com.crazyview.mvppro.activity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ProgressBar;import com.crazyview.mvppro.R;import com.crazyview.mvppro.presenter.LoginPresenterImpl;import butterknife.Bind;import butterknife.ButterKnife;import butterknife.OnClick;public class LoginActivity extends BaseActivity implements ILoginView {    @Bind(R.id.edt_user)    EditText edtUser;    @Bind(R.id.edt_pwd)    EditText edtPwd;    @Bind(R.id.progress_login)    ProgressBar progressLogin;    @Bind(R.id.btn_login)    Button btnLogin;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_login);        ButterKnife.bind(this);    }    @Override    public void showLoading() {        progressLogin.setVisibility(View.VISIBLE);    }    @Override    public void hideLoading() {        progressLogin.setVisibility(View.VISIBLE);    }    @Override    public void toMainActivity() {        MainActivity.showMainActivity(this);        finish();    }    @Override    public void showUsernameErro() {        edtUser.setError("Username can't be empty or Username is too short");    }    @Override    public void showPwdErro() {        edtPwd.setError("Password can't be empty !");    }    @Override    public void onUsernameErro() {        edtUser.setError("Username is not exits!");    }    @Override    public void onLoginowPwdErro() {        edtPwd.setError("Username or password is erro!");    }    @OnClick({R.id.btn_login})    void onClick(View view){        presenter.login(edtUser.getText().toString(),edtPwd.getText().toString());    }    @Override    protected void onResume() {        super.onResume();        presenter.attachView(this);//在Activity里的生命周期方法中完成Presenter和V的绑定并初始化    }    @Override    protected void onDestroy() {        super.onDestroy();    }    @Override    public LoginPresenterImpl initPresenter() {        return new LoginPresenterImpl();    }}

2、Presenter 层

封装了一个BasePresenter,同时把View的引用改为弱引用,在View 层的生命周期方法中完成Presenter的管理。

package com.crazyview.mvppro.presenter;import java.lang.ref.WeakReference;/** * Auther: Crazy.Mo * DateTime: 2017/9/7 17:10 * Summary: */public abstract class BasePresenter {    protected WeakReference modelView;    public void attachView(T view) {        this.modelView = new WeakReference(view);    }    public void dettachView() {        this.modelView.clear();    }    protected T getView() {        return modelView.get();    }}

继承BasePresenter实现具体的业务Presenter

package com.crazyview.mvppro.presenter;import com.crazyview.mvppro.activity.LoginActivity;import com.crazyview.mvppro.model.ILoginModel;import com.crazyview.mvppro.model.LoginModelImpl;/** * Auther: Crazy.Mo * DateTime: 2017/8/30 18:03 * Summary: */public class LoginPresenterImpl extends BasePresenter<LoginActivity> implements ILoginPresenter ,IOnLoginFinishListener{    public static final String USER_NOT_EXITS = "user_not_exits";    public static final String USER_PWD_ERRO = "user_pwd_erro";   //P需要与M 交互,所以需要持有M的引用    private ILoginModel loginModel;    public LoginPresenterImpl() {        this.loginModel = new LoginModelImpl(this);    }    @Override    public void login(String user, String pwd) {        if(getView()!=null) {            getView().showLoading();            if ("".equals(user)) {                getView().showUsernameErro();                getView().hideLoading();                return;            }            if ("".equals(pwd)){                getView().showPwdErro();                getView().hideLoading();                return;            }            loginModel.login(user,pwd);        }    }    @Override    public void onLoginSuccess() {        getView().toMainActivity();    }    @Override    public void onLoginFailed(String type) {        getView().hideLoading();        if(USER_NOT_EXITS.equals(type)){            getView().onUsernameErro();        }else if(USER_PWD_ERRO.equals(type)){            getView().onLoginowPwdErro();        }    }}

3、Model层

package com.crazyview.mvppro.model;import android.util.Log;import com.crazyview.mvppro.presenter.IOnLoginFinishListener;import com.crazyview.mvppro.presenter.LoginPresenterImpl;/** * Auther: Crazy.Mo * DateTime: 2017/8/31 9:20 * Summary: */public class LoginModelImpl implements ILoginModel {    public static final String DEFAULT_USER = "crazymo_";    public static final String DEFAULT_PWD = "cmo";    private IOnLoginFinishListener finishListener;    public LoginModelImpl(IOnLoginFinishListener listener){        this.finishListener=listener;    }    @Override    public void login(String user,String pwd) {        int k=0;        for(int i=0;i<100000;i++){            k++;        }        Log.e("CrazyMo_",Thread.currentThread().getName().toString());        if(DEFAULT_USER.equals(user)&& DEFAULT_PWD.equals(pwd)){            finishListener.onLoginSuccess();        }else if(!(DEFAULT_USER.equals(user))){            finishListener.onLoginFailed(LoginPresenterImpl.USER_NOT_EXITS);        }else if(DEFAULT_USER.equals(user)&&!(DEFAULT_PWD.equals(pwd))){            finishListener.onLoginFailed(LoginPresenterImpl.USER_PWD_ERRO);        }    }}

完整源码传送门

小结

MVP当然还有其他的实现形式,为了适配更多类型的View还可以考虑泛型等,如果你真正的理解面向接口编程,灵活使用好抽象和接口,你也可以改造出更高级的MVP模式,比如说Google 官方正在维护的Android 架构等其他第三方的项目。以上只是个人对于MVP使用的一些经验总结和感悟。

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. No.11 使用firewall配置的防火墙策略的生效模式
  3. 如何在android上 使用gif图片(android开源库android-gif-drawabl)
  4. Android设计模式系列--原型模式
  5. Android(安卓)UI基础——EditText控件
  6. 用git的windows客户端msysgit下载android代码
  7. Android(安卓)Jamendo开源在线音乐播放器源码分析一 jamendo初步
  8. android校园二手市场客户端+服务端源代码
  9. Android设计模式系列(7)--SDK源码之命令模式

随机推荐

  1. Android单个进程内存分配策略
  2. android中获得屏幕、视图、任务栏、状态
  3. 10个精品Android(安卓)主题下载
  4. 配置android jni开发环境
  5. 史上最详细的Android(安卓)Studio系列教
  6. Android(安卓)webView与js 交互以及jsbri
  7. 安卓自定义View(第一篇)
  8. Android(安卓)定制万能Adapter
  9. Android(安卓)display架构分析-SW架构分
  10. Android(安卓)Studio版本控制指南