rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • MVVM

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 的应用场景

  1. 中大型应用:业务逻辑复杂、页面交互多的场景(如电商 App 的商品详情、订单流程),通过分层降低维护成本。
  2. 需要频繁迭代的项目:View 与 Presenter 解耦,UI 改动(如换肤、改版)无需修改业务逻辑,反之亦然。
  3. 团队协作开发:职责分离后,可按模块分工(UI 开发者专注 View,逻辑开发者专注 Presenter/Model)。

优点

  1. 解耦彻底:View(Activity)仅处理 UI 逻辑,Presenter 处理业务逻辑,Model 处理数据,模块独立,便于维护。
  2. 可测试性强:Presenter 不依赖 Android 框架(通过 View 接口交互),可直接用 JUnit + Mockito 进行单元测试(无需 Android 环境)。
  3. 代码复用:Presenter 和 Model 可在不同 View 中复用(如登录逻辑可在 Activity 和 Fragment 中共享)。
  4. 生命周期管理更清晰:通过 detachView() 方法解除关联,减少因 Activity 销毁导致的内存泄漏。

缺点

  1. 样板代码多:需定义大量接口(View 接口、回调接口),简单场景下显得冗余。
  2. Presenter 持有 View 引用风险:若未正确使用弱引用或 detachView(),可能导致 Activity 销毁后 Presenter 仍持有引用,引发内存泄漏。
  3. 状态恢复复杂:屏幕旋转等场景下,View 重建需手动恢复 Presenter 状态(需额外处理,如通过 onSaveInstanceState 保存数据)。
  4. 学习成本:相比 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 的应用场景

  1. 复杂交互场景:如表单提交(多字段验证)、列表分页加载、实时数据更新(如聊天、股票行情)等,数据绑定可减少大量 UI 更新代码。
  2. 需要状态持久化的场景:屏幕旋转、进程重建时,ViewModel 可保留数据(与 Activity 生命周期解耦),无需手动保存 / 恢复状态。
  3. 官方推荐架构:结合 Jetpack 组件(ViewModel、LiveData、Room、DataBinding)时,MVVM 是最自然的选择,适合现代 Android 开发。
  4. 大型团队协作:数据驱动和双向绑定使 UI 开发者与逻辑开发者可并行工作(UI 开发者专注布局,逻辑开发者专注 ViewModel)。

优点

  1. 彻底解耦:View 仅负责展示,ViewModel 处理逻辑,Model 管理数据,层间依赖清晰(View 依赖 ViewModel,ViewModel 依赖 Model,反之不成立)。
  2. 数据驱动 UI:通过 DataBinding 和 LiveData,数据变化自动更新 UI,无需手动调用 setText()、setVisibility() 等方法,减少模板代码。
  3. 生命周期安全:ViewModel 与 Activity 生命周期解耦(不受旋转销毁影响),避免内存泄漏(不持有 View 引用)。
  4. 可测试性强:ViewModel 不依赖 Android 框架,可通过 JUnit 直接测试业务逻辑(无需 instrumentation 测试)。
  5. 状态管理简化:UI 状态(加载中、错误、空数据)统一由 ViewModel 管理,便于维护和扩展。

缺点

  1. 学习成本高:需掌握 DataBinding、LiveData、ViewModel 等概念,初期上手难度高于 MVC/MVP。
  2. 调试复杂度增加:数据绑定自动更新 UI,问题定位可能需要跟踪数据流向(如 LiveData 观察者链)。
  3. 不适合简单场景:静态页面或简单交互(如一个按钮跳转)使用 MVVM 会显得过度设计。
  4. DataBinding 潜在问题:复杂布局中的绑定表达式可能降低代码可读性;编译时错误提示不够友好。

总结

MVVM 是 Android 官方推荐的架构模式,核心优势是数据驱动 UI 和生命周期安全,通过 ViewModel 和 DataBinding 实现了视图与业务逻辑的彻底解耦,适合中大型复杂应用。虽然学习成本较高,但能显著提升代码可维护性和测试效率,是现代 Android 开发的主流选择。实际开发中,建议结合 Kotlin 协程、Flow 和 Jetpack Compose(声明式 UI)进一步简化实现。

资料

ViewModel+LiveData+DataBinding使用

最近更新:: 2025/10/28 00:02
Contributors: luokaiwen, 罗凯文