一、架构概述
对于MVP (Model View Presenter)
架构是从著名的MVC(Model View Controller)
架构演变而来的。而对于Android
应用的开发中本身可视为一种MVC
架构。通常在开发中将XML
文件视为MVC
中的View
角色,而将Activity
则视为MVC
中的Controller
角色。不过更多情况下在实际应用开发中Activity
不能够完全充当Controller
,而是Controller
和View
的合体。于是Activity
既要负责视图的显示,又要负责对业务逻辑的处理。这样在Activity
中代码达到上千行,甚至几千行都不足为其,同时这样的Activity
也显得臃肿不堪。所以对于MVC
架构并不很合适运用于Android
的开发中。
二、架构简介
对于一个应用而言我们需要对它抽象出各个层面,而在MVP
架构中它将UI
界面和数据进行隔离,所以我们的应用也就分为三个层次。
View
:对于View
层也是视图层,在View
层中只负责对数据的展示,提供友好的界面与用户进行交互。在Android
开发中通常将Activity
或者Fragment
作为View
层。Model
: 对于Model
层也是数据层。它区别于MVC
架构中的Model
,在这里不仅仅只是数据模型。在MVP
架构中Model
它负责对数据的存取操作,例如对数据库的读写,网络的数据的请求等。Presenter
: 对于Presenter
层他是连接View
层与Model
层的桥梁并对业务逻辑进行处理。在MVP
架构中Model
与View
无法直接进行交互。所以在Presenter
层它会从Model
层获得所需要的数据,进行一些适当的处理后交由View
层进行显示。这样通过Presenter
将View与Model
进行隔离,使得View
和Model
之间不存在耦合,同时也将业务逻辑从View
中抽离。
下面通过MVP结构图来看一下MVP中各个层次之间的关系。
在MVP
架构中将这三层分别抽象到各自的接口当中。通过接口将层次之间进行隔离,而Presenter
对View
和Model
的相互依赖也是依赖于各自的接口。这点符合了接口隔离原则,也正是面向接口编程。在Presenter
层中包含了一个View
接口,并且依赖于Model
接口,从而将Model
层与View
层联系在一起。而对于View
层会持有一个Presenter
成员变量并且只保留对Presenter
接口的调用,具体业务逻辑全部交由Presenter
接口实现类中处理。
三、Demo演示
实现一个模拟下载过程的操作,Demo下载地址:Gitee
动画演示过程如下:
通过点击底部的按键,来实现下载进度、下载进度展示图片、进图条以及各个按键的变化。
四、Demo分析
运用MVP
架构思想,View
层就是MainActivity
,负责UI
的更新;Model
层是DownloadUtil
下载模块,负责文件的模拟下载;Persenter
层就是代码中的PersenterImpl
文件,负责View
层及Model
数据交互。另外StatusMsg
为用户自定义的消息码。
AS工程目录结构:
五、Demo代码实现
- 首先编写布局文件
效果图如下:
xml文件代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/linearlayout_ancestor"
android:background="@color/colorLightBlue"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/linearlayout_outline_border"
android:background="@color/colorSnow"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp">
<LinearLayout
android:id="@+id/linearlayout_show_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPeachPuff"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp">
<TextView
android:id="@+id/tv_show_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/show_title_info"
android:gravity="center_horizontal"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearlayout_download_info"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp">
<LinearLayout
android:id="@+id/linearlayout_download_info_download_progress_textview"
android:layout_weight="1"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_download_progress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/download_progress_title"
android:textColor="@color/colorHotPink"
android:gravity="center"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearlayout_show_download_progress"
android:layout_weight="1"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_show_download_progress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/download_progress"
android:textColor="@color/colorMediumOrchid1"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearlayout_show_download_status_image"
android:layout_weight="1"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_show_download_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/download_init"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/linearlayout_show_progressbar_progress"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp">
<ProgressBar
android:id="@+id/pb_show_show_progressbar_progress"
android:layout_width="match_parent"
android:layout_height="10dp"
android:progress="0"
android:background="@color/colorAzure2"
style="@style/MyProgressBar" />
</LinearLayout>
<RelativeLayout
android:id="@+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp">
<LinearLayout
android:id="@+id/linearlayout_button_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<LinearLayout
android:id="@+id/linearlayout_button_area_1"
android:layout_weight="1"
android:orientation="vertical"
android:background="@color/colorHoneydew1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_start"
android:background="@color/colorHoneydew1"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/download_btn_start"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearlayout_button_area_2"
android:layout_weight="1"
android:background="@color/colorThistle1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_pause"
android:background="@color/colorThistle1"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/download_btn_pause"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearlayout_button_area_3"
android:layout_weight="1"
android:background="@color/colorHoneydew1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_goon"
android:background="@color/colorHoneydew1"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/download_btn_goon"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearlayout_button_area_4"
android:layout_weight="1"
android:background="@color/colorThistle1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_stop"
android:background="@color/colorThistle1"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/download_btn_stop"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
- 定义Interface
MainContract代码如下:
package com.example.downloadmvpdemo;
import android.view.View;
public interface MainContract {
/**
* @MainView 用于MainActivity中
* */
interface MainView {
void onTextViewShowProgress(int progress); // TextView 显示下载的进度(范围:0% - 100%)
void onImageViewShowDownloadStatus(int status); // ImageView 显示下载状态图片(四种显示状态:初始化、下载中、下载停止、下载结束)
void onProgressBarStep(int step); // ProgressBar 下载进度显示
void onDownloadFinished(); // 下载结束通知
}
/**
* @DownloadUtil 用于DownloadUtilImpl中
* */
interface DownloadUtil {
/**
* @onDownloadListener 用于PersenterImpl中
* */
interface onDownloadListener { // 下载监听
void onDownloadStart(); // 开始下载
void onDownloadPause(int progress); // 暂停下载
void onDownloadFinish(); // 下载结束
void onDownloadProgress(int progress); // 下载结束
}
void startDownload(onDownloadListener downloadListener); // 开始下载
void pauseDownload(onDownloadListener downloadListener); // 暂停下载
void stopDownload(onDownloadListener downloadListener); // 停止下载
}
/**
* @Persenter 用于PersenterImpl中
* */
interface Persenter {
void onButtonClick(View view); // 按键点击事件
}
}
- 定义StatusMsg类
StatusMsg.java文件代码如下:
package com.example.downloadmvpdemo;
public class StatusMsg {
public static final int IMAGE_DOWNLOAD_STATUS_INIT = 0X00; // Image 初始化
public static final int IMAGE_DOWNLOAD_STATUS_START = 0X01; // Image 开始
public static final int IMAGE_DOWNLOAD_STATUS_STOP = 0X02; // Image 停止
public static final int IMAGE_DOWNLOAD_STATUS_FINISH = 0X03; // Image 结束
public static final int UPDATE_TEXTVIEW_DOWNLOAD_PROGRESS = 0X04; // TextView 显示更新
public static final int UPDATE_IMAGEVIEW_DOWNLOAD_STATUS = 0X05; // ImageView 显示更新
public static final int UPDATE_PROGRESSBAR_DOWNLOAD_STEP = 0X06; // ProgressBar 显示更新
public static final int BUTTON_CLICK_START = 0X07; // Button 开始
public static final int BUTTON_CLICK_PAUSE = 0X08; // Button 暂停
public static final int BUTTON_CLICK_STOP = 0X09; // Button 停止
public static final int BUTTON_CLICK_INIT = 0X10; // Button 初始化
public static final int UI_INIT = 0X100; // UI 界面初始化
public static final int UI_BUTTON_INIT = 0X101; // UI Button 界面初始化
}
- 完善DownloadUtilImpl类
DownloadUtilImpl.java代码如下:
package com.example.downloadmvpdemo;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
public class DownloadUtilImpl implements MainContract.DownloadUtil {
// Log tag
private static final String LOG_TAG = "DownloadUtilImpl";
// Download Progress
private int mProgress = 0;
// Timer
private Timer mTimer = null;
private int TIMER_DELAY = 1000;
private int TIMER_PERIOD = 1000;
@Override
public void startDownload(final onDownloadListener downloadListener) {
downloadListener.onDownloadStart();
downloadListener.onDownloadProgress(mProgress);
if (mTimer == null) {
mTimer = new Timer();
}
mTimer.schedule(new TimerTask() {
@Override
public void run() {
if (mProgress < 100) {
mProgress += 10;
}
downloadListener.onDownloadProgress(mProgress);
if (mProgress == 100) {
downloadListener.onDownloadFinish();
mProgress = 0;
mTimer.cancel();
mTimer = null;
}
}
}, TIMER_DELAY, TIMER_PERIOD);
}
@Override
public void pauseDownload(onDownloadListener downloadListener) {
DEBUG("pauseDownload: " + mProgress);
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
downloadListener.onDownloadPause(mProgress);
}
@Override
public void stopDownload(onDownloadListener downloadListener) {
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
mProgress = 0;
DEBUG("stopDownload: " + mProgress);
}
private void DEBUG(String info) {
Log.d(LOG_TAG, info);
}
}
- 完善PersenterImpl类
PersenterImpl.java代码如下:
package com.example.downloadmvpdemo;
import android.util.Log;
import android.view.View;
import com.example.downloadmvpdemo.MainContract.DownloadUtil;
import com.example.downloadmvpdemo.MainContract.MainView;
public class PersenterImpl implements MainContract.Persenter, DownloadUtil.onDownloadListener {
// Log tag
private static final String LOG_TAG = "PersenterImpl";
// MainView
private MainView mainView = null;
// downloadUtil
private DownloadUtil downloadUtil = null;
// flag
private boolean isDownloading = false;
public PersenterImpl(MainView mainView, DownloadUtil downloadUtil) {
this.mainView = mainView;
this.downloadUtil = downloadUtil;
}
@Override
public void onDownloadStart() {
isDownloading = true;
mainView.onImageViewShowDownloadStatus(StatusMsg.IMAGE_DOWNLOAD_STATUS_START);
}
@Override
public void onDownloadPause(int progress) {
isDownloading = false;
mainView.onImageViewShowDownloadStatus(StatusMsg.IMAGE_DOWNLOAD_STATUS_STOP);
}
@Override
public void onDownloadFinish() {
isDownloading = false;
mainView.onImageViewShowDownloadStatus(StatusMsg.IMAGE_DOWNLOAD_STATUS_FINISH);
mainView.onDownloadFinished();
}
@Override
public void onDownloadProgress(int progress) {
mainView.onTextViewShowProgress(progress);
mainView.onProgressBarStep(progress);
}
@Override
public void onButtonClick(View view) {
switch (view.getId()) {
case R.id.btn_start:
case R.id.btn_goon:
DEBUG("Start");
if (!isDownloading) {
DEBUG("Start enter");
isDownloading = true;
downloadUtil.startDownload(this);
}
break;
case R.id.btn_pause:
DEBUG("Pause");
if (isDownloading) {
DEBUG("Pause enter");
isDownloading = false;
downloadUtil.pauseDownload(this);
}
break;
case R.id.btn_stop:
DEBUG("Stop");
downloadUtil.stopDownload(this);
isDownloading = false;
break;
default:
break;
}
}
private void DEBUG(String info) {
Log.d(LOG_TAG, info);
}
}
- 完善MainActivity
MainActivity.java代码如下:
package com.example.downloadmvpdemo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity implements MainContract.MainView {
// Log tag
private static final String LOG_TAG = "MainActivity";
// TextView
private TextView mTextViewDownloadProgress = null;
// ImageView
private ImageView mImageViewDownloadStatus = null;
// ProgressBar
private ProgressBar mProgressBarDownloadStep = null;
// Button
private Button mButtonStart = null;
private Button mButtonStop = null;
private Button mButtonGoon = null;
private Button mButtonPause = null;
// UIHandler
private UIHandler mUIHandler = new UIHandler();
//
private MainContract.Persenter persenter = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DEBUG("onCreate.");
initView();
persenter = new PersenterImpl(this, new DownloadUtilImpl());
}
private void initView() {
DEBUG("initView.");
mTextViewDownloadProgress = findViewById(R.id.tv_show_download_progress);
mImageViewDownloadStatus = findViewById(R.id.iv_show_download_status);
mProgressBarDownloadStep = findViewById(R.id.pb_show_show_progressbar_progress);
mButtonStart = findViewById(R.id.btn_start);
mButtonStop = findViewById(R.id.btn_stop);
mButtonGoon = findViewById(R.id.btn_goon);
mButtonPause = findViewById(R.id.btn_pause);
mButtonStart.setOnClickListener(new ButtonListener());
mButtonStop.setOnClickListener(new ButtonListener());
mButtonGoon.setOnClickListener(new ButtonListener());
mButtonPause.setOnClickListener(new ButtonListener());
mButtonPause.setEnabled(false);
mButtonGoon.setEnabled(false);
}
private void updateTextView(int progress) {
mTextViewDownloadProgress.setText(progress + "%");
}
private void updateImageView(int status) {
switch (status) {
case StatusMsg.IMAGE_DOWNLOAD_STATUS_INIT:
mImageViewDownloadStatus.setImageDrawable(getDrawable(R.mipmap.download_init));
break;
case StatusMsg.IMAGE_DOWNLOAD_STATUS_START:
mImageViewDownloadStatus.setImageDrawable(getDrawable(R.mipmap.download_ing));
break;
case StatusMsg.IMAGE_DOWNLOAD_STATUS_STOP:
mImageViewDownloadStatus.setImageDrawable(getDrawable(R.mipmap.download_pause));
break;
case StatusMsg.IMAGE_DOWNLOAD_STATUS_FINISH:
mImageViewDownloadStatus.setImageDrawable(getDrawable(R.mipmap.download_success));
break;
default:
break;
}
}
private void uiInit() {
mTextViewDownloadProgress.setText(R.string.download_progress);
mImageViewDownloadStatus.setImageDrawable(getDrawable(R.mipmap.download_init));
mProgressBarDownloadStep.setProgress(0);
}
private void uiButtonInit() {
mButtonStart.setEnabled(true);
mButtonPause.setEnabled(false);
mButtonGoon.setEnabled(false);
}
private void updateProgressBar(int step) {
mProgressBarDownloadStep.setProgress(step);
}
@Override
public void onTextViewShowProgress(int progress) {
Message message = new Message();
message.what = StatusMsg.UPDATE_TEXTVIEW_DOWNLOAD_PROGRESS;
message.arg1 = progress;
mUIHandler.sendMessage(message);
}
@Override
public void onImageViewShowDownloadStatus(int status) {
Message message = new Message();
message.what = StatusMsg.UPDATE_IMAGEVIEW_DOWNLOAD_STATUS;
message.arg1 = status;
mUIHandler.sendMessage(message);
}
@Override
public void onProgressBarStep(int step) {
Message message = new Message();
message.what = StatusMsg.UPDATE_PROGRESSBAR_DOWNLOAD_STEP;
message.arg1 = step;
mUIHandler.sendMessage(message);
}
@Override
public void onDownloadFinished() {
Message message = new Message();
message.what = StatusMsg.UI_BUTTON_INIT;
mUIHandler.sendMessage(message);
}
class UIHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case StatusMsg.UI_INIT:
uiInit();
uiButtonInit();
break;
case StatusMsg.UI_BUTTON_INIT:
uiButtonInit();
break;
case StatusMsg.UPDATE_TEXTVIEW_DOWNLOAD_PROGRESS:
updateTextView(msg.arg1);
break;
case StatusMsg.UPDATE_IMAGEVIEW_DOWNLOAD_STATUS:
updateImageView(msg.arg1);
break;
case StatusMsg.UPDATE_PROGRESSBAR_DOWNLOAD_STEP:
updateProgressBar(msg.arg1);
break;
default:
break;
}
}
}
class ButtonListener implements View.OnClickListener {
@Override
public void onClick(View view) {
persenter.onButtonClick(view);
switch (view.getId()) {
case R.id.btn_start:
mButtonStart.setEnabled(false);
mButtonPause.setEnabled(true);
mButtonGoon.setEnabled(false);
break;
case R.id.btn_pause:
mButtonPause.setEnabled(false);
mButtonStart.setEnabled(false);
mButtonGoon.setEnabled(true);
break;
case R.id.btn_goon:
mButtonPause.setEnabled(true);
mButtonStart.setEnabled(false);
mButtonGoon.setEnabled(false);
break;
case R.id.btn_stop:
mButtonStart.setEnabled(true);
mButtonPause.setEnabled(false);
mButtonGoon.setEnabled(false);
Message message = new Message();
message.what = StatusMsg.UI_INIT;
mUIHandler.sendMessage(message);
break;
default:
break;
}
}
}
private void DEBUG(String info) {
Log.d(LOG_TAG, info);
}
}
六、总结
- 由上面Demo代码可见,View只负责UI界面更新,Model只负责数据部分,Persenter作为View和Model的桥梁,在中间进行数据通讯。
- MVP代码的本质就是CallBack的使用,详见另一篇文章:Java回调方法(CallBack)。