深入理解MVP概念送上demo
一、概述
相信大家对MVC都是比较熟悉了:M-Model-模型、V-View-视图、C-Controller-控制器,MVP作为MVC的演化版本,那么类似的MVP所对应的意义:M-Model-模型、V-View-视图、P-Presenter-表示器。 从MVC和MVP两者结合来看,Controlller/Presenter在MVC/MVP中都起着逻辑控制处理的角色,起着控制各业务流程的作用。而 MVP与MVC最不同的一点是M与V是不直接关联的也是就Model与View不存在直接关系,这两者之间间隔着的是Presenter层,其负责调控 View与Model之间的间接交互。在 Android中很重要的一点就是对UI的操作基本上需要异步进行也就是在MainThread中才能操作UI,所以对View与Model的切断分离是合理的。此外Presenter与View、Model的交互使用接口定义交互操作可以进一步达到松耦合也可以通过接口更加方便地进行单元测试。所以也就有了这张图片(MVP和MVC的对比)
其实最明显的区别就是,MVC中是允许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的(代码中会体现)。
简单的需求来展示如何编写MVP的demo。
二、案例 Login Demo
build.gradle
compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' compile 'com.zhy:okhttputils:2.6.2' compile 'com.google.code.gson:gson:2.2.4' compile 'com.yanzhenjie:permission:1.0.6'
看到这样的效果,先看下完工后的项目结构:
ok,接下来开始一步一步的编写思路。
(一)Model
首先实体类User不用考虑这个肯定有,其次从效果图可以看到至少有一个业务方法login(),这两点没什么难度,我们首先完成:
1.定义Bean对象
package com.qinjie.administrator.mvp.bean; /** * Created by Administrator on 2017\7\16 0016. */ public class User { private int status; private String message; private BodyEntity body; private long timestamp; private Object exception; public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public BodyEntity getBody() { return body; } public void setBody(BodyEntity body) { this.body = body; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public Object getException() { return exception; } public void setException(Object exception) { this.exception = exception; } public static class BodyEntity { /** * user : {"id":12,"createTime":null,"updateTime":null,"enableFlag":0,"username":"afu","password":"123456","phone":"18601042258","imgurl":null,"createTimeString":"","updateTimeString":""} */ private UserEntity user; public UserEntity getUser() { return user; } public void setUser(UserEntity user) { this.user = user; } public static class UserEntity { private int id; private Object createTime; private Object updateTime; private int enableFlag; private String username; private String password; private String phone; private Object imgurl; private String createTimeString; private String updateTimeString; @Override public String toString() { return "UserEntity{" + "id=" + id + ", createTime=" + createTime + ", updateTime=" + updateTime + ", enableFlag=" + enableFlag + ", username='" + username + '\'' + ", password='" + password + '\'' + ", phone='" + phone + '\'' + ", imgurl=" + imgurl + ", createTimeString='" + createTimeString + '\'' + ", updateTimeString='" + updateTimeString + '\'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public Object getCreateTime() { return createTime; } public void setCreateTime(Object createTime) { this.createTime = createTime; } public Object getUpdateTime() { return updateTime; } public void setUpdateTime(Object updateTime) { this.updateTime = updateTime; } public int getEnableFlag() { return enableFlag; } public void setEnableFlag(int enableFlag) { this.enableFlag = enableFlag; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public Object getImgurl() { return imgurl; } public void setImgurl(Object imgurl) { this.imgurl = imgurl; } public String getCreateTimeString() { return createTimeString; } public void setCreateTimeString(String createTimeString) { this.createTimeString = createTimeString; } public String getUpdateTimeString() { return updateTimeString; } public void setUpdateTimeString(String updateTimeString) { this.updateTimeString = updateTimeString; } } } @Override public String toString() { return "User{" + "status=" + status + ", message='" + message + '\'' + ", body=" + body + ", timestamp=" + timestamp + ", exception=" + exception + '}'; } }
2.Model接口IUserLoginModel
public interface IUserLoginModel { public void login(String username, String password, OnLoginListener loginListener); }
3.Mode接口监听OnLoginListener
public interface OnLoginListener { void loginSuccess(User user); void loginFailed(); }
4.IUserLoginModelImpl
public class IUserLoginModelImpl implements IUserLoginModel { @Override public void login(final String username, final String password, final OnLoginListener loginListener) { String url = "http://44.33.444.777:8081/android/user/login?"; OkHttpUtils .get() .url(url) .addParams("username", username) .addParams("password", password) .addParams("phone", "18676364965") .build() .execute(new StringCallback() { @Override public void onError(Call call, Exception e, int id) { Log.e("TAG", "请求失败" + e.getMessage()); if (loginListener != null) { loginListener.loginFailed(); } } @Override public void onResponse(final String response, int id) { Log.e("TAG", "请求成功" + response); new Handler().postDelayed(new Runnable() { @Override public void run() { User user = new Gson().fromJson(response, User.class); //模拟登录成功 if (user.getStatus() == 200) { loginListener.loginSuccess(user); } else { loginListener.loginFailed(); } } }, 2000); } }); }
实体类不用说,至于业务类,我们抽取了一个接口,一个实现类这也很常见~~login方法,一般肯定是连接服务器的,为了演示效果,特意两秒后才响应登录成功或者失败状态。其实这里还是比较好写的,因为和传统写法没区别。
(二) View
1.IUserLoginView
上面我们说过,Presenter与View交互是通过接口。所以我们这里需要定义一个IUserLoginView,难点就在于应该有哪些方法,我们看一眼效果图:
可以看到我们有两个按钮,一个是login,一个是clear;
login说明了要有用户名、密码,那么对应两个方法:
String getUserName();
String getPassword();
再者login是个耗时操作,我们需要给用户一个友好的提示,一般就是操作ProgressBar,所以再两个:
void
showLoading();
void
hideLoading();
login当然存在登录成功与失败的处理,我们主要看成功我们是跳转Activity,而失败可能是去给个提醒:
void
toMainActivity(User user);
void
showFailedError();
ok,login这个方法我们分析完了~~还剩个clear那就简单了:
void
clearUserName();
void
clearPassword();
· 综上,接口完整为:
public interface IUserLoginView { String getUserName(); String getPassword(); void clearUserName(); void clearPassword(); void showLoading(); void hideLoading(); void toMainActivity(User user); void showFailedError(); }
有了接口,实现就太好写了~~~
总结下,对于View的接口,去观察功能上的操作,然后考虑:
· 该操作需要什么?(getUserName,getPassword)
· 该操作的结果,对应的反馈?(toMainActivity,showFailedError)
· 该操作过程中对应的友好的交互?(showLoading,hideLoading)
下面贴一下我们的View的实现类,哈,其实就是Activity,文章开始就说过,MVP中的View其实就是Activity。
2.LoginActivity -布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="用户名:" /> <EditText android:id="@+id/id_et_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPersonName" android:text="" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="密码:" /> <EditText android:id="@+id/id_et_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPersonName" android:text="" /> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal"> <Button android:id="@+id/id_btn_login" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="登录" /> <Button android:id="@+id/id_btn_clear" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_weight="1" android:text="清除" /> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="20dp" android:orientation="horizontal"> <ProgressBar android:id="@+id/id_pb_loading" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="gone" /> </LinearLayout> </LinearLayout>
3.LoginActivity -代码
public class LoginActivity extends AppCompatActivity implements IUserLoginView { private EditText mEtUsername, mEtPassword; private Button mBtnLogin, mBtnClear; private ProgressBar mPbLoading; private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.login); initViews(); } private void initViews() { mEtUsername = (EditText) findViewById(R.id.id_et_username); mEtPassword = (EditText) findViewById(R.id.id_et_password); mBtnClear = (Button) findViewById(R.id.id_btn_clear); mBtnLogin = (Button) findViewById(R.id.id_btn_login); mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading); mBtnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mUserLoginPresenter.login(); } }); mBtnClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mUserLoginPresenter.clear(); } }); } @Override public String getUserName() { return mEtUsername.getText().toString().trim(); } @Override public String getPassword() { return mEtPassword.getText().toString().trim(); } @Override public void clearUserName() { mEtUsername.setText(""); } @Override public void clearPassword() { mEtPassword.setText(""); } @Override public void showLoading() { mPbLoading.setVisibility(View.VISIBLE); } @Override public void hideLoading() { mPbLoading.setVisibility(View.GONE); } @Override public void toMainActivity(User user) { Toast.makeText(this, user.getBody().getUser().getUsername() + " 登录成功 , 跳转到主页面", Toast.LENGTH_SHORT).show(); } @Override public void showFailedError() { Toast.makeText(this, "登录失败", Toast.LENGTH_SHORT).show(); } }
对于在Activity中实现我们上述定义的接口,是一件很容易的事,毕竟接口引导我们去完成。
最后看我们的Presenter。
(三)Presenter
Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢?
其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。
1.UserLoginPresenter
public class UserLoginPresenter { private IUserLoginModel iModel; private IUserLoginView userLoginView; public UserLoginPresenter(IUserLoginView userLoginView) { this.userLoginView = userLoginView; this.iModel = new IUserLoginModelImpl(); } public void login() { userLoginView.showLoading(); iModel.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener() { @Override public void loginSuccess(final User user) { Log.e("TAG","loginSuccess:"+Thread.currentThread().getName()); //需要在UI线程执行 userLoginView.toMainActivity(user); userLoginView.hideLoading(); } @Override public void loginFailed() { //需要在UI线程执行 userLoginView.showFailedError(); userLoginView.hideLoading(); } }); } /** * 请求数据 */ public void clear() { userLoginView.clearUserName(); userLoginView.clearPassword(); } }
2.运行起来的时候记得加联网权限
<uses-permission android:name="android.permission.INTERNET"/>
|
注意上述代码,我们的presenter完成二者的交互,那么肯定需要二者的实现类。大致就是从View中获取需要的参数,交给Model去执行业务方法,执行的过程中需要的反馈,以及结果,再让View进行做对应的显示。
三、mvp架构总结
MVP作为MVC的演化,解决了MVC不少的缺点,对于Android来说,MVP的model层相对于MVC是一样的,而activity和fragment不再是controller层,而是纯粹的view层,所有关于用户事件的转发全部交由presenter层处理。下面还是让我们看图
从图中就可以看出,最明显的差别就是view层和model层不再相互可知,完全的解耦,取而代之的presenter层充当了桥梁的作用,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。看到这里大家可能会问,虽然view层和model层解耦了,但是view层和presenter层不是耦合在一起了吗?其实不是的,对于view层和presenter层的通信,我们是可以通过接口实现的,具体的意思就是说我们的activity,fragment可以去实现实现定义好的接口,而在对应的presenter中通过接口调用方法。不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试。这就解决了MVC模式中测试,维护难的问题。
当然,其实最好的方式是使用fragment作为view层,而activity则是用于创建view层(fragment)和presenter层(presenter)的一个控制器。
最后送上DEMO:
https://github.com/qinjieboy/mvp
上一篇: markdown语法