Android MVP架构

8 篇文章 1 订阅
8 篇文章 1 订阅

一、架构概述

对于MVP (Model View Presenter)架构是从著名的MVC(Model View Controller)架构演变而来的。而对于Android应用的开发中本身可视为一种MVC架构。通常在开发中将XML文件视为MVC中的View角色,而将Activity则视为MVC中的Controller角色。不过更多情况下在实际应用开发中Activity不能够完全充当Controller,而是ControllerView的合体。于是Activity既要负责视图的显示,又要负责对业务逻辑的处理。这样在Activity中代码达到上千行,甚至几千行都不足为其,同时这样的Activity也显得臃肿不堪。所以对于MVC架构并不很合适运用于Android的开发中。

二、架构简介

对于一个应用而言我们需要对它抽象出各个层面,而在MVP架构中它将UI界面和数据进行隔离,所以我们的应用也就分为三个层次。

  1. View:对于View层也是视图层,在View层中只负责对数据的展示,提供友好的界面与用户进行交互。在Android开发中通常将Activity或者Fragment作为View层。
  2. Model: 对于Model层也是数据层。它区别于MVC架构中的Model,在这里不仅仅只是数据模型。在MVP架构中Model它负责对数据的存取操作,例如对数据库的读写,网络的数据的请求等。
  3. Presenter: 对于Presenter层他是连接View层与Model层的桥梁并对业务逻辑进行处理。在MVP架构中ModelView无法直接进行交互。所以在Presenter层它会从Model层获得所需要的数据,进行一些适当的处理后交由View层进行显示。这样通过Presenter将View与Model进行隔离,使得ViewModel之间不存在耦合,同时也将业务逻辑从View中抽离。

下面通过MVP结构图来看一下MVP中各个层次之间的关系。
MVP架构
MVP架构中将这三层分别抽象到各自的接口当中。通过接口将层次之间进行隔离,而PresenterViewModel的相互依赖也是依赖于各自的接口。这点符合了接口隔离原则,也正是面向接口编程。在Presenter层中包含了一个View接口,并且依赖于Model接口,从而将Model层与View层联系在一起。而对于View层会持有一个Presenter成员变量并且只保留对Presenter接口的调用,具体业务逻辑全部交由Presenter接口实现类中处理。

三、Demo演示

实现一个模拟下载过程的操作,Demo下载地址:Gitee
动画演示过程如下:
MVP Demo
通过点击底部的按键,来实现下载进度、下载进度展示图片、进图条以及各个按键的变化。

四、Demo分析

运用MVP架构思想,View层就是MainActivity,负责UI的更新;Model层是DownloadUtil下载模块,负责文件的模拟下载;Persenter层就是代码中的PersenterImpl文件,负责View层及Model数据交互。另外StatusMsg为用户自定义的消息码。
AS工程目录结构:
AS工程目录结构

五、Demo代码实现

  1. 首先编写布局文件
    效果图如下:
    布局文件效果图
    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>
  1. 定义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); // 按键点击事件
    }
}

  1. 定义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 界面初始化
}

  1. 完善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);
    }
}

  1. 完善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);
    }
}

  1. 完善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);
    }
}

六、总结

  1. 由上面Demo代码可见,View只负责UI界面更新,Model只负责数据部分,Persenter作为View和Model的桥梁,在中间进行数据通讯。
  2. MVP代码的本质就是CallBack的使用,详见另一篇文章:Java回调方法(CallBack)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值