rokevin
移动
前端
语言
  • 基础

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

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

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

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

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

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

MVI

MVI(Model-View-Intent)是一种基于单向数据流和状态驱动的架构模式,起源于前端(如 Cycle.js),后被引入 Android 开发。其核心是通过唯一的状态流和意图(Intent) 实现组件间的解耦,适合复杂交互场景。以下从核心概念、代码示例、应用场景及优缺点展开详解:

MVI 核心组件与单向数据流

MVI 的核心是 “单向数据流”,数据在组件间按固定方向流动,状态变化可预测,便于调试和测试。

组件职责Android 对应实现
Model(模型)不仅包含数据,还包含UI 状态(State)(如加载中、成功、失败、表单数据等),是视图的唯一数据来源。数据类(如 LoginState)、仓库(Repository)
View(视图)被动展示 Model 中的 State,不包含业务逻辑;用户交互转化为 Intent 发送给 Presenter/ViewModel。Activity、Fragment、自定义 View
Intent(意图)封装用户交互行为(如点击登录、输入文本),是 View 向业务层传递指令的唯一方式。密封类(Sealed Class)或枚举(如 LoginIntent)
ViewModel(处理器)接收 Intent,处理业务逻辑(调用 Model 层),生成新的 State 并发送给 View。基于 LiveData 或 Flow 的 ViewModel

单向数据流图示

用户交互 → View → Intent → ViewModel → 处理逻辑(调用Model)→ 新State → View(更新UI)
                                       ↑                                  ↓
                                       └──────────────────────────────────┘
                                       (State闭环:View始终基于最新State渲染)

Java 代码示例(登录功能)

以登录功能为例,展示 MVI 各组件的协作(注:Java 中无密封类,用接口 + 实现类模拟 Intent):

1. Intent(用户意图封装)

定义用户可能的交互行为(点击登录、输入文本等):

// 意图基类
public interface LoginIntent {
    // 输入用户名
    class UsernameChanged implements LoginIntent {
        private final String username;
        public UsernameChanged(String username) { this.username = username; }
        public String getUsername() { return username; }
    }

    // 输入密码
    class PasswordChanged implements LoginIntent {
        private final String password;
        public PasswordChanged(String password) { this.password = password; }
        public String getPassword() { return password; }
    }

    // 点击登录按钮
    class LoginClicked implements LoginIntent {}
}

2. State(UI 状态模型)

封装 View 所需的所有状态(加载中、错误信息、表单数据等),是不可变对象(每次更新生成新实例):

public class LoginState {
    // 表单数据
    private final String username;
    private final String password;
    // 状态标识
    private final boolean isLoading;
    private final String errorMessage;
    private final boolean isLoginSuccess;

    // 构造函数(私有,通过Builder创建)
    private LoginState(String username, String password, boolean isLoading,
                       String errorMessage, boolean isLoginSuccess) {
        this.username = username;
        this.password = password;
        this.isLoading = isLoading;
        this.errorMessage = errorMessage;
        this.isLoginSuccess = isLoginSuccess;
    }

    // 初始状态(静态工厂方法)
    public static LoginState initial() {
        return new LoginState("", "", false, null, false);
    }

    // Builder模式(便于生成新状态)
    public static class Builder {
        private String username;
        private String password;
        private boolean isLoading;
        private String errorMessage;
        private boolean isLoginSuccess;

        public Builder from(LoginState state) { // 基于现有状态复制
            this.username = state.username;
            this.password = state.password;
            this.isLoading = state.isLoading;
            this.errorMessage = state.errorMessage;
            this.isLoginSuccess = state.isLoginSuccess;
            return this;
        }

        public Builder username(String username) {
            this.username = username;
            return this;
        }

        public Builder password(String password) {
            this.password = password;
            return this;
        }

        public Builder isLoading(boolean isLoading) {
            this.isLoading = isLoading;
            return this;
        }

        public Builder errorMessage(String errorMessage) {
            this.errorMessage = errorMessage;
            return this;
        }

        public Builder isLoginSuccess(boolean isLoginSuccess) {
            this.isLoginSuccess = isLoginSuccess;
            return this;
        }

        public LoginState build() {
            return new LoginState(username, password, isLoading, errorMessage, isLoginSuccess);
        }
    }

    // getter(无setter,确保不可变)
    public String getUsername() { return username; }
    public String getPassword() { return password; }
    public boolean isLoading() { return isLoading; }
    public String getErrorMessage() { return errorMessage; }
    public boolean isLoginSuccess() { return isLoginSuccess; }
}

3. Model 层(数据与业务逻辑)

包含数据仓库和业务处理(登录验证):

// 数据模型
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 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.onError("用户名或密码错误");
                }
            } catch (InterruptedException e) {
                callback.onError("网络异常");
            }
        }).start();
    }

    public interface LoginCallback {
        void onSuccess();
        void onError(String message);
    }
}

4. ViewModel(处理 Intent 并生成 State)

接收 Intent,处理逻辑,通过 LiveData 发送新 State 给 View:

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class LoginViewModel extends ViewModel {
    private final MutableLiveData<LoginState> stateLiveData = new MutableLiveData<>();
    private final LoginRepository repository;
    private LoginState currentState;

    public LoginViewModel() {
        this.repository = new LoginRepository();
        this.currentState = LoginState.initial(); // 初始状态
        stateLiveData.setValue(currentState);
    }

    // 暴露State流给View
    public LiveData<LoginState> getState() {
        return stateLiveData;
    }

    // 接收View发送的Intent
    public void processIntent(LoginIntent intent) {
        if (intent instanceof LoginIntent.UsernameChanged) {
            handleUsernameChanged(((LoginIntent.UsernameChanged) intent).getUsername());
        } else if (intent instanceof LoginIntent.PasswordChanged) {
            handlePasswordChanged(((LoginIntent.PasswordChanged) intent).getPassword());
        } else if (intent instanceof LoginIntent.LoginClicked) {
            handleLoginClicked();
        }
    }

    // 处理用户名输入
    private void handleUsernameChanged(String username) {
        currentState = new LoginState.Builder()
                .from(currentState)
                .username(username)
                .errorMessage(null) // 输入时清空错误
                .build();
        stateLiveData.setValue(currentState);
    }

    // 处理密码输入
    private void handlePasswordChanged(String password) {
        currentState = new LoginState.Builder()
                .from(currentState)
                .password(password)
                .errorMessage(null)
                .build();
        stateLiveData.setValue(currentState);
    }

    // 处理登录点击
    private void handleLoginClicked() {
        // 1. 显示加载中
        currentState = new LoginState.Builder()
                .from(currentState)
                .isLoading(true)
                .errorMessage(null)
                .isLoginSuccess(false)
                .build();
        stateLiveData.setValue(currentState);

        // 2. 调用仓库登录
        User user = new User(currentState.getUsername(), currentState.getPassword());
        repository.login(user, new LoginRepository.LoginCallback() {
            @Override
            public void onSuccess() {
                // 登录成功:更新状态
                currentState = new LoginState.Builder()
                        .from(currentState)
                        .isLoading(false)
                        .isLoginSuccess(true)
                        .build();
                stateLiveData.postValue(currentState); // 子线程用postValue
            }

            @Override
            public void onError(String message) {
                // 登录失败:更新状态
                currentState = new LoginState.Builder()
                        .from(currentState)
                        .isLoading(false)
                        .errorMessage(message)
                        .build();
                stateLiveData.postValue(currentState);
            }
        });
    }
}

5. View(Activity 渲染 UI 并发送 Intent)

观察 State 变化并渲染 UI,用户交互转化为 Intent 发送给 ViewModel:

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.widget.Toast;

public class LoginActivity extends AppCompatActivity {
    private EditText etUsername;
    private EditText etPassword;
    private Button btnLogin;
    private ProgressBar progressBar;
    private TextView tvError;
    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);

        // 观察State变化,更新UI
        viewModel.getState().observe(this, new Observer<LoginState>() {
            @Override
            public void onChanged(LoginState state) {
                updateUI(state);
            }
        });

        // 绑定用户交互到Intent
        etUsername.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                // 发送用户名变化Intent
                viewModel.processIntent(new LoginIntent.UsernameChanged(s.toString()));
            }
            // 其他方法省略
        });

        etPassword.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                // 发送密码变化Intent
                viewModel.processIntent(new LoginIntent.PasswordChanged(s.toString()));
            }
            // 其他方法省略
        });

        btnLogin.setOnClickListener(v -> {
            // 发送登录点击Intent
            viewModel.processIntent(new LoginIntent.LoginClicked());
        });
    }

    // 根据State更新UI
    private void updateUI(LoginState state) {
        // 更新输入框(可选,因输入框已双向绑定)
        etUsername.setText(state.getUsername());
        etPassword.setText(state.getPassword());
        // 显示/隐藏加载框
        progressBar.setVisibility(state.isLoading() ? View.VISIBLE : View.GONE);
        // 显示错误信息
        tvError.setText(state.getErrorMessage() != null ? state.getErrorMessage() : "");
        // 登录成功处理
        if (state.isLoginSuccess()) {
            Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    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);
        tvError = findViewById(R.id.tv_error);
    }
}

MVI 的应用场景

  1. 复杂交互场景:如表单提交(多输入验证)、购物车(增删改查 + 状态同步)等,状态变化频繁且需统一管理。
  2. 需要可预测性的应用:如金融类 App,状态变化需严格追踪,便于调试和问题定位。
  3. 团队协作项目:单向数据流和强类型约束(如 Intent、State)可规范代码风格,降低沟通成本。

优点

  1. 单向数据流:数据流向清晰(Intent→ViewModel→State→View),调试时可追踪完整状态变化链路。
  2. 状态驱动 UI:View 完全基于 State 渲染,避免手动更新 UI 导致的不一致问题(如 “忘记更新某个控件”)。
  3. 可测试性强:ViewModel 处理逻辑与 View 解耦,State 和 Intent 是纯数据类,可通过单元测试验证状态转换。
  4. 状态可复现:State 包含 UI 完整信息,便于实现 “状态保存与恢复”(如屏幕旋转、进程重建)。

缺点

  1. 样板代码多:需定义大量 Intent、State 类,简单场景下显得冗余(Java 中尤为明显,Kotlin 可通过数据类简化)。
  2. 内存开销:每次状态更新都会创建新的 State 对象(不可变特性),频繁更新可能增加内存消耗。
  3. 学习成本高:相比 MVC/MVP,需要理解 “单向数据流”“不可变状态” 等概念,初期上手较难。
  4. 不适合简单场景:如静态页面、简单列表展示,使用 MVI 会过度设计。

总结

MVI 是一种以状态为核心的架构模式,通过单向数据流实现了 UI 与业务逻辑的解耦,适合复杂交互场景。其优点是状态可预测、可测试性强,缺点是样板代码多、学习成本高。在 Android 开发中,推荐结合 Kotlin 协程和 Flow 使用(简化状态流处理),Java 中因语法限制会略显繁琐,但核心思想仍可借鉴

资料

MVI-hannesdorfmann
MVI-简书
MVI-简书2
Android最佳架构:MVI + LiveData + ViewModel | ProAndroidDev

最近更新:: 2025/10/28 00:02
Contributors: luokaiwen