Android 架构模式之 MVP

news/2024/10/19 7:08:11/

目录

  • 架构设计的目的
  • MVP 的理解
  • 代码
    • Model
    • View
    • Presenter
  • Android 中 MVP 的问题
  • 试吃个小李子
    • Model
    • View
    • Presenter

大家好!

作为 Android 程序猿,你有研究过 MVP 架构吗?在开始接触 Android 那一刻起,我们就开始接触 MVC 架构,可谓是用的不亦乐乎。可为什么又出现了 MVP 呢?都说它比 MVC 好,到底又好在哪里呢?

架构设计的目的

通过设计使程序模块化,模块内 高内聚、模块间 低耦合,提高开发效率,便于复用及后续维护。

MVP__11">对 MVP 的理解

<a class=MVP 架构图,箭头代表事件流向" />

上图是 MVP架构图,我们都知道,MVP架构中 M 代表 Model(模型)、V 代表 View(视图)、P 代表 Presenter(主持人/控制器)。它们的职责分别是:

  1. View 负责接收用户的输入事件,然后将事件传递给 Presenter;
  2. Presenter 收到事件后,会进行业务处理,通知 Model 获取数据;
  3. Model 根据获取数据方式通过不同渠道去取数据,拿到数据后返回给 Presenter;
  4. Presenter 进行后续处理,或者通知 View 更新 UI。

相比 MVC 架构MVP 架构看上去就清晰了很多:事件由 View 流向 Presenter 流向 Model,然后再由 Model 流回 Presenter 流回 View。在 MVP 架构中,Activity 就全心的处理着和 View 相关的事情,Model 负责向下分发数据请求,替 Presenter 分担了很大一部分负担,这里特意新增了一个 CacheRepository 来体现提取 Model 层的用意,这样就可以在 Model 层进行不同渠道的分发,既体现了单一职责原则,又很好的提高了代码的可读性。所以看上去是多了一层 Model 层,可实际上作用还是很大的。

代码

Model

IModel.java

public interface IModel {}

BaseModel.java

public abstract class BaseModel implements IModel {}

View

public interface IView {void showErr(String errMsg);
}

BaseActivity.java

public abstract class BaseActivity<P extends IPresenter> extends AppCompatActivityimplements IView {protected P mPresenter;public BaseActivity() {this.mPresenter = createPresenter();mPresenter.attachView(this);}public abstract P createPresenter();@Overrideprotected void onDestroy() {super.onDestroy();// Activity 销毁时,需要调用 detachView,防止内存泄漏if (mPresenter != null) {mPresenter.detachView();mPresenter.onDestroy();mPresenter = null;}}@Overridepublic void showErr(String errMsg) {Toast.makeText(this, errMsg, Toast.LENGTH_SHORT);}
}

Presenter

IPresenter.java

public interface IPresenter<V extends IView> {void attachView(V view);void detachView();void onDestroy();
}

BasePresenter.java

public abstract class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V> {protected WeakReference<V> mView;protected M mModel;public BasePresenter() {this.mModel = createModel();}protected abstract M createModel();@Overridepublic void attachView(V view) {mView = new WeakReference<>(view);}@Overridepublic void detachView() {if (mView.get() != null) {mView.clear();}}@Overridepublic void onDestroy() {if (mModel != null) {mModel = null;}}
}

上述代码中可以看到,Presenter 中持有 View 引用,想象一种情况,Activity 发起一个网络请求/耗时操作,Presenter 收到需求后就分发去处理需求并等待结果了,但是还没等处理结束,Activity 就执行关闭操作了,此时 Presenter 还持有着 Activity 的强引用,导致 Activity 无法被及时回收掉,这便导致了大名鼎鼎的内存泄漏了;上述代码中通过 WeakReference 持有 View 引用,这样可以有效解决内存泄漏问题,并且在涉及到 Model/View/Presenter 的引用调用的地方,都进行了非空判断,需要规避空指针的风险出现。

MVP__132">Android 中 MVP 的问题

不幸的是,MVP 中 Presenter 的职责就是 MVC 中 Controller 负责的内容,只不过在 Android 中 Controller 在全职作 控制器 的同时,还需要兼职一部分 View 的职责,而 Presenter 就只是全职作 控制器。所以 Presenter 同样存在 Controller 存在的问题,随着业务的增多,Presenter 会变得越拉越 臃肿/复杂,以及 很糟糕的代码可读性

另外,由于 MVP 模式依赖于接口,所以在新增一个业务需求时,会 爆炸式增长文件和函数,这个也很让人头疼。

其次,由于 View 会只有 Presenter 引用,Presenter 持有 View 和 Model 的引用,如果处理不当,也会存在 空指针 的风险。

最后,Presenter 会持有 View 的引用,这样就埋下了 内存泄漏 的种子,如果处理不好,问题还是蛮大的。所以就有了后来的MVVM。

试吃个小李子

点击按钮,请求 wanandroid 网站的 banner 接口数据,请求成功后更新到UI上显示接口数据

代码结构

MVP 架构的 Demo 是从 MVC 架构那套代码变更过来的,添加了很多文件,主要部分 涉及上图中展开的这几个文件,仔细看上图蓝框中的内容会发现,新增一个业务 Activity,共需要新增 6 个文件,其中 3 个是接口约束类、3 个是具体实现类,这也体现了上面说的文件/函数暴增的问题。

Model

请求接口
缓存数据

IMainModel.java

public interface IMainModel extends IModel {/*** 请求 banner 数据** @param callback*/void getNetworkBanner(ResponseCallback<List<Banner>> callback);/*** 读取 banner 本地数据** @return*/List<Banner> getLocalBanner();/*** 持久化存储 banner 数据** @param banners*/void saveBanner(List<Banner> banners);/*** 清空本地数据*/void clearLocalData();
}

MainModel.java

public class MainModel extends BaseModel implements IMainModel {@Overridepublic void getNetworkBanner(ResponseCallback<List<Banner>> callback) {// 收到需求,请求接口数据NetworkRepository.getInstance().requestBanners(callback);}@Overridepublic List<Banner> getLocalBanner() {// 收到需求,读取本地数据return CacheRepository.getInstance().getBanners();}@Overridepublic void saveBanner(List<Banner> banners) {// 收到需求,持久化存储 banner 数据CacheRepository.getInstance().saveBanners(banners);}@Overridepublic void clearLocalData() {// 收到需求,清空本地缓存数据CacheRepository.getInstance().clearLocalData();}
}

View

Button1,点击请求接口数据
Button2,获取本读缓存数据
Button3,清空本地缓存数据
TextView,用于回显数据

activity_main.xml

<?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"android:gravity="center"android:orientation="vertical"tools:context=".main.MainActivity"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="getNetworkInfo"android:text="@string/get_network_info" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="getLocalInfo"android:text="@string/get_local_info" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="clearLocalInfo"android:text="@string/clear_local_info" /><TextViewandroid:id="@+id/tv_banner_info"android:layout_width="wrap_content"android:layout_height="wrap_content" /></LinearLayout>

IMainView.java

public interface IMainView extends IView {/*** 更新 banner 数据** @param banners*/void updateBanner(List<Banner> banners, String from);
}

MainActivity.java

public class MainActivity extends BaseActivity<IMainPresenter> implements IMainView {private TextView mBannerInfoTv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mBannerInfoTv = (TextView) findViewById(R.id.tv_banner_info);}@Overridepublic IMainPresenter createPresenter() {return new MainPresenter();}/*** 按钮点击事件** @param view*/public void getNetworkInfo(View view) {// 收到点击事件,交给 presenter 进行业务处理if (mPresenter != null) {mPresenter.getNetworkBanner();}}/*** 按钮点击事件** @param view*/public void getLocalInfo(View view) {// 收到点击事件,交给 presenter 进行业务处理if (mPresenter != null) {mPresenter.getLocalBanner();}}/*** 按钮点击事件** @param view*/public void clearLocalInfo(View view) {// 收到点击事件,交给 presenter 进行业务处理if (mPresenter != null) {mPresenter.clearLocalData();}}@Overridepublic void updateBanner(List<Banner> banners, String from) {// 收到更新 ui 事件,更新 uishowBannerInfo(banners, from);}/*** 更新UI** @param banners*/private void showBannerInfo(List<Banner> banners, String from) {StringBuilder sb = new StringBuilder();if (banners.size() > 0) {sb.append("wanandroid 官网\nhttps://www.wanandroid.com\n\n").append("data from ").append(from).append(":\n");for (Banner item : banners) {Log.e("banner", item.toString());sb.append(item.getTitle()).append('\n');}}mBannerInfoTv.setText(sb.toString());}
}

Presenter

业务处理

IMainPresenter.java

public interface IMainPresenter<V extends IView> extends IPresenter<V> {/*** 获取 banner 网络数据*/void getNetworkBanner();/*** 获取 banner 本地数据*/void getLocalBanner();/*** 存储 banner 数据** @param banners*/void saveBanner(List<Banner> banners);/*** 清空本地数据*/void clearLocalData();
}

MainPresenter.java

public class MainPresenter extends BasePresenter<IMainView, IMainModel>implements IMainPresenter<IMainView> {@Overrideprotected IMainModel createModel() {return new MainModel();}@Overridepublic void getNetworkBanner() {if (mModel == null) {return;}// 收到新需求,分发给 model 处理mModel.getNetworkBanner(new ResponseCallback<List<Banner>>() {@Overridepublic void onSuccess(List<Banner> banners) {// 数据缓存saveBanner(banners);// 通知更新UInotifyUpdateBanner(banners, CommonConstant.FROM_NETWORK);}@Overridepublic void onFail(String msg) {IMainView view = mView.get();if (view != null) {view.showErr(msg);}}});}@Overridepublic void getLocalBanner() {if (mModel == null) {return;}// 收到新需求,分发给 model 处理List<Banner> banners = mModel.getLocalBanner();// 通知更新UInotifyUpdateBanner(banners, CommonConstant.FROM_LOCAL);}@Overridepublic void saveBanner(List<Banner> banners) {// 收到新需求,分发给 model 处理if (mModel != null) {mModel.saveBanner(banners);}}@Overridepublic void clearLocalData() {// 收到新需求,分发给 model 处理if (mModel != null) {mModel.clearLocalData();}}/*** 通知更新UI** @param banners*/private void notifyUpdateBanner(List<Banner> banners, String from) {// 获取到数据,通知更新 uiif (mView != null) {IMainView view = mView.get();if (view != null) {view.updateBanner(banners, from);}}}
}

附上源码链接

致谢:
感谢 wanandroid 提供的开放API

参考:
一个小例子彻底搞懂 MVP

写在最后:
很荣幸成为一名 Android 程序猿,虽然不是一名合格的猿。一路走来磕磕绊绊,借此感谢帮助过我的人,感谢指点、感恩遇见!


http://www.ppmy.cn/news/1511714.html

相关文章

STM32智能温室管理系统开发指南:利用MQTT/HTTP、Node.js、MySQL及React进行数据可视化(代码示例)

一、项目概述 随着农业现代化的推进&#xff0c;智能温室管理系统应运而生。该项目旨在开发一套基于STM32微控制器的智能温室管理可视化系统&#xff0c;通过实时监测温室环境参数&#xff08;如温度、湿度、光照度等&#xff09;&#xff0c;并对其进行智能控制&#xff0c;提…

我怎么会这么依赖 GUI?

AWS CLI、.NET 和 Lambda 函数 欢迎来到雲闪世界。在 Windows 上使用 Visual Studio 和 AWS Explorer 绝对会让你变得懒惰。我的意思是&#xff0c;能够通过右键单击项目来构建和部署 Lambda 函数之类的东西真是太棒了&#xff0c;但有时最好了解幕后发生了什么。 尽管如此&am…

爱心动画代码HTML5

这段代码是一个HTML5 Canvas动画&#xff0c;它通过JavaScript创建了一个动态的爱心效果。页面初始化时&#xff0c;首先定义了一些基本设置&#xff0c;如粒子数量、持续时间、速度等。然后&#xff0c;定义了Point和Particle类&#xff0c;用于处理粒子的位置和运动。接着&am…

Linux安装MQTT 服务器(图文教程)

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的消息传输协议&#xff0c;专为低带宽和不稳定的网络环境设计&#xff0c;非常适合物联网&#xff08;IoT&#xff09;应用。 官网地址&#xff1a;https://www.emqx.com/ 一、版本选择 根据自己…

开源的数据库增量订阅和消费的中间件——Cancl

目录 工作原理 MySQL主备复制原理 Canal 工作原理 主要功能和特点 应用场景 实验准备 安装JDK11 下载MySQL8.0 配置canal.admin 配置canal-deployer 测试数据读取 新增一台主机用做被同步的目标机器测试 官方地址&#xff1a;https://github.com/alibaba/canal?ta…

使用 C++ 实现一个简单的数据库连接池

使用 C 实现一个简单的数据库连接池 在现代应用程序中&#xff0c;数据库连接的管理是一个重要的性能瓶颈。频繁地创建和销毁数据库连接会导致显著的性能下降。为了解决这个问题&#xff0c;连接池技术应运而生。本文将介绍如何使用 C 实现一个简单的数据库连接池&#xff0c;…

JAVA--多线程

Java中的多线程是指在同一个Java虚拟机&#xff08;JVM&#xff09;中并发执行多个线程的能力。线程是程序执行的最小单元&#xff0c;Java提供了丰富的API来创建和管理线程。以下是Java中实现多线程的一些关键概念和方法&#xff1a; Thread 类&#xff1a;Java提供了Thread类…

jenkins配置+vue打包多环境切换

jenkins配置流水线过程 1.新建item 加入相关的参数就行了。 流水线脚本设置 后端脚本 node {stage checkoutsh"""#每次打包清空工作空间目录rm -rf $workspace/*cd $workspace#到工作空间下从远端svn服务端拉取代码svn co svn://10.1.19.21/repo/技术中台/低…