MVVM
介绍1
1. MVVM介绍
MVVM全名是Model-View-ViewModel,MVVM可以看作MVP的升级版。又称状态机制,View和ViewModel 是进行绑定的,改变ViewModel 就会直接作用到View视图上,而View 会把事件传递给ViewModel,ViewModel去对Model进行操作并接受更新。
Model:模型层,负责处理数据的加载或存储。与MVP中的M一样。 View:视图层,负责界面数据的展示,与用户进行交互。与MVP中的V一样。 ViewModel:视图模型,负责完成View于Model间的交互,负责业务逻辑。
1.1 作用
- 降低View和控制模块的耦合,减轻了视图的压力。
1.2 流程

MVVM.png
1.View与ViewModel进行绑定,能够实现双向的交互。ViewModel数据改变时,View会相应变动UI,反之亦然。 2.ViewModel进行业务逻辑处理,通知Model去更新。 3.Model数据更新后,把新数据传递给ViewModel。
2. MVVM例子实现
还是以点击按钮对数字+1为例子,将其改造成MVVM模式。与MVP不同的地方是,ViewModel会跟View进行绑定。这里会用到Android的 Data Binding。关于Data Binding,可以看下这篇文章介绍:Data Binding Library
2.1 Model层
跟MVP的一样
public class NumModel {
private int num = 0;
public void add(ModelCallback callback) {
callback.onSuccess(++num);//通知Presenter结果
}
public interface ModelCallback {//数据回调接口
void onSuccess(int num);
void onFailed(String text);
}
}
2.2 View层
改写布局,增加Data Binding。
vm_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="numVM"
type="com.xx.oo.NumViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{numVM.num}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{numVM.onClickAdd}"
android:text="点击+1"/>
</LinearLayout>
</layout>
VmActivity类,将View与ViewModel进行绑定:
public class VmActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NumViewModel numViewModel = new NumViewModel();
VmActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.vm_activity);
binding.setNumVM(numViewModel);//View与ViewModel绑定
}
}
2.3 ViewModel
ViewModel负责业务逻辑处理,并且数据有更新直接通知View去更改。
public class NumViewModel extends BaseObservable {
private String num;
private final NumModel mNumModel;
public NumViewModel() {
mNumModel = new NumModel();
}
@Bindable
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
notifyPropertyChanged(BR.num);//更新UI
}
public void onClickAdd(View view) {//点击事件处理
mNumModel.add(new NumModel.ModelCallback() {//相关逻辑处理,这里直接交给Model层
@Override
public void onSuccess(int num) {
setNum(num + "");//成功,更新数据
}
@Override
public void onFailed(String text) {
setNum(text);//失败,更新数据
}
});
}
}
3. MVP与MVVM区别
- ViewModel与View绑定后,ViewModel与View其中一方的数据更新都能立即通知到对方;Presenter需要通过接口去通知View进行更新。
4. MVVM的优点
- 相比于MVP,Presenter与View存在耦合。ViewModel与View的耦合则更低,ViewModel只负责处理和提供数据,UI的改变,比如TextView 替换 EditText,ViewModel 几乎不需要更改任何代码,只需专注于数据处理就可以了。
- ViewModel里面只包含数据和业务逻辑,没有UI的东西,方便单元测试。
5. MVVM的缺点
- 数据绑定使得程序较难调试,界面出现异常时,有可能是 View 的代码有问题,也可能是 Model 的代码有问题。由于数据绑定使得数据能够快速传递到其他为止,因此要定位出异常就比较有难度了。
6.其他
- 如果要弹Dialog等等操作,可以在Activity中监听ViewModel的数据变化来做处理。
介绍2
MVP(Model-View-Presenter)是 Android 开发中用于解决 MVC 中 Activity 职责过重问题的架构模式,核心是通过Presenter 层隔离 View 与 Model,使视图逻辑与业务逻辑完全解耦。以下从核心概念、代码示例、应用场景及优缺点展开详解:
MVP 核心组件与分工
MVP 将应用分为三个独立模块,职责边界清晰:
| 组件 | 职责 | Android 对应实现 |
|---|---|---|
| Model(模型) | 处理数据逻辑(网络请求、数据库操作、业务计算等),不依赖 View 和 Presenter。 | Java Bean、数据仓库、工具类 |
| View(视图) | 展示数据、接收用户交互(点击、输入等),通过接口与 Presenter 通信,不包含业务逻辑。 | Activity、Fragment、自定义 View |
| Presenter(Presenter) | 作为 View 与 Model 的中间层:接收 View 的交互指令 → 调用 Model 处理数据 → 通知 View 更新 UI。 | 独立的 Java 类(持有 View 接口和 Model 实例) |
核心交互流程
用户交互 → View(Activity)→ Presenter → Model(处理数据)
↓
View(更新UI)← Presenter ← 处理结果(回调)
Java 代码示例(登录功能)
以登录功能为例,展示 MVP 各组件的协作:
1. Model 层(数据与业务逻辑)
负责用户数据验证和网络请求(模拟):
// 数据模型:用户信息
public class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
// getter
public String getUsername() { return username; }
public String getPassword() { return password; }
}
// 业务逻辑:登录处理(Model 核心)
public class LoginModel {
// 模拟登录验证(网络请求/数据库查询)
public void login(User user, LoginCallback callback) {
new Thread(() -> { // 异步操作
try {
Thread.sleep(1000); // 模拟网络延迟
// 验证逻辑:用户名密码为 admin/123456
if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) {
callback.onSuccess("登录成功");
} else {
callback.onFailure("用户名或密码错误");
}
} catch (InterruptedException e) {
callback.onFailure("网络异常");
}
}).start();
}
// 回调接口:通知 Presenter 处理结果
public interface LoginCallback {
void onSuccess(String message);
void onFailure(String error);
}
}
2. View 层(接口 + Activity 实现)
View 层通过接口定义交互能力,Activity 实现接口并专注于 UI 操作:
// View 接口:定义 View 需实现的功能(更新 UI、展示加载等)
public interface LoginView {
// 显示加载中
void showLoading();
// 隐藏加载中
void hideLoading();
// 显示登录结果
void showResult(String message);
// 获取用户输入的用户名
String getUsername();
// 获取用户输入的密码
String getPassword();
}
// View 实现类:Activity(仅处理 UI 逻辑)
public class LoginActivity extends AppCompatActivity implements LoginView {
private EditText etUsername;
private EditText etPassword;
private Button btnLogin;
private ProgressBar progressBar;
private TextView tvResult;
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
// 初始化 Presenter(传入 View 接口)
presenter = new LoginPresenter(this, new LoginModel());
// 绑定登录按钮点击事件
btnLogin.setOnClickListener(v -> presenter.login());
}
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);
}
// 实现 LoginView 接口方法
@Override
public void showLoading() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
progressBar.setVisibility(View.GONE);
}
@Override
public void showResult(String message) {
tvResult.setText(message);
}
@Override
public String getUsername() {
return etUsername.getText().toString();
}
@Override
public String getPassword() {
return etPassword.getText().toString();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解除 Presenter 与 View 的关联(避免内存泄漏)
presenter.detachView();
}
}
3. Presenter 层(业务逻辑协调)
Presenter 作为中间层,协调 View 与 Model 的交互:
public class LoginPresenter {
// 持有 View 接口(弱引用避免内存泄漏)
private WeakReference<LoginView> viewRef;
// 持有 Model 实例
private LoginModel model;
// 构造函数:注入 View 和 Model
public LoginPresenter(LoginView view, LoginModel model) {
this.viewRef = new WeakReference<>(view);
this.model = model;
}
// 处理登录逻辑
public void login() {
LoginView view = viewRef.get();
if (view == null) return; // View 已销毁,直接返回
// 1. 显示加载
view.showLoading();
// 2. 获取 View 中的用户输入
String username = view.getUsername();
String password = view.getPassword();
User user = new User(username, password);
// 3. 调用 Model 处理登录
model.login(user, new LoginModel.LoginCallback() {
@Override
public void onSuccess(String message) {
// 切换到主线程更新 UI
if (viewRef.get() != null) {
viewRef.get().hideLoading();
viewRef.get().showResult(message);
}
}
@Override
public void onFailure(String error) {
if (viewRef.get() != null) {
viewRef.get().hideLoading();
viewRef.get().showResult(error);
}
}
});
}
// 解除 View 关联(Activity 销毁时调用)
public void detachView() {
if (viewRef != null) {
viewRef.clear();
viewRef = null;
}
}
}
4. 布局文件(activity_login.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/et_username"
android:hint="用户名"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/et_password"
android:hint="密码"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_login"
android:text="登录"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"/>
</LinearLayout>
MVP 的应用场景
- 中大型应用:业务逻辑复杂、页面交互多的场景(如电商 App 的商品详情、订单流程),通过分层降低维护成本。
- 需要频繁迭代的项目:View 与 Presenter 解耦,UI 改动(如换肤、改版)无需修改业务逻辑,反之亦然。
- 团队协作开发:职责分离后,可按模块分工(UI 开发者专注 View,逻辑开发者专注 Presenter/Model)。
优点
- 解耦彻底:View(Activity)仅处理 UI 逻辑,Presenter 处理业务逻辑,Model 处理数据,模块独立,便于维护。
- 可测试性强:Presenter 不依赖 Android 框架(通过 View 接口交互),可直接用 JUnit + Mockito 进行单元测试(无需 Android 环境)。
- 代码复用:Presenter 和 Model 可在不同 View 中复用(如登录逻辑可在 Activity 和 Fragment 中共享)。
- 生命周期管理更清晰:通过
detachView()方法解除关联,减少因 Activity 销毁导致的内存泄漏。
缺点
- 样板代码多:需定义大量接口(View 接口、回调接口),简单场景下显得冗余。
- Presenter 持有 View 引用风险:若未正确使用弱引用或
detachView(),可能导致 Activity 销毁后 Presenter 仍持有引用,引发内存泄漏。 - 状态恢复复杂:屏幕旋转等场景下,View 重建需手动恢复 Presenter 状态(需额外处理,如通过
onSaveInstanceState保存数据)。 - 学习成本:相比 MVC,需理解接口设计和层间交互,新手初期可能难以掌握。
总结
MVP 是对 MVC 的优化,通过引入 Presenter 层解决了 Activity 职责过重的问题,实现了 View 与业务逻辑的彻底解耦。其优点是可测试性强、代码复用率高,适合中大型项目;缺点是样板代码多、需注意内存泄漏风险。在实际开发中,可结合 Dagger(依赖注入)和 RxJava(异步处理)简化实现,进一步提升代码质量。
介绍2
MVVM(Model-View-ViewModel)是一种基于数据驱动和双向绑定的架构模式,通过 ViewModel 层隔离视图与业务逻辑,结合数据绑定(Data Binding)实现 UI 与数据的自动同步,是目前 Android 官方推荐的主流架构之一。以下从核心概念、代码示例、应用场景及优缺点展开详解:
MVVM 核心组件与数据流向
MVVM 彻底解耦了视图与业务逻辑,核心是数据驱动 UI,数据变化自动反映到视图,视图交互通过命令影响数据。
| 组件 | 职责 | Android 对应实现 |
|---|---|---|
| Model(模型) | 处理数据逻辑(网络请求、数据库操作、业务计算等),不依赖其他层。 | Java Bean、数据仓库(Repository)、数据源 |
| View(视图) | 展示数据、接收用户交互,通过数据绑定与 ViewModel 关联,无业务逻辑。 | Activity、Fragment、XML 布局(DataBinding) |
| ViewModel(视图模型) | 持有 UI 状态数据(如加载状态、列表数据),处理业务逻辑,通过 LiveData 或 Flow 暴露数据给 View,具有生命周期感知(与 Activity 生命周期解耦)。 | 继承 AndroidViewModel 或 ViewModel 的类 |
| DataBinding(数据绑定) | 连接 View 与 ViewModel 的桥梁,实现数据与 UI 的双向绑定(可选,但推荐使用)。 | XML 布局中的 @{} 表达式、绑定适配器 |
核心数据流向
用户交互 → View → ViewModel(处理逻辑)→ Model(数据处理)
↓
View(自动更新 UI)← LiveData/Flow ← ViewModel(更新数据)
Java 代码示例(登录功能)
以登录功能为例,展示 MVVM 各组件的协作(结合 DataBinding 和 LiveData):
1. 配置 DataBinding
在 app/build.gradle 中启用 DataBinding:
android {
...
dataBinding {
enabled = true
}
}
2. Model 层(数据与业务逻辑)
负责数据处理和业务验证:
// 数据模型:用户信息
public class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
// getter
public String getUsername() { return username; }
public String getPassword() { return password; }
}
// 登录仓库(处理数据请求)
public class LoginRepository {
// 模拟网络登录
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);
}
}
3. ViewModel 层(持有数据与逻辑)
持有 UI 状态,处理业务逻辑,通过 LiveData 暴露数据:
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;
private final LoginRepository repository;
public LoginViewModel() {
this.repository = new LoginRepository();
}
// 处理登录逻辑(供 View 调用)
public void login() {
String username = _username.getValue() != null ? _username.getValue() : "";
String password = _password.getValue() != null ? _password.getValue() : "";
// 显示加载状态
_isLoading.setValue(true);
_resultMessage.setValue("");
// 调用仓库登录
repository.login(new User(username, password), new LoginRepository.LoginCallback() {
@Override
public void onSuccess(String message) {
// 切换到主线程更新数据(LiveData.postValue() 自动处理线程)
_isLoading.postValue(false);
_resultMessage.postValue(message);
}
@Override
public void onFailure(String error) {
_isLoading.postValue(false);
_resultMessage.postValue(error);
}
});
}
// 更新用户名(供 View 绑定)
public void setUsername(String username) {
_username.setValue(username);
}
// 更新密码(供 View 绑定)
public void setPassword(String password) {
_password.setValue(password);
}
}
4. View 层(XML 布局 + Activity)
通过 DataBinding 绑定 ViewModel 数据,实现 UI 自动更新:
(1)XML 布局(activity_login.xml)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 变量声明:绑定 ViewModel -->
<data>
<variable
name="viewModel"
type="com.example.mvvm.LoginViewModel" />
<variable
name="activity"
type="com.example.mvvm.LoginActivity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<!-- 用户名输入框:双向绑定到 viewModel.username -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="用户名"
android:text="@={viewModel.username}" />
<!-- 密码输入框:双向绑定到 viewModel.password -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:inputType="textPassword"
android:text="@={viewModel.password}" />
<!-- 登录按钮:点击事件绑定到 activity 的 login 方法 -->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:onClick="@{() -> activity.login()}" />
<!-- 加载框:根据 isLoading 显示/隐藏 -->
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />
<!-- 结果文本:绑定到 resultMessage -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@{viewModel.resultMessage}" />
</LinearLayout>
</layout>
(2)Activity 代码(LoginActivity.java)
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import com.example.mvvm.databinding.ActivityLoginBinding;
public class LoginActivity extends AppCompatActivity {
private LoginViewModel viewModel;
private ActivityLoginBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化 DataBinding
binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
// 初始化 ViewModel
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
// 绑定生命周期所有者(使 LiveData 感知 Activity 生命周期)
binding.setLifecycleOwner(this);
// 绑定 ViewModel 到布局
binding.setViewModel(viewModel);
// 绑定 Activity 自身(供布局调用 login 方法)
binding.setActivity(this);
}
// 登录按钮点击事件(供布局调用)
public void login() {
viewModel.login();
}
}
MVVM 的应用场景
- 复杂交互场景:如表单提交(多字段验证)、列表分页加载、实时数据更新(如聊天、股票行情)等,数据绑定可减少大量 UI 更新代码。
- 需要状态持久化的场景:屏幕旋转、进程重建时,ViewModel 可保留数据(与 Activity 生命周期解耦),无需手动保存 / 恢复状态。
- 官方推荐架构:结合 Jetpack 组件(ViewModel、LiveData、Room、DataBinding)时,MVVM 是最自然的选择,适合现代 Android 开发。
- 大型团队协作:数据驱动和双向绑定使 UI 开发者与逻辑开发者可并行工作(UI 开发者专注布局,逻辑开发者专注 ViewModel)。
优点
- 彻底解耦:View 仅负责展示,ViewModel 处理逻辑,Model 管理数据,层间依赖清晰(View 依赖 ViewModel,ViewModel 依赖 Model,反之不成立)。
- 数据驱动 UI:通过 DataBinding 和 LiveData,数据变化自动更新 UI,无需手动调用
setText()、setVisibility()等方法,减少模板代码。 - 生命周期安全:ViewModel 与 Activity 生命周期解耦(不受旋转销毁影响),避免内存泄漏(不持有 View 引用)。
- 可测试性强:ViewModel 不依赖 Android 框架,可通过 JUnit 直接测试业务逻辑(无需 instrumentation 测试)。
- 状态管理简化:UI 状态(加载中、错误、空数据)统一由 ViewModel 管理,便于维护和扩展。
缺点
- 学习成本高:需掌握 DataBinding、LiveData、ViewModel 等概念,初期上手难度高于 MVC/MVP。
- 调试复杂度增加:数据绑定自动更新 UI,问题定位可能需要跟踪数据流向(如 LiveData 观察者链)。
- 不适合简单场景:静态页面或简单交互(如一个按钮跳转)使用 MVVM 会显得过度设计。
- DataBinding 潜在问题:复杂布局中的绑定表达式可能降低代码可读性;编译时错误提示不够友好。
总结
MVVM 是 Android 官方推荐的架构模式,核心优势是数据驱动 UI 和生命周期安全,通过 ViewModel 和 DataBinding 实现了视图与业务逻辑的彻底解耦,适合中大型复杂应用。虽然学习成本较高,但能显著提升代码可维护性和测试效率,是现代 Android 开发的主流选择。实际开发中,建议结合 Kotlin 协程、Flow 和 Jetpack Compose(声明式 UI)进一步简化实现。