从最简单的Android MVP讲起
mvp是一个老生常谈的话题了,网上太多讲MVP的文章了。但有的文章不是结合了rxjva,retrofit等开源项目,就是讲的太过复杂。所以我会写一个最简单的mvp demo。来帮助大家理解mvp的本质。
大多数时候,问题都可以拆解为,WHTA,WHY,HOW;什么是MVP,为什么使用MVP,如何使用MVP。
WHAT:先说什么是MVP,
20150622212916054.jpg上面这张图是网上找的,其实这张图已经很明显了,MVP和MVC的区别,在于以前的View层不仅要和model层交互,还要和controller层交互。而在mvp中,view层只和presenter层交互,而model层也和presenter交互,presenter构成了view层和model层的桥梁,也解耦了view层和model层。这一点很关键。我认为,这也是mvp的本质:
解耦view层和model层,让view层和model层通过presenter层进行通信。换个说法就是让诸如网络请求,数据库读写的逻辑,从activity中剥离出来。activit只负责页面的展示,不关心model层的逻辑。
WHY:之后来谈谈为什么要使用mvp
如果大家阅读过github上的一些开源的android项目,例如telegram,一款即时通讯软件,如果你对它不了解,你可以看一下我之前写的一篇文章在Android Studio上编译自己的Telegram;或者TweetLanes,一个第三方的功能完整的Twitter客户端。这些开源项目都存在一个问题,就是一个activity文件中一两千行,甚至三四千行代码。当然,我想这个在android项目中并不是一个奇怪的现象,当你的项目足够复杂,没有什么是不可能。在基于传统android架构的mvc模式中。model层很多时候只是一个bean类。而view层只是一个xml文件,controller层也就是activity层几乎承担了诸如网络请求,数据库,更新UI等所有的工作,全在activity里完成。这也就导致了activity文件十分庞大臃肿。但是,问题接踵而至。多人维护这样的一个项目是很痛苦的,一个几千行的activity,假如某个人改动了其中的一行,可能会导致其他修改他的人非常痛苦,因为牵扯太多的逻辑了。所以,人们开始尝试把几千行的代码,分成很多模块。网络请求放在一个模块,UI更新放在一个模块,其他的东西放另一个模块。之后就有了MVP。
HOW:在android中如何使用MVP模式
这里我新建了一个项目,项目里有3个class和一个interface,这就是最简单的mvp模式了。
main.png
用到的第三方框架只有常用的okhttp,AndroidManifest里只申请了一个Internet的权限,这里就不放出来了。
xml.png
MainActivity的xml文件也很简单,最上面有个edittext,输入你要请求的网址,中间有个button,点击开始请求,下面有个textview,展示你请求到的内容。大概的逻辑就是这样。这样的逻辑如果是按以前的写法,很简单,几行代码就能搞定,但我现在会用mvp来写。
之前说要剥离逻辑,那么就先从activity谈起。activity在mvp模式中,只是负责界面的展示,其他逻辑放在其他类里,那么怎么进行通信呢?通过接口就行了。所以我们这里先定义一个activity的ui逻辑接口。
public interface MainCallBack {
void getMessage(String message);
void error();
}
之后model层获取的数据,将会通过接口传递到activity中,进行展示。从某种意义上来说,接口其实就是mvp中的view层。因为activity只是这个接口的实现而已。那么我们接下来看一下model层是怎么写的。
public class MainModel {
public Call getData(String url) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
return client.newCall(request);
}
}
同样很简单,这个应该只是okhttp的基础用法。这里我们没有调用enqueue方法传入callback,是为了之后在presenter中使用,当然你也可以在这里调用,不过这样一来,你又要在model中声明一个接口。好了,“view层”和model层都有了,那么最后只要再有presenter层,那么mvp就完成了。
public class MainPresenter {
private MainCallBack callBack;
private MainModel model;
public MainPresenter(MainCallBack callBack) {
this.callBack = callBack;
model=new MainModel();
}
public void getUrlData(String url){
model.getData(url).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callBack.error();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
callBack.getMessage(response.body().string());
}
});
}
}
这是一个典型的mvp模式中的presenter层:接口通过构造函数传入,model直接在构造函数中实例化。presenter中调用model的getdata方法,并传入对应的参数,之后在okhttp的回调中调用对应的ui接口,那么一次mvp模式下的网络请求就完成了。
具体操作可以看一下activity中的代码:
public class MainActivity extends AppCompatActivity implements MainCallBack {
private TextView resultTextView;
private MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.btn);
handler = new MyHandler(this);
final EditText editText = findViewById(R.id.et_url);
resultTextView = findViewById(R.id.tv_result);
final MainPresenter presenter = new MainPresenter(this);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String url = editText.getText().toString();
presenter.getUrlData(url);
}
});
}
@Override
public void getMessage(String message) {
Message msg = handler.obtainMessage(0, message);
handler.sendMessage(msg);
}
@Override
public void error() {
Message msg = handler.obtainMessage(1, "error");
handler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> reference;
private MyHandler(MainActivity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = reference.get();
switch (msg.what) {
case 0:
activity.resultTextView.setText(msg.obj.toString());
break;
case 1:
activity.resultTextView.setText(msg.obj.toString());
break;
}
}
}
}
运行结果如下两图:
success.png
error.png
当网址错误的时候我直接在textview中显示“error”,当然你也可以自由选择显示什么。因为okhttp的回调是在子线程完成的,要刷新ui必须在主线程刷新,所以我这里写了一个handler。当然你也可以在model层进行线程调度。比如一个基于okhttp的开源框架,okgo就是这么做的
okgo.png
他在初始化的时候声明了一个主线程的handler,之后调用handler的post方法,完成从子线程到主线程的切换。当然这都是后话了。
看完这些, 你应该了解了mvp是怎么样的一回事。万变不离其宗,在此基础上扩展,你可以加入任何你喜欢的框架, rxjava,retrofit等。
或许看到这里还是会有人有疑问,我在一个activity中可以完成的事,写成这么几个类有必要吗?虽然我在前面已经解释过了,但我还是会结合我工作中的例子再解释一遍:我之前曾参与开发过一个和机票有关的app,如果有人接触过就知道,机票类app十分的繁杂。流程繁多,规则复杂,而且不允许出错。订票,退票,改签,实名认证,乘机人等,都有一系列的相当复杂的流程。以提交订单为例子,你可能需要提交很多东西,提交退票申请、提交订票申请、提交改签申请等等。逻辑都是先进行一系列检测和判断,弹出dialog框,把数据发送到服务器端,之后视服务器返回结果,进行跳转或者再弹出dialog框。如果你和之前一样,把所有逻辑写在一个activity里面,那么不同的提交,是写在不同的activity类里面的,为了省时间,大概很多人都会选择复制黏贴重复的逻辑。但在这个过程中,可能会产生很多问题,比如你又复制漏了一段代码之类的。如果使用mvp的话,只需要每个activity类都继承相同的ui接口,或者使用相同的model和presenter,那么最后产生的效果其实是一样的。但这样可以让代码更容易维护,修改一个地方的时候,所有地方就都变了。
那么,是否所有地方都需要mvp呢?答案是否定的。我认为,设计模式的关键在于用在正确且合适的地方。我在一个项目中,可能会同时使用mvp,mvc,mvvm。因为我知道这里应该用什么,如果一个逻辑不具有复用的可能,同时也非常简单,那么使用mvp就是多此一举。如果一个页面,带有id的view非常多,那么使用mvvm也就是android的databinding特性可以节约你大量的时间。
最后给出几点使用mvp的建议,经供参考: