Android经过这几年的不断发展壮大,APP的功能越来越强大,UI也越来越复杂,对于Android开发者来说UI层在程序开发过程中担任了越来越多的职责。通常一个APP是由多种数据模型(Model)和多种视图(View)组成,如果我们直接使用Model-View设计模型,那这将使得我们的程序代码变得复杂、耦合度高、不利于单元测试和代码重构。
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的全称为Model-View-Presenter,看起来只有三种角色Mode、Presenter、View,实质上是四种角色——View、View interface、Model、Presenter。
负责绘制UI元素,直接承担与用户进行交互的职能,在Android中通常体现为Activity、Fragment、View、Dialog等,在实际开发中也可以根据整个项目的业务对于所有View共性的可以抽象到一个基类。简单理解View 层只承担负责调用Presenter层职责
实质上是对View层的逻辑抽象,是需要View必须要实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试,在实际开发中也可以根据具体业务进行抽象到一个基类,一般具体的View interface 最好只对应一个View ,虽然理论上是可以对应多个View的,建议最好不要,于是比例应该是View:View Interface=1:1
实质上是作为View(实际上是View Interface)的逻辑代理实现(View层的具体逻辑实现应该放在Presenter层),自然是应该与View 一一对应,虽然我们从结构上能让一个Presenter为多个View提供逻辑支持。但是这样会导致Presenter里面的逻辑对到底实现哪个View产生一定的混乱,为了简单明了(个人观点)除了需要与View 交互,同时还需要去与Model交互,简而言之,Presenter层充当的的连接Model与View的桥梁作用,那么在代码上自然需要持有View Interface层和Model的引用,还是个人建议,为了简捷明了比例应该是Presenter:View Interface=1:1。
负责实现具体业务功能,Model层里才是功能的本质,其他三种角色最终通信的目的就是调用Model层的,主要负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)等。这里值得再注明的是M不直接和Activity等直接关联,也不一定和V层、Presenter 层一一对应,一种比较好的习惯是按照功能模块来封装Model层,通常Model层除了真正的功能实现之外,还可以通过一些回调来反馈结果。结合以上的理解,完整的MVP模式比例应该是V:P:M=1:1:n(个人建议,仅供参考)总而言之,MVP最根本的目的就是使M和V层解耦,同时降低Activity、Fragment等View层代码的臃肿程度。
有效降低了耦合度,实现了Model和View真正的完全分离
各功能模块职责划分明显,层次清晰
项目结构更加灵活,增强了代码可复用性
隐藏数据
降低耦合度,,可以修改View而不影响Modle
Model可以被复用
降低了单元测试的成本
View可以进行组件化。在MVP当中,View不直接依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它要做的只是调用P。这样就可以做到高度可复用的View组件。
把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较臃肿,维护起来会比较麻烦。
额外的代码复杂度及学习成本
通常个人建议建议,可以把所有需要与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();
}
通常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();
}
}
通常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();
}
}
}
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);
}
}
}
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();
}
}
封装了一个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();
}
}
}
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使用的一些经验总结和感悟。