前言
说起MVP大家现在也肯定不陌生了,当项目越来越复杂,参与的开发人员越多的时候,MVP的优势就体现出来了,他会让代码的逻辑特别的清晰,在维护代码或者我们要在一个Activity中加入新功能的时候,特别的方便。其实Android本身的开发可以说是一个MVC模式,Activity兼顾View和Controller,既绑定xml的布局文件,又要在Activity中处理一些逻辑代码,其实这样不太好,耦合度太高,我们把View和Controller抽离出来变成了View和Presenter,这便是MVP模式。
MVP简单介绍
其实网上对它的介绍已经很多了,我也不说了,一张图足以:
Model:比如在Model层中处理网络请求或者一些逻辑,尤其是很多可以复用的业务逻辑,多个模块都会用到的东西写在Model中是最好的了,打个比方,比如说一个项目中多次用到了上传图片,今天又新加了一个模块,此模块又需要上传图片,那么我们就不必在Presenter中再重复的写一次上传图片的功能了,直接用写好的model,传入本地路径,回调中进行操作即可,非常省事。但是如果这一个地方的逻辑只有这里会用到,其实可以考虑将原本写在Model的东西直接写在Presenter中,这样也比较省事。注意Model并不是JavaBean,要区分开来
View:就是Activity,只处理简单的UI操作,设置文本值,或者弹个框啦什么的
Presenter:负责处理Model中的数据,并且写一些逻辑的代码
实践
我自己写了一个MVP,假想了一些场景和需求,然后用最接近实战的方式演示一下:
那么我们要实现两个功能:
- 发送网络请求得到一个名字,显示在Activity中
- 发送网络请求得到一篇文章,显示在Activity中
这个也是我随机想的一些需求吧,实际的需求可能比较复杂,我们就用这几个非常简单的例子来实现MVP:
1、首先先来网络请求基本的操作我发送网络请求是用Retorfit,那么写个接口,url都是虚拟的,这里模拟一下
public interface GetMyInfoApi {
/**
* 获取我的名字
* @param id
* @return
*/
@GET("exempt/getName")
Call<NameBean> getMyName(@Query("id") String id);
/**
* 获取我的文章
* @param id
* @return
*/
@GET("exempt/getArticle")
Call<ArticleBean> getMyArticle(@Query("id") String id);
}
2、接受回调的bean
public class BaseResponseBean implements Serializable {
public String status;
public String errmsg;
/**
* 假设接口返回的state字段值为0证明请求成功,否则请求失败则存在错误信息errmsg
* @return
*/
public boolean isSucceed(){
return TextUtils.equals(status,"0");
}
}
public class NameBean extends BaseResponseBean{
public String name;
}
public class ArticleBean extends BaseResponseBean{
public String article;
}
3、Model层,将回调传给Presenter,让Presenter去处理Model中的数据,记得对bean要进行判空
public class MainModelImpl implements IMainModel{
GetMyInfoApi mApi = RequestUtils.createService(GetMyInfoApi.class);
/**
* 得到我的名字
*
* @param id
* @param callback
*/
@Override
public void getMyName(String id, final ICallback<NameBean> callback) {
mApi.getMyName(id).enqueue(new Callback<NameBean>() {
@Override
public void onResponse(Call<NameBean> call, Response<NameBean> response) {
NameBean bean = response.body();
if (bean == null) {
return;
}
if (bean.isSucceed()) {
callback.onSucceed(bean);
} else {
callback.onError(bean.errmsg);
}
}
@Override
public void onFailure(Call<NameBean> call, Throwable t) {
callback.onError("网络连接不可用!");
}
});
}
/**
* 得到我的文章
*
* @param id
* @param callback
*/
@Override
public void getMyArticle(String id, final ICallback<ArticleBean> callback) {
mApi.getMyArticle(id).enqueue(new Callback<ArticleBean>() {
@Override
public void onResponse(Call<ArticleBean> call, Response<ArticleBean> response) {
ArticleBean bean = response.body();
if (bean == null) {
return;
}
if (bean.isSucceed()) {
callback.onSucceed(bean);
} else {
callback.onError(bean.errmsg);
}
}
@Override
public void onFailure(Call<ArticleBean> call, Throwable t) {
callback.onError("网络连接不可用!");
}
});
}
}
4、接下来就是写接口啦,官方推荐的写法是将View和Presenter这两个接口都放到Contract这个类当中,可以看到我们的任务已经非常的清晰啦,这里要继承BasePresenter和BaseView也是之前的项目这么写,在BaseView当中有个setPresenter的方法,将Presenter传入Activity中。其实也可以简单一点,Presenter和View都不用继承基类,然后在Activity中直接new个Presenter出来,然后直接传入this,Presenter的构造方法中得到mView,这样也是可以的,和大家说一下,以免看不懂为何要继承BasePresenter和BaseView,就是一个习惯问题吧,怎么整都是可以的,只要把View和Presenter整的他俩能联系上了就行。
interface MainContract {
interface Presenter extends BasePresenter{
/**
* 发送网络请求得到name
*/
void getMyName(String id);
/**
* 发送网络请求得到文章内容
*/
void getMyArticle(String id);
}
interface View extends BaseView<Presenter>{
/**
* 显示名称
*/
void showMyName(String name);
/**
* 显示文章
*/
void showMyArticle(String article);
/**
* 显示toast
*/
void showToast(String msg);
/**
* Activity是否被销毁
*/
boolean isViewFinishing();
}
}
5、再就是Activity了,让它接上View接口,new一个Presenter出来传入this,调用mPresenter的getMyName()和getMyArticle方法,id是随便写的假的哈哈,点击事件呢调用mPresenter的addGroup()方法,那么就会发送网络请求,然后在Presenter中又会调用View中的show方法来显示数据。
public class MainActivity extends AppCompatActivity implements MainContract.View{
private TextView tvName;
private TextView tvArticle;
private MainContract.Presenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MainPresenter(this);
initView();
}
public void initView(){
tvName = (TextView) findViewById(R.id.tv_name);
tvArticle = (TextView) findViewById(R.id.tv_article);
mPresenter.getMyName("123");
mPresenter.getMyArticle("123");
}
@Override
public void setPresenter(MainContract.Presenter presenter) {
mPresenter = presenter;
}
/**
* 显示名称
*
* @param name
*/
@Override
public void showMyName(String name) {
tvName.setText(name);
}
/**
* 显示文章
*
* @param article
*/
@Override
public void showMyArticle(String article) {
tvArticle.setText(article);
}
/**
* 显示toast
*
* @param msg
*/
@Override
public void showToast(String msg) {
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
}
/**
* Activity是否被销毁
*/
@Override
public boolean isViewFinishing() {
return isFinishing();
}
}
6、最后写Presenter,得到mView,并且实例化Model,这就是我们说的Presenter与View和Model联系,View和Model是不能直接联系的,得到数据后处理数据,直接在回调中调用mView.show()方法,把数据以参数的形式传过去就OK,我这里因为是假的接口,肯定会执行onFailure()方法,在onFailure中模拟个假数据,这样运行项目就可以显示数据啦。
public class MainPresenter implements MainContract.Presenter{
private MainContract.View mView;
public MainPresenter(MainContract.View mView) {
this.mView = mView;
this.mView.setPresenter(this);
}
/**
* 发送网络请求得到name
*
* @param id
*/
@Override
public void getMyName(String id) {
new MainModelImpl().getMyName(id, new ICallback<NameBean>() {
@Override
public void onSucceed(NameBean data) {
if (mView.isViewFinishing()) {
return;
}
mView.showMyName(data.name);
}
@Override
public void onError(String msg) {
if (mView.isViewFinishing()) {
return;
}
mView.showToast(msg);
}
});
}
/**
* 发送网络请求得到文章内容
*
* @param id
*/
@Override
public void getMyArticle(String id) {
new MainModelImpl().getMyArticle(id, new ICallback<ArticleBean>() {
@Override
public void onSucceed(ArticleBean data) {
if (mView.isViewFinishing()) {
return;
}
mView.showMyArticle(data.article);
}
@Override
public void onError(String msg) {
if (mView.isViewFinishing()) {
return;
}
mView.showToast(msg);
}
});
}
@Override
public void start() {
}
}
可能有的人会觉得这调过来调过去的,不是多此一举吗,其实这也是我的例子写的比较简单,当你的项目很大的时候,用MVP会感觉非常的舒适,你会感觉逻辑非常的清晰,尤其当某一块业务逻辑会多次使用的时候,在Model层的东西可以复用,是非常爽的。当然,如果有那种特殊的情况,比如一个Activity特别简单,甚至连网络都不用请求,那就不用写MVP了。我们一般目录的放法是这样的:Model层独立放一个包,Activity、Contract、Presenter是放在一个包下,可以看Demo
Demo地址:https://github.com/pengboboer/MvpTest
总结
这就是一个最接近实战的一个MVP模式,自己也是重新想了一个也亲自写了一下,加深记忆,网上很多人写的非常的复杂,Model层也不知道写了些什么,模拟也没模拟一个真实的网络请求,有的看起来乱七八糟的,不好理解。如果没用过Retrofit的同学提前看一下Retrofit,那么希望能帮助到一些初学者,大家一起共勉!