【Android】MVC与MVP的区别,MVP网络请求实践

一、MVC模式

目录

  • 一、MVC模式
  • 二、MVP模式
    • 1、MVP的简单应用
      • 1.1 导入相关依赖包并设置权限
      • 1.2 实现Model
      • 1.2 实现Presenter
      • 1.3 实现View
      • 1.4分析项目结构和绑定过程
      • 1.5效果展示
    • 2、MVP结合RxJava

一、MVC模式

MVC(Model(模型)——View(视图)——Controller(控制器))

Android中MVC的角色定义如下:

  • 模型(Model)层:针对业务模型建立的数据结构和相关类
  • 视图(View)层:一般作为UI的展示XML文件
  • 控制(Controller)层:通常在Activity和Fragment中,或者在由他们控制的业务类中

简单来说MVC就是通过Controller层操作Model层数据,并且返回给VIew展示。

[外链图片转存中…(img-oi8sABEO-1702569842215)]

MVC的缺点:

  • Activity不是一个标准的Controller,它主要是加载应用布局、初始化界面、并且收发用户操作的请求而做出响应
  • VIew层和Model层互相耦合,不易于开发维护

二、MVP模式

MVP(Model——View——presenter)它是MVC的演化版本,MPV角色定义如下:

  • Model:主要负责数据的存取功能
  • View:负责处理用户事件和视图展示,在Android中它可能是Activity、Fragment或者某给View的控件。
  • Presenter:作为View和Model之间的桥梁,它从Model中检索数据返回给View。使得View和Model之间没有耦合

主要的控制逻辑在Presenter里实现,而且Presenter与具体的View是没有直接关联的,而是通过定义好的接口交互。这样使得View变更时Presenter不会受到影响。绝不允许View直接访问Model,这就是它和MVC的不同之处。

1、MVP的简单应用

1.1 导入相关依赖包并设置权限

导入需要的相关依赖:

implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'org.projectlombok:lombok:1.18.30'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.github.bumptech.glide:glide:4.16.0'

接下来设置网络请求权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

我们使用这个免费的Api获取一首网易热门歌曲

https://api.uomg.com/api/rand.music?sort=热歌榜&format=json

JSON数据结构如下所示:

{"code": 1,"data": {"name": "晚安","url": "http://music.163.com/song/media/outer/url?id=1359356908","picurl": "http://p1.music.126.net/8N1fsMRm2L5HyZccc6I3ew==/109951164007377169.jpg","artistsname": "颜人中"}
}

先看一下完整的项目目录结构:

[外链图片转存中…(img-pjbexowu-1702569842216)]

1.2 实现Model

创建一个musicData用于存储转化后的JSON数据:

@NoArgsConstructor
@Data
public class musicData {@SerializedName("data")private DataDTO data;@NoArgsConstructor@Datapublic static class DataDTO {@SerializedName("name")private String name;@SerializedName("url")private String url;@SerializedName("picurl")private String picurl;@SerializedName("artistsname")private String artistsname;public String getName() {return name;}public String getUrl() {return url;}public String getPicurl() {return picurl;}public String getArtistsname() {return artistsname;}}public DataDTO getData() {return data;}
}`

定义获取网络数据的接口类NetTask:

public interface NetTask<T> {void execute(T data, LoadTasksCallBack callBack);
}

这里有一个回调接口LoadTasksCallBack用于监听网络访问回调的各种状态:

public interface LoadTasksCallBack<T> {/*** 网络请求成功** @param data:数据仓库,存放解析后的数据*/void onSuccess(T data);/***网络请求失败*/void onFailed();
}

接下来我们编写NetTask的实现类以获取数据,如下所示:

public class MusicInfoTask implements NetTask<String> {private static MusicInfoTask INSTANCE = null;private static final String HOST = "https://api.uomg.com/api/rand.music?sort=%E7%83%AD%E6%AD%8C%E6%A6%9C&format=json";private LoadTasksCallBack loadTasksCallBack;public static MusicInfoTask getInstance(){if (INSTANCE == null){INSTANCE = new MusicInfoTask();}return INSTANCE;}@Overridepublic void execute(String data, final LoadTasksCallBack callBack) {//这里展示一下传入的data数据,HOSP是写死的,没有拼接data,可以自行实现使用data重新拼接。Log.e("TAG", "execute: "+data);sendOkHttpRequest(HOST,new okhttp3.Callback(){@Overridepublic void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {if (response.isSuccessful()){String responseBody = response.body().string();Gson gson = new Gson();musicData musicData = gson.fromJson(responseBody, musicData.class);callBack.onSuccess(musicData);}}@Overridepublic void onFailure(@NonNull Call call, @NonNull IOException e) {callBack.onFailed();}});}/*** 发送网络请求** @param address* @param callback*/public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(address).build();client.newCall(request).enqueue(callback);}
}

MusicInfoTask是一个单例模式,在execute方法中通过OKHttp来获取数据,同时在okhttp3.Callback中回调自定义的LoadTasksCallBack。

通过时序图理解execute方法是如何做的:

1.2 实现Presenter

我们首先定义一个契约接口MusicInfoContract用来存放具有相同业务的Presenter和View的接口,便于查找和维护。代码如下:

public interface MusicInfoContract {interface Presenter {void getMusiInfo(String ip);}/*** 视图需要的功能** @author lukecc0* @date 2023/12/13*/interface View extends BaseView<Presenter> {void setMusicData(musicData musicData);void showError();/*** 用于判断Fragment是否成功加入到Activity** @return {@link Boolean}*/Boolean isACtive();}
}

在这里看见Presenter接口定义了获取数据的方法,而View接口定义了与界面交互的方法。其中isActive方法用于判断Fragment是否加入到了Activity中。

另外,View接口基础于BaseView接口,BaseView接口如下所示:

public interface BaseView<T>{/*** 为视图绑定对应的presenter** @param presenter*/void setPresenter(T presenter);
}

BaseView接口的目的就是为View绑定对应的Presenter。它作为一个View的管理类,为每一个View实现绑定方法。

接着实现Presenter接口,如下所示:

public class MusicInfoPresenter implements MusicInfoContract.Presenter, LoadTasksCallBack<musicData> {private NetTask netTask;private MusicInfoContract.View addTaskView;/*** @param addTaskView 是我们传入的View用于实现Presenter绑定View,然后我们操作这个View实现Ui的变更* @param netTask   传入的Model用于实现Presenter绑定Model,通过这个Model获取数据*/public MusicInfoPresenter(MusicInfoContract.View addTaskView, NetTask netTask) {this.netTask = netTask;this.addTaskView = addTaskView;}@Overridepublic void onSuccess(musicData musicData) {if (addTaskView.isACtive()) {addTaskView.setMusicData(musicData);}}@Overridepublic void onFailed() {if (addTaskView.isACtive()) {addTaskView.showError();}}@Overridepublic void getMusiInfo(String ip) {//1、将自身回调,把自身传入到Model中,实现Model绑定PresenternetTask.execute(ip, this);}
}

在MusicInfoPresenter中含有NetTask和MusicInfoContract.View的实例,并且还实现了LoadTasksCallBack接口。

在注释1中MusicInfoPresenter将自身传递给NetTask的execute方法来获取数据。并回调给MusicInfoPresenter本身实现的onSuccess、onFailed两个方法,在这两个方法中通过addTaskView与View交互。我们看一下这个getMusiInfo方法的时序图理解它是怎么做的:

[外链图片转存中…(img-6HAbY7JS-1702569842216)]

这样我们就理解了,这里的Presenter作为一个中间代理,通过NetTask也就是Model来获取和保存数据,然后通过View更新页面。在这个过程中通过自定义接口使得Model和View没有任何交互。

1.3 实现View

在前面的契约接口MusicInfoContract中我们已经自定义了View接口,接下来我们使用MusicInfoFragment实现它:

public class MusicInfoFragment extends Fragment implements MusicInfoContract.View {private TextView textView;private ImageView imageView;private Button button;private MusicInfoContract.Presenter mPresenter;public static MusicInfoFragment newInstance() {return new MusicInfoFragment();}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View root = inflater.inflate(R.layout.music_fragment, container, false);textView = root.findViewById((int) R.id.textView);imageView = root.findViewById((int) R.id.image);button = root.findViewById((int) R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {mPresenter.getMusiInfo("热歌榜");}});return root;}@Overridepublic void setPresenter(MusicInfoContract.Presenter presenter) {mPresenter = presenter;}@Overridepublic void setMusicData(musicData musicData) {Log.e("TAG", "execute: " + musicData.getData().toString());if (musicData != null) {textView.setText(musicData.getData().getName());requireActivity().runOnUiThread(new Runnable() {@Overridepublic void run() {String originalUrl = musicData.getData().getPicurl();//将http协议换成httpsString modifiedUrl = originalUrl.replace("http://", "https://");Glide.with(requireActivity()).load(modifiedUrl).into(imageView);}});}}@Overridepublic void showError() {textView.setText("Error");}@Overridepublic Boolean isACtive() {//判断是否加入到Activityreturn isAdded();}
}

在上面注释1的部分通过实现setPresenter方法来实现注入MusicInfoPresenter,用于实现View绑定到Presenter。

在注释2中则调用MusicInfoPresenter的getMusiInfo方法来获取Ip地址的信息。另外Fragment实现了MusicInfoContract.View接口,用来接受MusicInfoPresenter的回调。

那么MusicInfoFragment是在哪里调用setPresenter实现注入MusicInfoPresenter?

其实在MainActivity中实现这个注入,我们看一下MainActivity做了什么事情:

public class MainActivity extends AppCompatActivity {private MusicInfoPresenter presenter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1、创建FragmentMusicInfoFragment fragment = (MusicInfoFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);if (fragment == null){fragment = MusicInfoFragment.newInstance();//2、将Fragment加入到Activity中ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),fragment,R.id.fragment);}MusicInfoTask task = MusicInfoTask.getInstance();//3、将View和Model注入Presenter中presenter = new MusicInfoPresenter(fragment,task);//4、将Presenter注入View中实现双向绑定fragment.setPresenter(presenter);}
}

这个例子中Activity不作为View层,而是作为View、Model、Presenter三层的纽带。

📌注意我们看注释3、4可以发现View和Presenter是双向绑定的

ActivityUtils的addFragmentToActivity负责提交事务,实现了绑定Fragment到Activity,我们看一下代码:

public class ActivityUtils {public static void addFragmentToActivity(FragmentManager fragmentManager,Fragment fragment,int frameId){FragmentTransaction transaction = fragmentManager.beginTransaction();transaction.add(frameId,fragment);transaction.commit();}
}

1.4分析项目结构和绑定过程

从图中可以看出,View和Model之间并没有什么联系;View和Presenter通过接口交互,并在Activity中互相注入。Model的NetTask在Activity中注入Presenter,并等待Presenter调用。

1.5效果展示

2、MVP结合RxJava

这个例子是在上个例子的基础上修改的,加入了RxJava3和Retrofit2实现网络请求。

首先导入需要的依赖:

implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'

定义网络访问接口,如下所示:

public interface MusicService {@GET("rand.music?format=json")Observable<musicData> getMusicData(@Query("sort") String sort);
}

getMusicData返回Observable类型是为了支持RxJava,然后我们修改NetTask接口:

public interface NetTask<T> {//    void execute(T data, LoadTasksCallBack callBack);Disposable execute(T data, LoadTasksCallBack callBack);
}

接下来修改NetTask接口的实现类MusicInfoTask,代码如下:

public class MusicInfoTask implements NetTask<String> {private static MusicInfoTask INSTANCE = null;private Retrofit retrofit;private static final String HOST = "https://api.uomg.com/api/";public MusicInfoTask() {createRetrofit();}public static MusicInfoTask getInstance(){if (INSTANCE == null){INSTANCE = new MusicInfoTask();}return INSTANCE;}/***  初始化Retrofit*/private void createRetrofit(){retrofit= new Retrofit.Builder().baseUrl(HOST).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava3CallAdapterFactory.create()).build();}@Overridepublic Disposable execute(String data, LoadTasksCallBack callBack) {MusicService service = retrofit.create(MusicService.class);Disposable disposable = service.getMusicData(data).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(new DisposableObserver<musicData>() {@Overridepublic void onNext(@NonNull musicData musicData) {// 收到数据时的操作callBack.onSuccess(musicData);}@Overridepublic void onError(@NonNull Throwable e) {// 发生错误时的操作callBack.onFailed();}@Overridepublic void onComplete() {// 完成时的操作}});return disposable;}}

主要变化就是使用RxJava和Retrofit代替OKHttp访问网络,在execute方法中返回Disposable。接下来我们实现取消网络请求功能。首先定义一个BasePresenter接口:

public interface BasePresenter {void subscribe();void unsubscribe();
}

然后改写Presenter接口,继承这个BasePresenter:

public interface MusicInfoContract {
//    interface Presenter {
//        void getMusiInfo(String ip);
//    }interface Presenter extends BasePresenter{void getMusiInfo(String ip);}...........
}

最后改写MusicInfoPresenter类:

public class MusicInfoPresenter implements MusicInfoContract.Presenter, LoadTasksCallBack<musicData> {private NetTask netTask;private MusicInfoContract.View addTaskView;private CompositeDisposable compositeDisposable;private Disposable disposable;/*** @param addTaskView 是我们传入的View用于实现Presenter绑定View,然后我们操作这个View实现Ui的变更* @param netTask   传入的Model用于实现Presenter绑定Model,通过这个Model获取数据*/public MusicInfoPresenter(MusicInfoContract.View addTaskView, NetTask netTask) {this.netTask = netTask;this.addTaskView = addTaskView;compositeDisposable = new CompositeDisposable();}@Overridepublic void onSuccess(musicData musicData) {if (addTaskView.isACtive()) {addTaskView.setMusicData(musicData);}}@Overridepublic void onFailed() {if (addTaskView.isACtive()) {addTaskView.showError();unsubscribe();}}@Overridepublic void getMusiInfo(String ip) {//1、将自身回调,把自身传入到Model中,实现Model绑定Presenterdisposable = netTask.execute(ip, this);subscribe();}@Overridepublic void subscribe() {if (disposable != null) {compositeDisposable.add(disposable);}}@Overridepublic void unsubscribe() {if (compositeDisposable != null && compositeDisposable.isDisposed()){compositeDisposable.dispose();}}
}

当注释1调用execute方法时返回一个disposable,然后调用subscribe方法传入compositeDisposable,用来管理这个网络请求,当我们需要取消这个网络请求时调用unsubscribe即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/225984.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式-状态(State)模式

目录 开发过程中的一些场景 状态模式的简单介绍 状态模式UML类图 类图讲解 适用场景 Java中的例子 案例讲解 什么是状态机 如何实现状态机 SpringBoot状态自动机 优点 缺点 与其他模式的区别 小结 开发过程中的一些场景 我们在平时的开发过程中&#xff0c;经常会…

【Android】在Android上使用mlKit构建人脸检测程序

在Android上构建人脸检测程序 目录 1、导入mlKit依赖包2、配置人脸检测器并且获取人脸检测器3、加载图片资源4、调用人脸检测器5、绘制矩形边框6、完整代码7、效果展示 1、导入mlKit依赖包 dependencies {// ...// Use this dependency to bundle the model with your appi…

CSS盒子的浮动与网页布局(重点,有电影页面案例)

浮动适用于那种盒子的并列布局 CSS 提供了三种传统布局方式(简单说,就是盒子如何进行排列顺序)&#xff1a;  普通流&#xff08;标准流&#xff09;  浮动  定位 标准流&#xff08;普通流/文档流&#xff09; 所谓的标准流: 就是标签按照规定好默认方式排列. 1. 块级…

abap 如何debug 更新进程

今天在测试环境做一个外向交货单过账的时候&#xff0c;每次都会dump ST22中看到报错如下&#xff1a;DBSQL_DUPLICATE_KEY_ERROR 接着我就去SM13去看下在哪个跟新里面失败了 双击错误条目可以看到 那么我就想说去debug看看当时的变量到底是啥&#xff0c;为啥会主键重复&…

数据库的三大范式

第一范式&#xff1a; 属性不可分割&#xff1a;每个属性都是不可分割的原子项&#xff08;实体的属性就是表中的列&#xff09; 在上表中contact应该分为phone和adress两列 第二范式&#xff1a; 在满足第一范式的情况下&#xff0c;表中不存在部分依赖&#xff0c;非主键列…

压缩包文件暴力破解 -Server2005(解析)

任务五十一: 压缩包文件暴力破解 任务环境说明:Server2005 1. 通过本地PC中渗透测试平台Kali使用Nmap扫描目标靶机服务版本信息,将 Telnet 版本信息字符串 作为 Flag 提交; flag:Microsoft Windows XP telnetd 2. 通过本地PC中渗透测试平台Kali对服务器场景Windows进行渗透测…

云演CTF Blog

1、啥也搞不了&#xff0c;扫目录。出来个console 2、有显示锁掉了 3、抓包&#xff0c;改返回包 改成true&#xff0c;放包 不好意思&#xff0c;不会了&#xff0c;哈哈哈哈哈哈哈哈哈 你会的话&#xff0c;请告诉我&#xff0c;大佬

C++:命名空间

从今天正式开始对C的学习&#xff0c;这里只学习C对C的拓展&#xff0c;和C相同的部分在C语言专栏中都可以找到&#xff0c;我们先看一段C代码 #include<iostream> using namespace std; int main() {cout<<"hello world<<endl;return 0; } 同样也是打…

数学公式推导中 “:=“和“:=“的区别

A:B 将A定义为&#xff08;记为&#xff0c;令为&#xff09;B A:B 将B定义为&#xff08;记为&#xff0c;令为&#xff09;A

YOLOv8-Seg改进:轻量化卷积设计 | DualConv双卷积魔改v8结构

🚀🚀🚀本文改进: DualConv双卷积魔改v8结构,达到轻量化的同时并能够实现小幅涨点 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1)手把手教你如何训练YOLOv8-seg; 2)模型创新,提升分割性能…

​【EI会议征稿中】#先投稿,先送审#第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)​

#先投稿&#xff0c;先送审# 第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#xff09; 2024 3rd International Conference on Cyber Security, Artificial Intelligence and Digital Economy 2024年3月1日-3日 | 中国南京 会议官网&#xff1a…

漏刻有时数据可视化Echarts组件开发(45)机场流程导航线和指示点的开发记录

路径线 ECharts中的路径线是指用于连接起点和终点的线。在ECharts中&#xff0c;路径图主要用于带有起点和终点信息的线数据的绘制&#xff0c;如地图上的航班、路线等。路径线可以用于展示数据点之间的连接关系&#xff0c;以及数据点之间的相对位置。 {//路径图name: 路线图…

Mr. Cappuccino的第65杯咖啡——MacOS安装Docker

MacOS安装Docker 下载Docker安装Docker查看Docker相关信息镜像加速 下载Docker Docker官网 Docker文档中心 Docker桌面版下载地址 安装Docker 查看Docker相关信息 docker --versiondocker info镜像加速 阿里云镜像加速器 "registry-mirrors": ["https://gq8…

SpringBoot 源码解析2:启动流程1

SpringBoot 源码解析2&#xff1a;启动流程1 1.启动方式2.SpringBootApplication3.SpringApplication3.1 构造器SpringApplication3.2 SpringApplication#run 3.3 SpringApplication#run 中关键方法3.1 SpringApplication#prepareEnvironment3.2 SpringApplication#prepareCont…

BIM 技术:角色漫游

本心、输入输出、结果 文章目录 BIM 技术&#xff1a;角色漫游前言BIM角色漫游中&#xff0c;用户如何与建筑模型进行交互手势识别技术在BIM角色漫游中的应用有哪些图示花有重开日&#xff0c;人无再少年实践是检验真理的唯一标准 BIM 技术&#xff1a;角色漫游 编辑&#xff1…

基于ssm金旗帜文化培训学校网站的设计与开发论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对培训学校展示信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

【云原生kubernets】Pod详解

一、Pod介绍 1.1.概念 Pod是kubernetes中最小的资源管理组件&#xff0c;Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的&#xff0c;例如&#xff0c;用于管理Pod运行的State…

使用国内镜像源安装opencv

在控制台输入命令&#xff1a; pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple 验证安装&#xff1a; step 1&#xff1a; 打开终端&#xff1b;step 2&#xff1a; 输入python&#xff0c;进入Python编译环境&#xff1b;step 3&#xff1a; 粘贴…

打开软木塞,我们来谈谈葡萄酒泡泡吧

香槟是任何庆祝场合的最佳搭配。从婚礼和生日到单身派对和典型的周五晚上&#xff0c;这款气泡饮料是生活中特别聚会的受欢迎伴侣。 来自云仓酒庄品牌雷盛红酒分享你知道吗&#xff0c;你喜欢喝的那瓶香槟酒可能根本不是香槟&#xff0c;而是汽酒&#xff1f;你不是唯一一个认…

面试官:你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢

一、什么是SPA SPA&#xff08;single-page application&#xff09;&#xff0c;翻译过来就是单页应用SPA是一种网络应用程序或网站的模型&#xff0c;它通过动态重写当前页面来与用户交互&#xff0c;这种方法避免了页面之间切换打断用户体验在单页应用中&#xff0c;所有必要…