MVPVM
可以看到MVPVM 其实就是MVP的变种,加入了MVVM事件特性,增加了ViewModel,功能分类: View:只做视图更新操作 Model: 只做数据处理,网络数据 、本地数据 Presenter: 只做业务逻辑处理,View或者Model 事件分发 ViewModel: 绑定View 和 Model,添加数据变更监视器
从0开始搭建MVP+ViewModel框架的android应用01---MVPVM诞生记
在 Android 架构中,MVPVM 并不是一个官方或广泛认可的标准模式,而是开发者开发者在实践中结合 MVP(Model-View-Presenter) 和 MVVM(Model-View-ViewModel) 思想形成的混合架构。其核心是保留 MVP 中 Presenter 的业务协调能力,同时引入 MVVM 中 ViewModel 的数据持有与生命周期感知特性,解决 MVP 中状态管理复杂和内存泄漏的问题。
MVPVM 核心组件与设计思想
MVPVM 在 MVP 基础上增加了 ViewModel 层,明确各组件职责:
| 组件 | 职责 | 与其他模式的区别 |
|---|---|---|
| Model | 处理数据逻辑(网络请求、数据库操作等),不依赖其他层。 | 与 MVP、MVVM 一致,纯数据层。 |
| View | 展示数据、接收用户交互,通过接口与 Presenter 通信,不包含业务逻辑。 | 同 MVP 的 View,但不再直接依赖 Presenter 更新 UI,而是观察 ViewModel 的数据变化。 |
| Presenter | 协调业务逻辑:接收 View 的交互指令 → 调用 Model 处理数据 → 更新 ViewModel。 | 比 MVP 的 Presenter 更 “轻量”,不直接操作 View,而是通过 ViewModel 传递数据。 |
| ViewModel | 持有 UI 状态数据(如加载状态、表单数据),具有生命周期感知(与 Activity 生命周期解耦),通过 LiveData 通知 View 更新。 | 引入 MVVM 的 ViewModel 特性,解决 MVP 中状态恢复和内存泄漏问题。 |
核心交互流程
用户交互 → View → Presenter → Model(处理数据)
↓
ViewModel(更新数据)← Presenter ← 处理结果
↓
View(观察数据变化,更新UI)
Java 代码示例(登录功能)
以登录功能为例,展示 MVPVM 各组件的协作:
1. Model 层(数据与业务逻辑)
与 MVP 一致,负责数据处理:
// 数据模型
public class User {
private String username;
private String password;
// 构造函数、getter
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() { return username; }
public String getPassword() { return password; }
}
// 登录业务逻辑
public class LoginModel {
public void login(User user, LoginCallback callback) {
new Thread(() -> { // 模拟网络请求
try {
Thread.sleep(1000);
if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) {
callback.onSuccess("登录成功");
} else {
callback.onFailure("用户名或密码错误");
}
} catch (InterruptedException e) {
callback.onFailure("网络异常");
}
}).start();
}
public interface LoginCallback {
void onSuccess(String message);
void onFailure(String error);
}
}
2. ViewModel 层(持有状态数据)
使用 LiveData 存储 UI 状态,具有生命周期感知:
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class LoginViewModel extends ViewModel {
// 私有可变数据(仅内部更新)
private final MutableLiveData<Boolean> _isLoading = new MutableLiveData<>(false);
private final MutableLiveData<String> _resultMessage = new MutableLiveData<>("");
private final MutableLiveData<String> _username = new MutableLiveData<>("");
private final MutableLiveData<String> _password = new MutableLiveData<>("");
// 暴露不可变的 LiveData 给 View 观察
public LiveData<Boolean> isLoading = _isLoading;
public LiveData<String> resultMessage = _resultMessage;
public LiveData<String> username = _username;
public LiveData<String> password = _password;
// 更新状态的方法(供 Presenter 调用)
public void setLoading(boolean loading) {
_isLoading.setValue(loading);
}
public void setResultMessage(String message) {
_resultMessage.setValue(message);
}
public void setUsername(String username) {
_username.setValue(username);
}
public void setPassword(String password) {
_password.setValue(password);
}
}
3. Presenter 层(协调逻辑)
接收 View 指令,调用 Model 处理数据,更新 ViewModel:
public class LoginPresenter {
private LoginModel model;
private LoginViewModel viewModel;
// 构造函数:注入 Model 和 ViewModel
public LoginPresenter(LoginModel model, LoginViewModel viewModel) {
this.model = model;
this.viewModel = viewModel;
}
// 处理登录逻辑
public void login(String username, String password) {
// 1. 更新 ViewModel:显示加载
viewModel.setLoading(true);
viewModel.setResultMessage("");
// 2. 调用 Model 登录
User user = new User(username, password);
model.login(user, new LoginModel.LoginCallback() {
@Override
public void onSuccess(String message) {
// 更新 ViewModel:登录成功
viewModel.setLoading(false);
viewModel.setResultMessage(message);
}
@Override
public void onFailure(String error) {
// 更新 ViewModel:登录失败
viewModel.setLoading(false);
viewModel.setResultMessage(error);
}
});
}
// 处理用户名输入变化
public void onUsernameChanged(String username) {
viewModel.setUsername(username);
}
// 处理密码输入变化
public void onPasswordChanged(String password) {
viewModel.setPassword(password);
}
}
4. View 层(Activity)
观察 ViewModel 数据变化更新 UI,用户交互通过 Presenter 处理:
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.text.Editable;
import android.text.TextWatcher;
public class LoginActivity extends AppCompatActivity {
private EditText etUsername;
private EditText etPassword;
private Button btnLogin;
private ProgressBar progressBar;
private TextView tvResult;
private LoginPresenter presenter;
private LoginViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
// 初始化 ViewModel(生命周期感知)
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
// 初始化 Presenter(注入 Model 和 ViewModel)
presenter = new LoginPresenter(new LoginModel(), viewModel);
// 观察 ViewModel 数据变化,更新 UI
observeViewModel();
// 绑定用户交互到 Presenter
bindEvents();
}
private void initView() {
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
btnLogin = findViewById(R.id.btn_login);
progressBar = findViewById(R.id.progress_bar);
tvResult = findViewById(R.id.tv_result);
}
private void observeViewModel() {
// 观察加载状态
viewModel.isLoading.observe(this, isLoading -> {
progressBar.setVisibility(isLoading ? View.VISIBLE : View.GONE);
});
// 观察结果信息
viewModel.resultMessage.observe(this, message -> {
tvResult.setText(message);
});
// 观察用户名(可选,用于回显)
viewModel.username.observe(this, username -> {
etUsername.setText(username);
});
}
private void bindEvents() {
// 用户名输入变化 → 通知 Presenter
etUsername.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
presenter.onUsernameChanged(s.toString());
}
// 其他方法省略
});
// 密码输入变化 → 通知 Presenter
etPassword.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
presenter.onPasswordChanged(s.toString());
}
// 其他方法省略
});
// 登录按钮点击 → 通知 Presenter
btnLogin.setOnClickListener(v -> {
String username = etUsername.getText().toString();
String password = etPassword.getText().toString();
presenter.login(username, password);
});
}
}
MVPVM 的应用场景
- 需要状态持久化的场景:如屏幕旋转、进程重建时需保留用户输入或加载状态(依赖 ViewModel 的生命周期特性)。
- 中等复杂度项目:既需要 Presenter 清晰的业务协调能力,又希望简化状态管理(避免 MVP 中手动恢复状态的繁琐)。
- 团队混合技能栈:团队中部分成员熟悉 MVP,部分熟悉 MVVM,MVPVM 可作为过渡方案。
优点
- 状态管理简化:ViewModel 持有 UI 状态并与 Activity 生命周期解耦,解决了 MVP 中屏幕旋转后状态丢失的问题。
- 内存泄漏风险降低:Presenter 不再直接持有 View 引用,而是通过 ViewModel 传递数据,减少因 View 销毁导致的泄漏。
- 业务逻辑与 UI 解耦更彻底:View 仅通过观察 ViewModel 更新 UI,Presenter 专注业务逻辑,职责更单一。
- 可测试性保留:Presenter 和 ViewModel 均不依赖 Android 框架,可通过 JUnit 独立测试。
缺点
- 层级冗余:相比 MVP 多了 ViewModel 层,简单场景下代码量增加(需维护 Presenter 和 ViewModel 两个中间层)。
- 学习成本高:需要同时理解 MVP 的 Presenter 协调逻辑和 MVVM 的 ViewModel/LiveData 数据驱动思想。
- 职责边界模糊:若设计不当,可能出现 Presenter 和 ViewModel 职责重叠(如两者都处理部分业务逻辑),反而增加复杂度。
- 不如纯 MVVM 简洁:对于复杂交互,MVVM 可通过 DataBinding 进一步简化 View 与 ViewModel 的绑定,而 MVPVM 仍需 Presenter 中转,略显繁琐。
总结
MVPVM 是 MVP 向 MVVM 过渡的混合架构,通过引入 ViewModel 解决了 MVP 的状态管理和内存泄漏问题,同时保留了 Presenter 对业务逻辑的清晰控制。其优点是状态持久化能力强、解耦更彻底,缺点是层级冗余、学习成本高。实际开发中,若团队已熟悉 MVVM,更推荐直接使用纯 MVVM(结合 DataBinding/Compose);若需兼容现有 MVP 代码,MVPVM 可作为折中方案。