Android MVP 架构介绍与使用入门 [复制链接]

2019-8-12 09:54
小小CTO 阅读:1176 评论:1 赞:1
Tag:  MVP

MVP 与 MVC 简介

MVP 软件架构在现在的应用中, 特别是 Android 端的编程中尤为突出的使用,因为 MVP 架构可以很深层次的去解耦视图、业务逻辑、数据源三者的关系,让它们之间的相互依赖性降低。MVP 是针对 Android 端开发而言的,它其实是 MVC 演变过来的,因为 MVC 模式在 Android 开发中并不是那么愉快。主要表现方面,比如,我们在写一个功能模块时,Activity 中的代码很容易就突破到上千甚至上万行,除了必要的 findviewid,listener 等,其他的代码几乎都是业务逻辑相关的,这就显得 Activity 非常的臃肿,也不是说不可以,但是在模块升级时造成业务逻辑的改变,我们就需要去成千上万行的 Activity 中寻找业务逻辑代码,这就有可能出现多处代码需要修改,不细心的话,非常容易出 Bug,而且除了 Bug 代码也不好定位。

第二个问题,数据源(来自数据库或这网络)部分可能会写好几份,比如,有两个 Activity 同时有着个人信息数据要显示,我必须在这两个 Activity 中各请求一次网络或者访问数据库,得到最新的数据,这就出现了代码重复的问题。

带着这两个问题,我们来看看 MVP 架构能够怎样帮助我们解决 MVC 出现的这种问题!首先,我来简单的介绍一下 MVC 它指的是:

  • model(模型),负责访问网络数据、访问数据库数据,提供数据源
  • view(视图),负责更新界面、响应用户界面操作
  • controller(控制器),负责业务逻辑控制,处理数据

我们在 Android 开发中,Activity 负责 UI 显示及更新操作,相当于 View 层,我们一般都会把代码直接写在 Activity 中,不管是数据源、逻辑控制、数据处理等操作一个劲的往 Activity 里写,那么它们三者的关系都互相依赖,你中有我,我中有你,它们就形成了三角的关系。

Android MVP 架构介绍与使用入门

这样的关系,导致 Activity 代码量过于庞大,修改和维护起来比较困难。这样的三角恋关系,显然不是长久之际。于是,MVP 它就强行把 View 与 Model 隔离了,让它们不再有联系,传说中的 Presenter 就是脚踏两只船,来回奔走于 View 与 Model 之间的契约者、联系者。

MVP(model、view 、presenter),将 Activity 中的代码抽离了出来,把 Activity(Fragment、View) 只当作 View 层,通过一个 Presenter(契约层)将 View 和 Model 层联系起来,让 View 和 Model 层充分的解耦。

我们访问网络得到数据并显示出来会是这样一个流程:

  1. Activity 启动时,告诉 Presenter 我要数据了
  2. Presenter 就会叫 Model 去访问数据接口得到数据
  3. Model 得到原数据就交给 Presenter 了,Presenter 一看数据不规范,赶紧处理一下,处理完成
  4. Presenter 就把处理后的数据汇报给 Activity(View),View 拿到数据就做显示的操作,工作结束

MVC

下面我们通过一个简单的案例来进行了解 MVP 架构的代码写法,当然,这里的写法不唯一,因为每个人写代码的方式都是不一样的,最核心的是 MVP 架构的思想,把思想转为实际代码,这才是值得我们探索和学习的地方。

来看看代码,我们在网络请求时,获取到数据,并 toast 和显示到 textview 上,这样的代码我相信谁都会写,但一般人都是直接一口气写在 MainActivity 类上面,比如这样:

public class MainActivity extends AppCompatActivity {

private TextView tv;
private static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initViews();

request();
showDialog();
}

private void initViews() {
tv = findViewById(R.id.tv);
}

private void showDialog() {
ProgressDialog dialog = new ProgressDialog(this);
dialog.show();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
dialog.dismiss();
}
}, 1500);
}

private void request() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com/")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}

@Override
public void onResponse(Call call, Response response) throws IOException {
String resp = response.body().string();
toast(resp);
}
});
}

private void toast(String resp) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "" + resp, Toast.LENGTH_SHORT).show();
tv.setText(resp);
}
});
}

}

它的效果其实就是请求百度首页,返回网页数据以 string 方式显示出来而已。

Android MVP 架构介绍与使用入门

上面的代码,其实就是 MVC 架构的写法,把 Model(网页数据)、Presenter(数据处理)、View(activity)三层写在了一起,虽然没什么问题,但我们看看 MainActivity 中的代码比较杂乱,在业务代码比较多的情况下,MainActivity 更加的沉重,不利于维护与业务升级。

MVP 实战

那么我们下面就要将这个类中的代码改写为 MVP 的写法,回顾上面提及的 MVP 架构的思想,它是将 View 层与 Model 层彻底隔离,意味着 View 和 Model 都不再持对方的引用,它们通过一个第三者 Presenter 来代理事物的传递,所以 Presenter 层会持有 Model 与 View 层的引用,这是第一步。

第二步,是将它们之间的联系抽象出来,以接口的方式相互调用,所以 Model 、View、Presenter 各自拥有自己的接口和抽象方法,所以这就会无形的多出了三个接口类,这也就是 MVP 的缺点之一。所以,为了较少的创建接口类,我们就给这三层接口定义了一个契约接口,把它们更加紧密的结合在一起,方法查看,例如代码这样写:

(1)契约类:

package com.test.mvp.mvpdemo.mvp.v1;

import okhttp3.Callback;

/**
* 契约接口,可以很直观的看到 M、V、P 层接口中提供的方法
*/
public interface MainContract {
interface IMainModel {
void requestBaidu(Callback callback);
}

interface IMainView {
void showDialog();

void succes(String content);
}

interface IMainPresenter {
void handlerData();
}
}

然后,再将之前的一个单独的 MainActivity 分包,分别创建 Model 层实现类、Presenter 层实现类、MainActivity 就相当于View 层,这样一来架构就更加清晰明了:

Android MVP 架构介绍与使用入门

接着,分别给这三层实现我们刚刚写的 MainContract 中相对应的接口,我们先来看看 Model 层,它就主要负责网络请求,也就是我们的 OKHttp 请求到百度首页的一个操作,很简单的代码。

(2)Model 层:

package com.test.mvp.mvpdemo.mvp.v1.model;

import com.test.mvp.mvpdemo.mvp.v1.MainContract;

import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;

/**
* model 层,请求网络或数据库,提供数据源(原始数据)
*/
public class DataModel implements MainContract.IMainModel {

@Override
public void requestBaidu(Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com/")
.build();
client.newCall(request).enqueue(callback);
}
}

这里需要传入一个 OKHttp 的 Callback ,由于不能在 Model 层中处理业务数据,所以我们让它自己回调给 Presenter 层来处理,保证数据的原始状态。

(3)Presenter 层:

然后是 Presenter 层,它是处理业务逻辑和业务数据的,所以必须持有 Model 的引用,同时要将处理完的数据交给 View 层用于显示,也必须持有 View 的引用,那么,一开始我们就要把这两层给实例化,具体看下面的代码:

package com.test.mvp.mvpdemo.mvp.v1.presenter;

import com.test.mvp.mvpdemo.mvp.v1.MainContract;
import com.test.mvp.mvpdemo.mvp.v1.model.DataModel;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

/**
* presenter 层,承担业务逻辑处理,数据源处理等
*/
public class MainPresenter implements MainContract.IMainPresenter {

private MainContract.IMainModel mModel;
private MainContract.IMainView mView;

public MainPresenter(MainContract.IMainView view) {
this.mView = view;
mModel = new DataModel();
}

@Override
public void handlerData() {
if (mView != null) {
mView.showDialog();
}
/**
* 发起请求,获得回调数据
*/
mModel.requestBaidu(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}

@Override
public void onResponse(Call call, Response response) throws IOException {
String content = response.body().string();
if (mView != null) {
mView.succes(content);
}
}
});
}
}

这里的处理数据我就不做说明了,注意一点,这里是请求网络数据会有延时,我们 Presenter 层持有了 View 层的引用,也就是 Activity 的引用,在网络堵塞的情况下,用户在打开 Activity 又马上给关闭了,这时 View 相当于被摧毁了,如果不进行判断 View 引用是否为空,当数据在延迟几秒后返回时,就会造成空指针异常,所以每次都要进行判断才合理。

最后,在 Presenter 层处理完了数据,就要将数据传达给 View 层显示,所以要调用 View 接口的抽象方法,把数据参数传递过去,那么 View 在收到数据后,不需要多余的逻辑操作,直接显示就好了。那么 View(MainActivity)的代码应该做如下修改:

(4)View 层:

package com.test.mvp.mvpdemo.mvp.v1.view;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;

import com.test.mvp.mvpdemo.R;
import com.test.mvp.mvpdemo.mvp.v1.MainContract;
import com.test.mvp.mvpdemo.mvp.v1.presenter.MainPresenter;

/**
* MVP 的写法,Version 1: 基础写法
*/
public class MainActivity extends AppCompatActivity implements MainContract.IMainView {

private TextView tv;

private MainPresenter mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initViews();

mPresenter = new MainPresenter(this);
mPresenter.handlerData();
}

private void initViews() {
tv = findViewById(R.id.tv);
}

@Override
public void showDialog() {
ProgressDialog dialog = new ProgressDialog(this);
dialog.show();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
dialog.dismiss();
}
}, 1500);
}

@Override
public void succes(String content) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "" + content, Toast.LENGTH_SHORT).show();
tv.setText(content);
}
});
}
}

使用 MVP 架构,我们可以做到 Model 层和 Presenter 层的复用,如果不同的 Veiw 层需要相同的数据,那么就无需修改 Model 层和 Presenter 层,直接实现接口就可以了。

到此为止,一个从网络获取数据并显示的案例就被我们改写为 MVP 架构了,这是最基础的 MVP 的入门版本,其中精要的就是interface 接口的使用,而接口的用法也是 Java 的基础,所以本篇文章内容应该不难理解。而 MVP 架构也无需引入哪些库、框架啊等等,它更是一种编程架构、编程思想,并且现在也非常流行,所以我们一定要搞定它。


我来说两句
您需要登录后才可以评论 登录 | 立即注册
facelist
所有评论(1)
gunner880101 2019-8-12 11:19
  
回复
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )