MVP架构由浅入深篇一
前言:不得不说,才开始学习MVP架构各种接口的调用和解耦代码真的让人眼花缭乱,对于很多问题都是一知半解:
- 关于如何在Activity中高效的复用Presenter和View;
- Mode层定义到什么程度才算是比较理想的解耦;
- Model层与Presenter层如何比较优雅的相互通信。
所以决定静下心来理解一下该架构的艺术。博客主要参考https://www.jianshu.com/p/5c3bc32afa36?utm_campaign=haruki,其中加入自己的理解和代码实现来学习和验证MVP架构的使用技巧。
目录
一、MVP架构概述
1.为什么要使用MVP架构?
MVP(Model、View、Presenter)模式将Activity中的业务逻辑分离出来,避免了Activity逻辑的高耦合,可以把Model理解成房东,View就是找房的自己,Presenter就是中介,让Activity只做UI的功能,把其他功能抽出去让其他层来完成,缺点就是代码量增加了,但是带来的优点远超缺点,尤其是项目比较大的时候更是事半功倍。
2.MVP理论知识
在MVP架构中跟MVC类似分为三层。
- Activity和Fragment视为View层 --> 理解为四处找房的自己
- Presenter为业务处理层,既能调用UI逻辑,又能请求数据 --> 相当于中介公司
- Model层中包含着具体的数据请求,数据源 --> 相当于房东
三层之间调用顺序为view-->presenter-->model,不能反向调用和跨级调用
由上图可知,Model层通过Callback反馈数据给Presenter层,Presenter层通过View反馈操作给Activity层。其中View和Callback都是以接口的形式存在。
- Callback中定义了请求数据时反馈的各种状态:成功、失败、错误等
- View中定义了Activity的具体操作,主要是将请求得到的数据在界面上更新
二、基础版MVP代码实现
因为是模拟网络数据请求,所以有三个请求数据的按钮分别对应成功、失败、异常三种不同的反馈状态。
1.文件结构
2.Callback接口
因为Callback接口是presenter和model打交道的接口,所以接口中定义了请求数据的方法
package com.example.mvpapplication;
/**
* callback负责presenter和model交互
* 相当于:中介和房东交互接口
*/
public interface MvpCallback {
/**
* 请求数据成功
* @param data
*/
void onSuccess(String data);
/**
* 使用网络API接口请求,请求数据失败
* @param msg
*/
void onFailure(String msg);
/**
* 请求数据时发生错误
*/
void onError();
/**
* 请求数据完成
*/
void onComplete();
}
3.Model 类
Model 类中定了具体的网络请求操作。利用postDelayed
方法模拟耗时操作,通过判断请求参数反馈不同的请求状态:(房东)
package com.example.mvpapplication;
import android.os.Handler;
public class MvpModel {
/**
* 获取网络数据
* @param param 请求参数
* @param callback 数据回调接口 -- callback相当于房东如果有房子就要回复给中介
*/
public static void getNetData(final String param,final MvpCallback callback){
//利用postDelayed方法模拟网络请求数据的操作
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
switch (param){
case "normal":
callback.onSuccess("根据参数"+param+"的请求网络数据成功");
break;
case "failure":
callback.onFailure("请求数据失败");
break;
case "error":
callback.onError();
break;
}
callback.onComplete();
}
},2000);
}
}
4.View 接口
View接口是Activity与Presenter层的中间层,它的作用是根据具体业务的需要,为Presenter提供调用Activity中具体UI逻辑操作的方法。相当于我和中介之间的交易
package com.example.mvpapplication;
/**
* 这里相当于找房子的自己--对UI进行操作的接口
*/
public interface MvpView {
/**
* 显示正在加载数据进度
*/
void showLoading();
/**
* 隐藏进度
*/
void hideLoading();
/**
* 展示数据
* @param data
*/
void showData(String data);
/**
* 显示数据错误原因回调接口
* @param msg
*/
void showFailureMessage(String msg);
/**
* 当数据错误时回调接口
*/
void showErrorMessage();
}
5.Presenter类
Presenter类是具体的逻辑业务处理类,负责请求数据,并对数据请求的反馈进行处理。Presenter相当于中介的作用,所以里面要有MvpView和Callback的声明。
package com.example.mvpapplication;
/**
* 这里相当于中介的作用
*/
public class MvpPresenter {
//因为中介要和雇主打交道,所以要声明MvpView
private MvpView mView;
public MvpPresenter(MvpView view){
this.mView = view;
}
/**
* 相当于帮我找房子--请求数据
* @param param
*/
public void getData(String param){
//显示正在加载进度条
mView.showLoading();
//调用Model请求数据
MvpModel.getNetData(param, new MvpCallback() /*开始和Model打交道的接口*/{
@Override
public void onSuccess(String data) {
//调用view接口显示数据
mView.showData(data);
}
@Override
public void onFailure(String msg) {
mView.showFailureMessage(msg);
}
@Override
public void onError() {
mView.showErrorMessage();
}
@Override
public void onComplete() {
mView.hideLoading();
}
});
}
}
6.布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="点击按钮获取网络数据"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取数据【成功】"
android:onClick="getData"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取数据【失败】"
android:onClick="getDataForFailure"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取数据【异常】"
android:onClick="getDataForError"
/>
</LinearLayout>
7.MainActivity
在MainActivity中,主要是对MvpView的实现,进行UI操作
package com.example.mvpapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements MvpView{
//进度条
ProgressDialog progressDialog;
TextView text;
//找到中介
MvpPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = findViewById(R.id.text);
//初始化进度条
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setMessage("正在加载中...");
//初始化presenter
presenter = new MvpPresenter(this);
}
/**
* button事件
*/
public void getData(View view){
presenter.getData("normal");
}
public void getDataForFailure(View view){
presenter.getData("failure");
}
public void getDataForError(View view){
presenter.getData("error");
}
@Override
public void showLoading() {
if (!progressDialog.isShowing()){
progressDialog.show();
}
}
@Override
public void hideLoading() {
if (progressDialog.isShowing()){
progressDialog.dismiss();
}
}
@Override
public void showData(String data) {
text.setText(data);
}
@Override
public void showFailureMessage(String msg) {
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
text.setText(msg);
}
@Override
public void showErrorMessage() {
Toast.makeText(this,"请求异常",Toast.LENGTH_SHORT).show();
text.setText("请求异常");
}
}
8.运行结果:(以成功请求数据为例)
至此,基础版MVP已经完成了,下面我们来分析一下基础版存在的问题和如何优化?
三、基础版MVP问题分析
通过上面的代码实现,看似已经完成了MVP的框架了,但是远远不够,其中一个问题就是复用问题。
由于上述例子我们只有一个业务逻辑,即只有一个Activity,那如果有两个或者更多Activity呢?我们需要创建N多个MVP框架吗?当然在不需要MVP的Activity中当然不用创建,可是当需要MVP的Activity呢?当然这也要分情况,如果Activity业务逻辑相似当然可以复用,但是当业务逻辑不一样时,就必须创建另一个MVP了。可是又如何复用业务逻辑相似的MVP呢?请看下述分析:
场景1:业务逻辑完全相同
这种场景很简单,由于业务逻辑完全相同,就直接复用就好了。
场景2、3:包含部分相同业务逻辑
场景2和场景3的逻辑类似,都属于一个业务逻辑中包含另外一个可以单独存在的业务逻辑,这种情况采用继承的方法即可。
场景4
场景4中Activity C想要同时调用独立服务于Activity A 和 Activity B的业务逻辑,只需要将两个业务逻辑对应的Presenter分别实例化并调用业务方法即可。
场景5
场景5属于场景3与场景4的结合体,同样需要先把A和B的业务逻辑拆分开,然后同时调用。
基础版MVP总结
从上述的分析可知,基础版MVP存在复用问题。由于MVP框架还算是比较繁重的,所以复用很有必要,可以减少很多没必要的重复代码。我们能想到的方法就是类比Binder连接池,在Presenter层写完整的业务逻辑,然后通过不同的业务标志进行不同的业务处理,这种方式很简单,但是这样的话,可能会有一定的问题,比如Presenter层业务过于繁重,维护较难等。毕竟架构师提出的复用方案是实践得出的真理,存在即是合理的。在下一篇中,我们将介绍进阶版MVP框架的实现。