Android MVP框架学习

什么是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了,整篇学习笔记就到这里了,希望自己的理解没有错误,也是参考了博客大神的博文来学习的,这里贴出博客大神的文章:
http://blog.csdn.net/lmj623565791/article/details/46596109

本篇学习笔记中的代码已上传到github,点击这里查看代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yubo_725

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值