1. MVP定义
MVP全称 Model-View-Presenter,即模型-视图-层现器。
Model: 对于Model层也是数据层。它区别于MVC架构中的Model,在这里不仅仅只是数据模型。在MVP架构中Model它负责对数据的存取操作,例如对数据库的读写,网络的数据的请求等。
View::对于View层也是视图层,在View层中只负责对数据的展示,提供友好的界面与用户进行交互。在Android开发中通常将Activity或者Fragment作为View层。
Presenter:对于Presenter层是连接View层与Model层的桥梁并对业务逻辑进行处理。在MVP架构中Model与View无法直接进行交互。所以在Presenter层它会从Model层获得所需要的数据,进行一些适当的处理后交由View层进行显示。这样通过Presenter将View与Model进行隔离,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离。
MVP结构图如下所示:
调用顺序:
a. 用户操作了界面;
b. View层相应用户操作,然后向Presenter层发出请求;
c. Presenter层接受了View层的请求,调用Module层处理业务逻辑,Module处理完毕后返回给Presenter 层,然后Presenter 层调用View层显示相应的结果。
2. 案例场景
当用户启动 App 进入页面后,页面都会进行像服务端请求数据,根据结果显示获得的数据。如果请求数据发生异常,则显示相应的提示页面。下面将根据这个场景来写相关的 MVP 代码。
3. 最简单的MVP
Presenter 层:
接口:
package cn.zzw.messenger.simplemvpdemo.mvp;
/**
* @author 鹭岛猥琐男
* @create 2019/9/5 21:35
*/
public interface IPresenter {
public void getTaskInfo();
public void requestSuccess(DataBean bean);
}
实现类:
package cn.zzw.messenger.simplemvpdemo.mvp;
/**
* @author 鹭岛猥琐男
* @create 2019/9/5 21:35
*/
public class SimplePresenter implements IPresenter {
private IView mView;
private IModule mModule;
public SimplePresenter(IView mView) {
this.mView = mView;
mModule = new SimpleModule();
}
@Override
public void getTaskInfo() {
//接收View获取数据的请求,Presenter向Module请求数据
mView.showProgress();
mModule.requestData(this);
}
public void requestSuccess(DataBean bean) {
//数据请求成功后,通知View
mView.hideProgress();
mView.showSuccess(bean);
}
}
Module 层:
接口:
package cn.zzw.messenger.simplemvpdemo.mvp;
/**
* @author 鹭岛猥琐男
* @create 2019/9/5 21:36
*/
public interface IModule {
public void requestData(IPresenter presenter);
}
实现类:
package cn.zzw.messenger.simplemvpdemo.mvp;
import android.os.AsyncTask;
import android.util.Log;
import java.util.Random;
/**
* @author 鹭岛猥琐男
* @create 2019/9/5 21:57
*/
public class SimpleModule implements IModule {
IPresenter mPresenter;
@Override
public void requestData(IPresenter presenter) {
this.mPresenter = presenter;
new RequestAsyncTask().execute();
}
/* 这里用AsyncTask模拟请求数据,并延迟5秒*/
class RequestAsyncTask extends AsyncTask<Void, Void, DataBean> {
@Override
protected DataBean doInBackground(Void... voids) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e("zzw", "延迟5秒");
Random random = new Random();
DataBean mBean = new DataBean("鹭岛猥琐男", random.nextInt(50));
return mBean;
}
@Override
protected void onPostExecute(DataBean bean) {
super.onPostExecute(bean);
mPresenter.requestSuccess(bean);//当请求数据成功后,通知给Presenter
}
}
}
数据的实体类:
package cn.zzw.messenger.simplemvpdemo.mvp;
/**
* @author 鹭岛猥琐男
* @create 2019/9/5 21:33
*/
public class DataBean {
private String name;
private int age;
public DataBean(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "我的名字是" + name + ",今年" + age + "岁。";
}
}
View 层:
Activity页面的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/mTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Activity类:
package cn.zzw.messenger.simplemvpdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.widget.TextView;
import cn.zzw.messenger.simplemvpdemo.mvp.DataBean;
import cn.zzw.messenger.simplemvpdemo.mvp.IPresenter;
import cn.zzw.messenger.simplemvpdemo.mvp.IView;
import cn.zzw.messenger.simplemvpdemo.mvp.SimplePresenter;
/**
* @author 鹭岛猥琐男
* @create 2019/9/3 21:23
*/
public class MainActivity extends AppCompatActivity implements IView {
ProgressDialog mProgressDialog;
IPresenter mPresenter;
TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mTv = findViewById(R.id.mTv);
mPresenter = new SimplePresenter(this);
mPresenter.getTaskInfo();
}
@Override
public void showProgress() {
if (null == mProgressDialog) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("数据加载中...");
}
mProgressDialog.show();
}
@Override
public void hideProgress() {
if (null != mProgressDialog) {
mProgressDialog.hide();
}
mProgressDialog = null;
}
@Override
public void showError() {
}
@Override
public void showSuccess(DataBean info) {
mTv.setText(info.toString());
}
@Override
protected void onStop() {
super.onStop();
hideProgress();
}
}
效果:
从上面的例子,可以看出:
1. 业务逻辑已经不在Activity 中处理,Activity只是负责View的显示和隐藏。
2. View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。
但是它存在以下问题:
1. Presenter 持有 View 的引用,以及 Module 的引用,这样会造成内存泄漏
2. 每个页面都需要写相应的接口 IView、IPresente、、IModule 以及对应的实现类,这样文件数就会特别多。
3. 优化后的 MVP
此种写法是参考Google官方的mvp例子进行实现的:https://github.com/googlesamples/android-architecture/tree/todo-mvp/todoapp。
页面的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/mTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="Hello World!"
android:textSize="20sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Presenter层的接口:
BasePresenter:
package cn.zzw.javamvpdemo.mvp;
/**
* @author 鹭岛猥琐男
* @create 2019/9/8 08:38
*/
public interface BasePresenter<V extends BaseView> {
public void initData();
}
View层的接口:
BaseView:
package cn.zzw.javamvpdemo.mvp;
/**
* @author 鹭岛猥琐男
* @create 2019/9/8 08:41
*/
public interface BaseView<T extends BasePresenter> {
public void showLoading();
public void cancelLoading();
}
Contract类:
MainContract :
package cn.zzw.javamvpdemo.mvp;
/**
* @author 鹭岛猥琐男
* @create 2019/9/8 08:45
*/
public interface MainContract {
interface IView extends BaseView {
public void showInfo(DataBean bean);
}
interface IPresenter extends BasePresenter {
public void requestDataSuccess(DataBean bean);
public void attachView(IView view);
public void detachView();
}
}
Presenter层对应的实现类:
MainPresenter:
package cn.zzw.javamvpdemo.mvp;
import android.os.AsyncTask;
import android.util.Log;
import java.util.Random;
/**
* @author 鹭岛猥琐男
* @create 2019/9/8 09:05
*/
public class MainPresenter implements MainContract.IPresenter {
private MainContract.IView mView;
@Override
public void initData() {
mView.showLoading();
new RequestAsyncTask().execute();
}
@Override
public void requestDataSuccess(DataBean bean) {
if (mView != null) {
mView.cancelLoading();
mView.showInfo(bean);
}
}
@Override
public void attachView(MainContract.IView view) {
if (view != null) {
mView = view;
}
}
@Override
public void detachView() {
if (mView != null) {
mView = null;
}
}
/* 这里用AsyncTask模拟请求数据,并延迟5秒*/
class RequestAsyncTask extends AsyncTask<Void, Void, DataBean> {
@Override
protected DataBean doInBackground(Void... voids) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e("zzw", "延迟5秒");
Random random = new Random();
DataBean mBean = new DataBean("鹭岛猥琐男", random.nextInt(50));
return mBean;
}
@Override
protected void onPostExecute(DataBean bean) {
super.onPostExecute(bean);
requestDataSuccess(bean);
}
}
}
Activity:
package cn.zzw.javamvpdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.widget.TextView;
import cn.zzw.javamvpdemo.mvp.DataBean;
import cn.zzw.javamvpdemo.mvp.MainContract;
import cn.zzw.javamvpdemo.mvp.MainPresenter;
/**
* @author 鹭岛猥琐男
* @create 2019/9/8 08:35
*/
public class MainActivity extends AppCompatActivity implements MainContract.IView {
private ProgressDialog mProgressDialog;
private MainContract.IPresenter mPresenter;
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.mTv);
mPresenter = new MainPresenter();//创建对应的Presenter
mPresenter.attachView(this);//Presenter绑定View
mPresenter.initData();
}
@Override
public void showLoading() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("加载中...");
mProgressDialog.show();
}
}
@Override
public void cancelLoading() {
if (mProgressDialog != null) {
if (mProgressDialog.isShowing()) {
mProgressDialog.hide();
}
mProgressDialog = null;
}
}
@Override
public void showInfo(DataBean bean) {
mTv.setText(bean.toString());
}
@Override
protected void onDestroy() { //当页面销毁的时候,取消Presenter对View的持有
super.onDestroy();
if (null != mPresenter) {
mPresenter.detachView();
mPresenter = null;
}
}
}
效果跟上面的例子一样,这里就不贴图了。
此种写法跟上一种写法相比,多了一个Contract类,将 View 层和 Presenter 层写在一起,条理更加清晰了,当接口变多了,Contract 优势明显出来了。而且在这种写法中当Activity关闭的时候,实现了 Presenter 中对View的解绑定,以及在Presenter类中增加了对View的判空,从而规避空指针。
参考:
https://github.com/LRH1993/android_interview/blob/master/android/advance/mvp.md
https://blog.csdn.net/suyimin2010/article/details/82077523
https://www.cnblogs.com/ijaye/p/8588481.html
https://github.com/googlesamples/android-architecture/tree/todo-mvp/