什么是MVP框架
做Android开发也有好几年时间了,最近接触了Android开发的MVP模式,MVP即Model、View、Presenter的缩写。如果有过一些项目开发经验的人,在项目功能越来越多,逻辑越来越复杂的时候,代码一定会写得越来越乱,乱到自己都很难看下去了(本人在做项目时深有体会)。因为Android以前的开发模式比较类似于MVC框架,XML布局为View层,数据实体为Model层,Activity为Controller层,当一个Activity中的逻辑比较多的时候,Activity中的代码过千行是非常容易达到的,而且很多逻辑和View交互的代码都在Activity中,这样的开发模式,导致View和Controller耦合得非常紧,不仅代码写着看着乱,而且也非常不便于后期的维护和扩展。MVP开发模式的推出,不仅非常有效的解决了View和Controller耦合的问题,而且Activity被彻底当作View来看待了,模型和视图的交互是Presenter和View通过接口来完成,代码层次也分明了,后期的扩展和修改也很方便了。
举个栗子
光说无用,举个例子来说明最实在最容易理解了,下面的一个Demo是我在学习MVP框架时写的,希望自己写得没错(如果有错希望大神指出)
这个项目名为Test MVP,主要功能是实现下载文件,页面上有一个下载按钮,还有一个进度条,下图是项目的目录结构:
下面上布局文件的代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.test.testmvp.MainActivity">
<Button
android:id="@+id/download_btn"
android:onClick="handleDownloadBtnClick"
android:text="CLICK TO DOWNLOAD"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ProgressBar
android:layout_below="@+id/download_btn"
android:layout_marginTop="20dp"
android:visibility="gone"
android:id="@+id/progress_bar"
android:max="100"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
布局文件很简单,就没必要多说明了。
在view包中,主要是一个View的接口文件IDownloadView.java,代码如下:
package com.test.testmvp.view;
/**
* Created by yubo on 2017/3/26.
* view层的抽象接口,需要Activity去实现该接口(所以Activity当作view看待)
*/
public interface IDownloadView {
void initViews();
void showProgressBar();
void hideProgressBar();
void updateProgressBar(int progress);
void showToast(String msg);
}
这个View的接口文件,定义了一系列有关View的操作的方法,由于MVP模式中将Activity完全视为View来看待,所以我们的Activity需要实现这个IDownloadView接口,关于Activity的具体实现,放在后面贴代码。
下面说presenter包,由于本Demo中主要就是一个下载的逻辑,所以IDownloadPresenter接口中,主要就是一个下载的方法,代码如下:
package com.test.testmvp.presenter;
import com.test.testmvp.listener.OnDownloadListener;
import java.io.File;
/**
* Created by yubo on 2017/3/26.
*/
public interface IDownloadPresenter {
//下载文件的逻辑,由实现类去实现该方法,listener用于监听下载过程
void startDownload(String urlStr, File saveFile, OnDownloadListener listener);
}
下载的IDownloadPresenter接口的startDownload方法有三个参数,第一个是下载地址,第二个是下载的文件,第三个是一个下载的监听器,OnDownloadListener代码如下:
package com.test.testmvp.listener;
/**
* Created by yubo on 2017/3/26.
* 下载的监听器,处理下载的不同阶段
*/
public interface OnDownloadListener {
void onStartDownload();
void onProgressUpdate(int progress);
void onDownloadSuccess();
void onDownloadError(Exception e);
}
主要就是监听开始下载、下载进度、下载完成、下载出错这4个过程。有了IDownloadPresenter接口,还需要一个实现类DownloadPresenterImpl,这个实现类的代码如下:
package com.test.testmvp.presenter;
import android.app.Activity;
import android.content.Context;
import com.test.testmvp.listener.OnDownloadListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
/**
* Created by yubo on 2017/3/26.
*/
public class DownloadPresenterImpl implements IDownloadPresenter {
private Context mContext;
public DownloadPresenterImpl(Context context) {
this.mContext = context;
}
@Override
public void startDownload(final String urlStr, final File saveFile, final OnDownloadListener listener) {
if (listener == null) {
throw new IllegalArgumentException("OnDownloadListener should not be null!");
}
listener.onStartDownload();
new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
inputStream = conn.getInputStream();
fileOutputStream = new FileOutputStream(saveFile);
int totalSize = inputStream.available();
int hasDownload = 0;
int hasRead = 0;
byte[] buf = new byte[512];
while ((hasRead = inputStream.read(buf)) > 0) {
fileOutputStream.write(buf, 0, hasRead);
hasDownload += hasRead;
final int progress = (int) (hasDownload * 100.0f / totalSize);
listener.onProgressUpdate(progress);
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onProgressUpdate(progress);
}
});
}
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onDownloadSuccess();
}
});
} catch (final MalformedURLException e) {
e.printStackTrace();
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onDownloadError(e);
}
});
} catch (final IOException e) {
e.printStackTrace();
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onDownloadError(e);
}
});
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}
逻辑和视图的解耦,就是靠的Presenter完成的(要是用以前的类似MVC的开发模式,那Presenter中的代码就全部写到Activity中去了)。下面还有一个DownloadFileModel类,就是对下载的文件做了一个简单的封装,代码如下:
package com.test.testmvp.model;
import java.io.File;
import java.io.IOException;
/**
* Created by yubo on 2017/3/26.
*/
public class DownloadFileModel {
private String fileName;
private String fileSavePath;
private File file;
public DownloadFileModel(String fileName, String fileSavePath) {
this.fileName = fileName;
this.fileSavePath = fileSavePath;
this.file = new File(fileSavePath + File.separator + fileName);
if (this.file.exists()) {
this.file.delete();
}
try {
this.file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
public File getFile() {
return this.file;
}
public String getFilePath() {
return this.file.getAbsolutePath();
}
}
下面上MainActivity的代码:
package com.test.testmvp;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.test.testmvp.listener.OnDownloadListener;
import com.test.testmvp.model.DownloadFileModel;
import com.test.testmvp.presenter.DownloadPresenterImpl;
import com.test.testmvp.view.IDownloadView;
public class MainActivity extends AppCompatActivity implements IDownloadView {
//文件下载地址,可能已失效
private static final String DOWNLOAD_FILE_URL = "http://do.xiazaiba.com/route.php?ct=stat&ac=stat&g=aHR0cDovL2FwcHMud2FuZG91amlhLmNvbS9yZWRpcmVjdD9zaWduYXR1cmU9YzkxNGQ2OSZ1cmw9aHR0cCUzQSUyRiUyRmRvd25sb2FkLmVvZW1hcmtldC5jb20lMkZhcHAlM0ZjaGFubmVsX2lkJTNEMTAwJTI2Y2xpZW50X2lkJTI2aWQlM0QyNDQwMDMmcG49Y29tLnVzb2Z0LmFwcC51aSZtZDU9OTI4MWIxZjU1YmM1YzExNmYyMThmZjYwY2FhZWEwMmImYXBraWQ9MTQyODY1ODgmdmM9MTEmc2l6ZT0zNDE0MTEw";
//下载的文件名
private static final String DOWNLOAD_FILE_NAME = "test.apk";
//下载的文件存放路径
private static final String DOWNLOAD_FILE_SAVE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
//进度条控件
private ProgressBar mProgressBar;
//处理下载的Presenter
private DownloadPresenterImpl downloadPresenter;
//下载的文件Model
private DownloadFileModel downloadFileModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
downloadFileModel = new DownloadFileModel(DOWNLOAD_FILE_NAME, DOWNLOAD_FILE_SAVE_PATH);
downloadPresenter = new DownloadPresenterImpl(this);
}
@Override
public void initViews() {
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
}
//处理下载按钮点击事件
public void handleDownloadBtnClick(View view) {
downloadPresenter.startDownload(DOWNLOAD_FILE_URL, downloadFileModel.getFile(), new OnDownloadListener() {
@Override
public void onStartDownload() {
showProgressBar();
}
@Override
public void onProgressUpdate(int progress) {
updateProgressBar(progress);
}
@Override
public void onDownloadSuccess() {
hideProgressBar();
showToast("download success, path: " + downloadFileModel.getFilePath());
}
@Override
public void onDownloadError(Exception e) {
hideProgressBar();
showToast(e.getMessage());
}
});
}
@Override
public void showProgressBar() {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgressBar() {
mProgressBar.setVisibility(View.GONE);
}
@Override
public void updateProgressBar(int progress) {
mProgressBar.setProgress(progress);
}
@Override
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
可以看到Activity实现了IDownloadView接口,并实现了接口里的方法,Activity持有了一个Presenter的引用和一个Model的引用,Activity作为View来看待,当按钮有点击事件时,会调用Presenter的startDownload方法来处理业务逻辑,而视图的更新,是在startDownload方法的回调接口中,调用IDownloadView接口的方法去完成的,不会存在逻辑和视图的直接交互,这样也就达到了低耦合的目的。 OK了,整篇学习笔记就到这里了,希望自己的理解没有错误,也是参考了博客大神的博文来学习的