当前位置: 首页 > news >正文

android MVC/MVP/MVVM/MVI架构发展历程和编写范式

前言

Android的架构发展进程。诸位看了那么多帖子,都在说MVVM,MVI架构如何如何。看完我的帖子,结合你自己的开发,你认为你见过的代码真正完全遵循了MVVM, MVI等架构吗?
个人认为:架构只是一种思想,并且在android上它们的使用是模糊的。在官方的开发库的指引和约束下,才形成了现在的开发规范。比如liveData和Flow,ViewModel的开发框架之下,其实是有MVVM和MVI的影子的,但又不完全相同。我们主要是领悟其中部分的架构重点:
业务分层数据驱动单向数据流, 事件绑定监听, 生命周期感知关注内存泄露

MVC

View:视图层,android 中的 xml文件/Activity/Fragment;

Controller:控制层,但在开发过程中会把 Controller 层的任务也放到 Activity/Fragment 层,导致 View 层臃肿,耦合严重。

Model:模型层,一般是网络请求、数据库相关的操作。

让我们把时间拨回到蛮荒时代(10年前?),还没有jetpack的库,没有ViewModel + LiveData的蛮荒时代,刚刚接触android开发,常常一把梭代码都在Activity里面写完。大家也听说了window,前端,后端开发,都有MVC架构,于是把它引入到android上。

screenshot-20250801-103422

Demo:

class XXXActivity {private XXXController controller = new XXXController();void onCreate() {setContentView(R.layout.ui)findViewById(R.id.button).setOnClick() {controller.request() {mTextView.setText()}}}
}class XXXController {String request() {apiModel.requestApi()}
}class ApiModel {String requestApi()
}
  • 优点:
    职责分离清晰(视图/逻辑/数据)
    学习成本低,适合中小项目
    降低了业务耦合性

  • 缺点:
    视图逻辑与业务逻辑混杂
    臃肿的Activity/Fragment
    不利于复用,controller与Activity之间的深度绑定

天然的,android上就是xml编写布局,View自然而然的已经帮你分开。但是Activity又与xml和控件强相关,xml和Activity都充当了View;而Controller虽然你可能想到抽取成一个函数用来屏蔽Model数据的请求,但是结果往往需要直接设置上去,但是往往Activity也充当了部分controller的职责。controller与Activity之间界限模糊,代码相互耦合,不利于重构和抽象。

MVP

Model:模型层用于数据查询以及业务逻辑,跟MVC大同小异。
View:视图层用于展示与用户实现交互的页面,通常实现数据的输入和输出功能,跟MVC大同小异。
Presenter:表示器负责连接M层和V层,从而完成Model层与View层的交互,还可以进行一些业务逻辑的处理。

基于在android实现上,controller和View(activity)之间过于耦合交错的原因,于是引入了MVP架构。
它的引入本质是想彻底打断View与Model之间的耦合,交给Presenter层去管理V和M的交互。而且便于重用。
screenshot-20250801-103422

  • 优点:
    层次清晰:模块职责划分明显,接口功能清晰,Model层和View层分离,修改View而不影响Model。
    重用性高:功能复用度高,方便.一个Presenter可以复用于多个View,而不用更改Presenter的逻辑。
    开发职责互不影响:如果后台接口还未写好,但已知返回数据类型的情况下,完全可以写出此接口完整的功能。
    面向接口编程。

  • 缺点:
    由于View层和Model层都需要经过Presenter层,导致Presenter层过于复杂,维护麻烦
    面向接口编程,样板代码非常多。

Demo:如下我让AI帮我写的,是一个标准的MVP代码。
你想想一个简单的界面请求与状态变更竟然冒出一堆抽象的类,View也得提供函数,请求接口也得额外封装。追加代码的时候,简直是灾难。这种样板代码写起来过于繁琐。


public interface LoginContract {interface View {void showProgress();void hideProgress();void loginSuccess();void loginFail(String error);}interface Presenter {void login(String username, String password);}interface Model {void performLogin(String username, String password, OnLoginListener listener);interface OnLoginListener {void onSuccess();void onFailure(String error);}}
}class LoginModel implements LoginContract.Model {@Overridepublic void performLogin(String username, String password, OnLoginListener listener) {// 模拟网络请求延迟new Handler().postDelayed(() -> {if ("admin".equals(username) && "123456".equals(password)) {listener.onSuccess();} else {listener.onFailure("用户名或密码错误");}}, 2000);}
}class LoginPresenter implements LoginContract.Presenter {private LoginContract.View view;private LoginContract.Model model;public LoginPresenter(LoginContract.View view) {this.view = view;this.model = new LoginModel();}@Overridepublic void login(String username, String password) {view.showProgress();model.performLogin(username, password, new LoginContract.Model.OnLoginListener() {@Overridepublic void onSuccess() {view.hideProgress();view.loginSuccess();}@Overridepublic void onFailure(String error) {view.hideProgress();view.loginFail(error);}});}
}class LoginActivity extends AppCompatActivity implements LoginContract.View {private EditText etUsername, etPassword;private Button btnLogin;private ProgressBar progressBar;private LoginContract.Presenter presenter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);presenter = new LoginPresenter(this);btnLogin.setOnClickListener(v -> {...presenter.login(username, password);});}@Overridepublic void showProgress() {progressBar.setVisibility(View.VISIBLE);}@Overridepublic void hideProgress() {progressBar.setVisibility(View.GONE);}@Overridepublic void loginSuccess() {...}@Overridepublic void loginFail(String error) {Toast.makeText(this, error, Toast.LENGTH_SHORT).show();}
}

MVVM

而MVVM的出现,它的思想可以追溯到window的WPF。

它的最重要的思想就是数据驱动模型。抛弃了以前的命令式事件驱动
screenshot-20250801-103422

数据与UI双向绑定,从而省去了模型数据改变后通知数据更新的步骤。核心就是UI上你输入的内容,立刻反应到变量上;而变量的数据发生变化,立刻自动更新UI。
screenshot-20250801-103422

我们来试图揣测下android官方的心路历程
当时MVC和MVP模式编写的代码,导致Activity/Fragment耦合臃肿,同时,它们又与Context强相关,Activity的旋转导致数据丢失,controller/presenter持有View引用,代码耦合,内存泄露等问题,都会让开发出来的app质量不高。

于是google官方推出jetpack系列库,设计了lifecycle+ViewModel+LiveData的官方框架:

  • lifecycle构架整个androidx基石,通过自动解除引用的观察者模式构建了Activity/Fragment等生命周期的基础;所有的viewModel,liveData, 以及后来的协程scope都与它脱不开;
  • viewModel要求脱离context/Activity/Fragment,解构控制层代码;
  • LiveData用来做监听器和更新数据,提供数据驱动(发展到kotlin,又有Flow)。

逐渐形成了如下的android编程范式:

  1. ViewModel持有liveData提供注册绑定:提前将ViewModel里面liveData在Activity/View层注册监听准备好。
  2. View → ViewModel:用户交互(如点击按钮)触发事件,通过接口或Data Binding传递给ViewModel。
  3. ViewModel → Model:ViewModel调用Model(数据库,网络Api等)的方法获取或处理数据。
  4. Model → ViewModel:Model通过回调或响应式编程将结果返回给ViewModel。
  5. ViewModel → View:ViewModel更新LiveData/StateFlow,View通过数据绑定自动更新UI。
  • 优点:
    分离关注点:View专注UI展示,ViewModel处理业务逻辑,Model管理数据,代码结构清晰。
    可测试性强:ViewModel不依赖Android框架,可通过单元测试验证逻辑。
    生命周期安全:ViewModel感知Activity/Fragment的生命周期,避免内存泄漏。

  • 缺点:
    数据绑定会消耗额外资源,占用更多的内存
    数据绑定会导致模型数据的变化变得难以追踪

MVI

我们来看,时下android kt+Flow+协程开发更接近的一种思想。
MVI即Model-View-Intent,最重要的就是单向数据流
它受Cycle.js前端框架的启发,提倡一种单向数据流的设计思想,非常适合数据驱动型的UI展示项目。
screenshot-20250801-103422

Model
与其他MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。
当前界面展示的内容无非就是UI状态的一个快照:例如数据加载过程、控件位置等都是一种UI状态
View
与MVVM/MVC等中的View一致,可能是一个Activity、Fragment或者任意UI承载单元。
MVI中的View通过订阅Intent的变化实现界面刷新(不是Activity的Intent、后面介绍)
Intent
此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model进行数据请求。

从界面发出的数据叫Intent,而界面接收的数据叫State,这样整个界面的刷新流程就形成一条Unidirectional Data Flow(UDF),即单向数据流。

  • 优点:
    强调单一数据流和不可变状态,易于调试和测试。
    状态集中管理,UI 只需渲染状态,逻辑清晰。
    Intent 和 State 的分离,使得代码更加模块化。

  • 缺点:
    对于简单的应用可能显得过于复杂。
    状态对象可能会变得非常庞大,管理起来比较麻烦。
    学习曲线较陡,特别是对不熟悉函数式编程和不可变状态的开发者。

这里用AI生成的demo:

sealed class CounterState {
//不推荐写成data class。原因看上一篇帖子 https://blog.csdn.net/jzlhll123/article/details/149749205data class Success(val count: Int) : CounterState()object Loading : CounterState()data class Error(val message: String) : CounterState()
}sealed class CounterIntent {object Increment : CounterIntent()object Decrement : CounterIntent()object Reset : CounterIntent()
}CounterViewModel : ViewModel() {private val _state = MutableStateFlow<CounterState>(CounterState.Success(0))val state: StateFlow<CounterState> = _statefun processIntent(intent: CounterIntent) {when (intent) {is CounterIntent.Increment -> {val current = (_state.value as? CounterState.Success)?.count ?: 0_state.value = CounterState.Success(current + 1)}is CounterIntent.Decrement -> {val current = (_state.value as? CounterState.Success)?.count ?: 0_state.value = CounterState.Success(current - 1)}is CounterIntent.Reset -> {_state.value = CounterState.Success(0)}}}
}class MainActivity : AppCompatActivity() {private val viewModel: CounterViewModel by viewModels()private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.state.collect { state ->when (state) {is CounterState.Success -> updateUI(state.count)CounterState.Loading -> showLoading()is CounterState.Error -> showError(state.message)}}}}binding.btnIncrement.setOnClickListener {viewModel.processIntent(CounterIntent.Increment)}binding.btnDecrement.setOnClickListener {viewModel.processIntent(CounterIntent.Decrement)}binding.btnReset.setOnClickListener {viewModel.processIntent(CounterIntent.Reset)}}private fun updateUI(count: Int) {binding.tvCount.text = count.toString()}
}

开发范式为:

  1. ViewModel中持有Flow(数据对象),它们可以供View层去注册监听,刷新UI。
  2. 定义了所有的显示UI的State;
  3. 定义了所有的View层往ViewModel调用的Intent(非Android Intent类,这里理解为Action更为合适,以后用Action来表述);
  4. View->ViewModel:传递Action
  5. ViewModel->Model: 解析具体某个Action,执行对应的Model数据层请求,并转变得到State。
  6. ViewModel->View: State更新以后,触发View层的UI更新。

总结

收回开头个人感悟,只有理解了MVC,MVP,MVVM,MVI的核心思想:

业务分层数据驱动单向数据流, 事件绑定监听, 生命周期感知内存泄漏关注, …
这些开发原则才是重点。
再结合时下流程的开发框架,ViewModel+LiveData+Flow+协程,相信一定能开发出高质量,可维护性高的项目代码。

在下一篇文章会继续学习android官方架构开发指南,并给出一个比较合适的基础开发架构。

http://www.lryc.cn/news/607181.html

相关文章:

  • W3D引擎游戏开发----从入门到精通【10】
  • 蚂蚁开源团队发布的2025大模型开源开发生态发展情况速览
  • androidstudio调试apt
  • Ubuntu 系统下使用 lsusb 命令识别 USB 设备及端口类型详解
  • LS-DYNA 分析任务耗时长,企业如何科学提升许可证使用效率?
  • Flask 中的应用上下文和请求上下文
  • [AI8051U入门第十二步]W5500-Modbus TCP从机
  • SQLFlash:一款由AI驱动的SQL优化工具
  • leetcode热题——全排列
  • 《平台经济法律风险合规发展》研讨会在北京召开
  • Fiddler中文版使用指南 提升开发流程的一站式抓包与调试体验
  • Day17--二叉树--654. 最大二叉树,617. 合并二叉树,700. 二叉搜索树中的搜索,98. 验证二叉搜索树
  • 如何在 Mac OS 上安装 Cursor
  • 【目标检测】芯片缺陷识别中的YOLOv12模型、FP16量化、NMS调优
  • Lombok常用注解及功能详解
  • Redis学习18-分布式锁
  • Vue 3.5 defineModel:让组件开发效率提升 10 倍
  • 暑期算法训练.12
  • 【VSCode】常用插件推荐(持续更新~)
  • 从资源闲置到弹性高吞吐,JuiceFS 如何构建 70GB/s 吞吐的缓存池?
  • C 实现难度过高的俄罗斯方块
  • 数据赋能(371)——数据挖掘——概述
  • LLM Prompt与开源模型资源(1)提示词工程介绍
  • UniApp与WebView双向通信机制及生产级实现方案全解析
  • 计数组合学7.10(舒尔函数的组合定义)
  • Golang 语言 Channel 的使用方式
  • 数据结构:链表(Linked List)
  • 如何通过黑白棋盘进行定位配准融合?(前后安装的两个相机)
  • 【Mysql】联合索引生效分析案例
  • 【科研绘图系列】R语言绘制环状分组显著性柱状堆积图