为啥要用MVP
网上有很多关于怎样写好MVP模式的文章,但是我还是想写一篇关于此类的文章,多一个不多少一个不少,仅供参考提供不同的见解嘛</可爱>。
很早就像写一篇MVC的进阶篇MVP了,MVC这里就不了说太多了,因为即使你不同设计模式,写代码的风格和MVC也差不了太远啦,MVC(model view control)M为数据,View为视图,C的控制即接受操作数据展示在View上,在Android中View即为XML布局,M的各种实体数据类,C为Activity,在实际开发中分的并没有那么清晰,因为往往实际需求都比较复杂(一个Activity往往有很多个网络请求)导致M和C都挤在Activity里面,使得Activity轻松就能上1000+行代码,这就是为啥要使用MVP的缘故了,这样一来给项目的后期维护修改带来了很大的困难,特别是还不写注释,看起啦简直不要太爽。
什么是MVP
MVP即Model View Presenter,从字面上就能看到C变成了P即通俗理解为代表层,P持有M和V的引用。数据和视图的交互都通过P,即M和V不直接打交道,必须通过P来间接通信。我说的这么通俗,你们应该能够看懂哈,(看不懂顺着网线来打我呀^_^)
怎样写MVP
对于怎样写MVP模式,可谓仁者见仁智者见智,这里已登录操作作为背景,来看看MVP咋写,先来看初级版的MVP吧,我们先创建UserBean实体类
public class UserBean {
private String Id;
private String Name;
public String getId() {
return Id;
}
public void setId(String id) {
Id = id;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
}
就是普通的实体类,再写一个UserBean的接口IUser来定义对UserBean实体类的数据操作接口,保存和载入数据
public interface IUser {
void SaveUserInfo(UserBean user);
UserBean loadUser();
}
然后在创建User类实现IUser接口,实现对UserBean的数据操作
public class User implements IUser {
private UserBean userBean;
@Override
public void SaveUserInfo(UserBean user) {
this.userBean=user;
}
@Override
public UserBean loadUser() {
if(userBean!=null){
return userBean;
}
return null;
}
}
好了Model到这就写完了,接下来就开始写View了,除了XML外要添加一个IUserView 的接口,XML就不贴了两个按钮和两个编辑框,下面是IUserView 的代码
public interface IUserView {
UserBean getUser();
void setUser(UserBean user);
}
用于定义View对数据的操作,这个接口是Activity 需要实现的,这样Activity就是IUserView 的实现类了
下面是重要的presenter的代码,它持有对View和Model的引用、定义了保存和载入的 两个方法
public class UserPresenter {
private IUserView userView;
private IUser user;
public UserPresenter(IUserView userView) {
this.userView = userView;
user=new User();
}
public void saveUser(){
user.SaveUserInfo(userView.getUser());
}
public void loadUser(){
userView.setUser(user.loadUser());
}
}
下面来看Activity的代码
public class MainActivity extends AppCompatActivity implements IUserView{
private TextView id_tv,name_tv;
private EditText id_ev,name_ev;
private Button save,load;
private UserPresenter userPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
userPresenter=new UserPresenter(this);
id_tv=findViewById(R.id.id_tv);
name_tv=findViewById(R.id.name_tv);
id_ev=findViewById(R.id.id_ev);
name_ev=findViewById(R.id.name_ev);
save=findViewById(R.id.save_btn);
load=findViewById(R.id.load_btn);
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userPresenter.saveUser();
}
});
load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userPresenter.loadUser();
}
});
}
//从xml界面获取用户名和密码
@Override
public UserBean getUser() {
return null;
}
//设置用户名和密码到xml界面
@Override
public void setUser(UserBean user) {
}
}
可以看到两按钮的点击事件里面的方式是Presenter已经定义好了的,像这样把所有的数据逻辑操作都定义在UserPresenter中这样看起来就不会很乱特别是有需求很复杂的时候,思路就比较清晰了。到这里一个初级版的MVP结构就已经写完了,但是看着代码量比起MVC多了很多呀,难不成每一个需求写一个Presenter么?是不是费力不讨好呢,就现在的需求来看这样写确实费力不讨好。所以说有时候不能为了设计而设计。
这里还有一个问题会存在内存泄露,如果在Presenter中执行了耗时操作,在还没等耗时操作完Activity就退出来得话就会有内存泄露,这个问题暂且放在这儿。
我想这么简单的MVP你们应该看懂了,下面就来个进阶版的MVP
进阶版MVP
我们可以写一个基类来定义一些基本的操作,让其类实现/继承这基类就可以简化大量操作代码,好的,先来一个BaseView
public interface BaseView<T> {
/**
* 显示正在加载
*/
void showLoading();
/**
* 关闭加载
*/
void hideLoading();
/**
* 提示信息
* @param msg
*/
void showToast(String msg);
/**
* 显示错误提示
*/
void showError();
/**
* 显示网络请求数据
* @param data
*/
void showData(T data);
/**
* 获取上下文
* @return
*/
Context getContent();
}
这个类定义个一些对用户友好的提示,如果你还要其他的,可以自行添加,接下来来看网络请求回调响应的基类CallBack
public interface CallBack<T> {
/**
* 数据请求成功回调
* @param data
*/
void onSuccess(T data);
/**
* 网络请求失败提示信息
* @param msg
*/
void onFail(String msg);
/**
* 网络请求发送错误提示信息
* @param msg
*/
void onError(String msg);
/**
* 请求结束,无论请求结果是否成功
*/
void onComplete();
}
好了,现在还差BasePresenter基类,它持有BaseVIew的子类,定义个绑定Activity和解绑Activity的操作,解决上面内存泄露的问题,当要退出当前Activity的时候在onDestory回调detachView()方法解绑就可以了
public class BasePresenter<V extends BaseView> {
//需要绑定的view
private V view;
//绑定view时初始化 相关参数
public void attachView(V view){
this.view=view;
}
//解绑View
public void detachView(){
this.view=null;
}
//获取view的绑定状态
public boolean isViewAttach(){
return view!=null;
}
//获取绑定的View
public V getView(){
return view;
}
}
数据实体类的和上面初级版的一样。来看看Model,来模拟网络数据请求,定义了三种情况,请求成功,请求失败,请求发送错误和请求完成。
public class Model {
public static void getNetData(final String params, final CallBack<String> callBack){
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
switch (params){
case "success":
callBack.onSuccess("请求成功");
break;
case "failure":
callBack.onFail("请求失败");
break;
case "error":
callBack.onError("请求发送错误");
break;
}
callBack.onComplete();
}
},3000);
}
}
下面来创建Presenter MvpPresenter
public class MvpPresenter extends BasePresenter<MVPView> {
public void getData(String params){
if(!isViewAttach()){
//如果没有持有view直接返回
return;
}
//显示加载进度
getView().showLoading();
Model.getNetData(params, new CallBack<String>() {
@Override
public void onSuccess(String data) {
if(isViewAttach()){
getView().showData(data);
}
}
@Override
public void onFail(String msg) {
if(isViewAttach()){
getView().showToast(msg);
}
}
@Override
public void onError(String msg) {
if(isViewAttach()){
getView().showError();
}
}
@Override
public void onComplete() {
if(isViewAttach()){
getView().hideLoading();
}
}
});
}
}
对于Activity也创建一个BaseActivity,写上一些公共的方法,它实现了BaseView
public abstract class BaseActivity extends AppCompatActivity implements BaseView{
private ProgressDialog progressDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressDialog=new ProgressDialog(this);
progressDialog.setCancelable(false);
}
@Override
public void showLoading() {
if(!progressDialog.isShowing()){
progressDialog.show();
}
}
@Override
public void hideLoading() {
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
}
@Override
public void showToast(String msg) {
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
}
@Override
public void showError() {
Toast.makeText(this,"发生错误",Toast.LENGTH_SHORT).show();
}
@Override
public Context getContent() {
return BaseActivity.this;
}
}
好了基本上框架基本搭完了,等等,还少显示视图数据的接口MVPView
public interface MVPView extends BaseView{
/**
* 当请求数据成功后回调此接口
* @param data
*/
@Override
void showData(Object data);
}
这样就完了
来看看对于一个登录的需求怎样运用MVP框架
public class MainActivity extends BaseActivity implements MVPView{
private TextView id_tv,name_tv;
private EditText id_ev,name_ev;
private Button save,load;
private MvpPresenter mvpPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mvpPresenter=new MvpPresenter();
mvpPresenter.attachView(this);
initView();
}
private void initView() {
id_tv=findViewById(R.id.id_tv);
name_tv=findViewById(R.id.name_tv);
id_ev=findViewById(R.id.id_ev);
name_ev=findViewById(R.id.name_ev);
save=findViewById(R.id.save_btn);
load=findViewById(R.id.load_btn);
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mvpPresenter.getData("保存");
}
});
load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mvpPresenter.getData("载入");
}
});
}
//回到现实数据
@Override
public void showData(Object data) {
}
@Override
protected void onDestroy() {
mvpPresenter.detachView();
super.onDestroy();
}
}
同样还是登录的Activity是不是看上去代码更优雅了呢?(劳资就是要说优雅,不服来打我呀)。到这里你会发现有个问题,对于不同的需求还是要重新写不同的具体Model和不同的具体Presenter,嗯这是个问题,不过可以解决的,留在下次写吧,要相信寄几个儿嘛