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上。

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的交互。而且便于重用。
-
优点:
层次清晰:模块职责划分明显,接口功能清晰,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。
它的最重要的思想就是数据驱动模型。抛弃了以前的命令式事件驱动。
数据与UI双向绑定,从而省去了模型数据改变后通知数据更新的步骤。核心就是UI上你输入的内容,立刻反应到变量上;而变量的数据发生变化,立刻自动更新UI。
我们来试图揣测下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编程范式:
- ViewModel持有liveData提供注册绑定:提前将ViewModel里面liveData在Activity/View层注册监听准备好。
- View → ViewModel:用户交互(如点击按钮)触发事件,通过接口或Data Binding传递给ViewModel。
- ViewModel → Model:ViewModel调用Model(数据库,网络Api等)的方法获取或处理数据。
- Model → ViewModel:Model通过回调或响应式编程将结果返回给ViewModel。
- 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展示项目。
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()}
}
开发范式为:
- ViewModel中持有Flow(数据对象),它们可以供View层去注册监听,刷新UI。
- 定义了所有的显示UI的State;
- 定义了所有的View层往ViewModel调用的Intent(非Android Intent类,这里理解为Action更为合适,以后用Action来表述);
- View->ViewModel:传递Action
- ViewModel->Model: 解析具体某个Action,执行对应的Model数据层请求,并转变得到State。
- ViewModel->View: State更新以后,触发View层的UI更新。
总结
收回开头个人感悟,只有理解了MVC,MVP,MVVM,MVI的核心思想:
业务分层,数据驱动,单向数据流, 事件绑定监听, 生命周期感知, 内存泄漏关注, …
这些开发原则才是重点。
再结合时下流程的开发框架,ViewModel+LiveData+Flow+协程
,相信一定能开发出高质量,可维护性高的项目代码。
在下一篇文章会继续学习android官方架构开发指南,并给出一个比较合适的基础开发架构。