Android中使用MVP架构相关知识梳理

1|0Android中使用MVP架构相关知识梳理

1|1综述

本文为了记录笔者对MVP架构的认知, 因为当前使用的主要是MVVM, 后面需要考虑尝试MVI或者其他架构, 也可能考虑尝试flutter, 所以将承上启下的MVP相关知识点总结梳理出来, 方便记忆以及后期的回顾.

Android中的MVP架构从MVC架构演变而来, 所以要谈论MVP就不得不先说MVC.
MVC是最传统的Android开发架构, 按照字面理解会分为三层

  • Model层: 负责实现业务逻辑, 一般包括业务逻辑和实体模型
  • View层: 负责展示页面, 一般对应布局文件
  • Controller层: 负责接收输入, 调用业务逻辑并更新页面的显示, 一般对应Activity/Fragment

这个三层的划分实际上并不差, 责任清晰, 包括早期的web开发使用的也是MVC架构.
但MVC开始慢慢被新架构替代, 因为Activity/Fragment不是一个完美的Controller, 它还承担了一部分view的职责. 由于Activity/Fragment既承载视图的显示逻辑,又包含业务逻辑. 这就导致Activity/Fragment如果用来承担Controller的职责的时候会特别的重, 很多MVC结构的项目中, 一个Controller一千多行, 甚至两千多行, 代码耦合度高,同时还难以维护和测试.

于是逐步发展出了MVP架构, 架构同样分了三层

  • Model层: 负责实现业务逻辑, 一般包括业务逻辑和实体模型
  • View层: 负责展示页面, 一般对应布局文件, 以及和显示逻辑强关联的Activity/Fragment
  • Presenter层: 负责存放页面状态, 根据用户的输入调用业务逻辑和显示逻辑

这样, 原本笨重的Activity/Fragment大半职责被拆分到了Presenter层.

1|2概念

一个典型的MVP架构如下图所示

这里用户与View层交互, View通过与Presenter互相持有调用Presenter中实现的调用业务逻辑. Presenter通过持有Model实例并调用对应的方法来实现业务逻辑. 如果业务逻辑有返回数据, 那么数据将会返回到Presenter, 然后数据通过Presenter返回View, 最后被用户所感知.

当然只有图只是为了方便理解关系. 但关系总归要落于实处. 下面给出一个最简单的MVP架构示例以供参考.

//一个Presenter持有Activity的实例, 并在调用构造函数时获取Model类的实例 public class MainPresenter { private MainActivity view; private Model model; public MainPresenter(MainActivity view) { this.view = view; this.model = new Model(); } //唯一的方法通过mode获取数据 public void getData() { String data = model.getData(); view.showResult(data); } } //一个Activity作为view层, 持有Presenter的实例 public class MainActivity extends AppCompatActivity { private MainPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); presenter = new MainPresenter(this); init(); } private void init() { Button btnClick = findViewById(R.id.btn_click); btnClick.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.getData(); } }); } void showResult(String message) { TextView tvResult = findViewById(R.id.tv_result); tvResult.setText(message); } } //一个model提供数据 public class Model { String getData() { return "通过Model获取的data"; } } //layout略, 只要求一个id为btn_click的button和一个id为tv_result的text.

上面这个例子没有使用接口, 没有抽象类, 没有依赖注入或者Provider工厂, 没有使用回调或者数据总线返回getData的结果, 也没有用线程模拟延时, 各个类之间高度耦合, 而且还有内存泄露的问题.

但MVP和MVC两种架构之间最本质的区别给出来了, 那就是Activity从作为Controller的角色改为View的角色, 只负责接收用户的输入, 并显示结果. 而原本Activity肩负的调用Model并且获取结果后通过页面显示出来的职责被移交给了Presenter.

这里的核心是Activity和Presenter互相持有, 且原本MVC中Controller的职责被一分为二, 负责调度业务逻辑的部分被划分给了Presenter.

从整个操作流程分析, 用户通过View触发OnClickListener()回调, OnClickListener()回调调用被Activity持有的Presenter实例对应的方法, Presenter获取数据后通过Presenter持有的Activity实例调用showResult()方法将获取的数据显示到页面上.

1|3MVP架构的可用实现

前面已经给出了一个mvp架构的最小示例了, 但是一个高度耦合的mvp框架是无法满足实际的使用需求, 所以在真正使用时一般至少需要引入接口, 使得View和Presenter之间互相解耦, 并准备好attachView和detachView两个方法, 方便Presenter和View的生命周期绑定. 这样才算是一个初步可用的mvp架构.

//使用contract即契约类汇总单个页面对应的View和Presenter接口, 方便维护 public interface MainContract { interface View { void showResult(String message); } interface Presenter { void attachView(View view); void detachView(); void getData(); } } //修改Presenter, 实现attachView和detachView方法, 避免内存泄露 public class MainPresenter implements MainContract.Presenter { private MainContract.View view; private Model model; @Override public void attachView(MainContract.View view) { this.view = view; this.model = new Model(); } @Override public void detachView() { this.view = null; this.model = null; } //唯一的方法通过mode获取数据 public void getData() { String data = model.getData(); view.showResult(data); } } //修改Activity, 在恰当的生命周期调用Presenter的attachView()和detachView()方法 public class MainActivity extends AppCompatActivity implements MainContract.View { private MainContract.Presenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); presenter = new MainPresenter(); presenter.attachView(this); init(); } @Override protected void onDestroy() { super.onDestroy(); presenter.detachView(); presenter = null; //可以不写 } private void init() { Button btnClick = findViewById(R.id.btn_click); btnClick.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.getData(); } }); } @Override public void showResult(String message) { TextView tvResult = findViewById(R.id.tv_result); tvResult.setText(message); } } //model部分没有变化 public class Model { String getData() { return "通过Model获取的data"; } } //同样, layout略

以上示例相较于一开始用于理解的版本初步解决了两个问题, 一个是内存泄露, 一个是Presenter和View的解耦. 属于可用范畴, 当然正常使用仍然应该加上依赖注入或者使用Provider进行手动注入来进一步解耦.

1|4进阶版本的MVP架构

在上述版本中MVP架构仍然存在一些问题, 比如如果有多个页面每个页面都需要调用attachView()和detachView()方法, 这么做很麻烦而且也担心遗漏所导致的内存泄露, 所以如果要进一步拓展, 可以考虑引入基类和WeakReference 并且使用依赖注入框架或手动注入来尽可能解耦合. 参考下面这个拓展写法.

//首先是两个以base命名的基类 public abstract class BaseActivity<V, P extends BasePresenter<V>> extends AppCompatActivity{ protected P presenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); presenter = getPresenter(); presenter.attachView(getCurrentView()); } @Override protected void onDestroy() { super.onDestroy(); presenter.detachView(); } protected abstract P getPresenter(); protected abstract V getCurrentView(); } public abstract class BasePresenter<V> { private Reference<V> mViewRef; public void attachView(V view) { mViewRef = new WeakReference<V>(view); this.onAttach(); } public void detachView() { mViewRef.clear(); mViewRef = null; this.onDetach(); } protected boolean isViewAttached() { return mViewRef != null && mViewRef.get() != null; } protected V getView() { return mViewRef != null ? mViewRef.get() : null; } protected abstract void onAttach(); protected abstract void onDetach(); } //一般来说应该选择一个依赖注入框架比如ButterKnife, Dagger等, 但是那就超出本文想要讨论的范畴了, 所以这里给出手动注入的做法 public class App extends Application { @Override public void onCreate() { super.onCreate(); initModuleProvider(); } void initModuleProvider(){ ModuleProvider.getInstance(); } } public class ModuleProvider { private static volatile ModuleProvider instance; private static final Object mSync = new Object(); private ModuleProvider(){} public static ModuleProvider getInstance(){ if (instance == null){ synchronized (mSync){ if (instance == null){ instance = new ModuleProvider(); } } } return instance; } private final Model model = new Model(); private Reference<MainPresenter> mainPresenterRef; public Model getModel(){ return model; } public MainPresenter getMainPresenter() { if (mainPresenterRef == null || mainPresenterRef.get() == null){ mainPresenterRef = new WeakReference<>(new MainPresenter()); } return mainPresenterRef.get(); } } //然后是一直没有改变的model类 public class Model { public String getData() { return "通过Model获取的data"; } } //最后是正常的Contract, 以及View和Presenter public interface MainContract { interface View { void showResult(String message); } interface Presenter{ void getData(); } } public class MainActivity extends BaseActivity<MainContract.View, MainPresenter> implements MainContract.View { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } @Override protected void onDestroy() { super.onDestroy(); } @Override protected MainPresenter getPresenter() { return ModuleProvider.getInstance().getMainPresenter(); } @Override protected MainContract.View getCurrentView() { return this; } private void init() { Button btnClick = findViewById(R.id.btn_click); btnClick.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.getData(); } }); } @Override public void showResult(String message) { TextView tvResult = findViewById(R.id.tv_result); tvResult.setText(message); } } public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter{ private Model model; @Override protected void onAttach() { model = ModuleProvider.getInstance().getModel(); } @Override protected void onDetach() { model = null; } //唯一的方法通过mode获取数据 public void getData() { if (!isViewAttached()){ return; } String data = model.getData(); getView().showResult(data); } }

以上就是一个比较完整的MVP架构, View使用Fragment也是类似的做法. 不过为了控制大小尽量避免引入三方依赖, 作为Model层示例的Model类也只是给出了一个样子货.
实际使用时可以拓展为Repository层架构或者直接Clean Architect架构, 两者结合就称得上是一个完整的android项目结构了.


__EOF__

本文作者地维藏光
本文链接https://www.cnblogs.com/dwcg/articles/18070757.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   地维藏光  阅读(70)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示