MVC、MVP、MVVM

一、Android 中的 MVC

1. Android MVC 分层

在 Android 里,MVC 大致可以这样对应:

MVC 角色 Android 中常见对应
Model 数据层,例如 Bean、Repository、网络请求、数据库操作
View XML 布局文件
Controller Activity / Fragment

例如:

UserActivity
 ├── 负责展示页面
 ├── 负责监听按钮点击
 ├── 负责请求网络
 ├── 负责处理数据
 ├── 负责更新 UI

这其实就是早期 Android 很常见的写法。


2. Android MVC 示例

比如登录页面:

public class LoginActivity extends AppCompatActivity {

    private EditText etUsername;
    private EditText etPassword;
    private Button btnLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        etUsername = findViewById(R.id.etUsername);
        etPassword = findViewById(R.id.etPassword);
        btnLogin = findViewById(R.id.btnLogin);

        btnLogin.setOnClickListener(v -> {
            String username = etUsername.getText().toString();
            String password = etPassword.getText().toString();

            // 业务逻辑
            if (username.isEmpty() || password.isEmpty()) {
                Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
                return;
            }

            // 网络请求
            login(username, password);
        });
    }

    private void login(String username, String password) {
        // 请求接口
        // 更新 UI
    }
}

这种写法很直观,但是问题也明显:

  • Activity 太臃肿;
  • UI 逻辑、业务逻辑、网络请求混在一起;
  • 不方便单元测试;
  • 后期维护困难。

3. Android MVC 的问题

Android 中的 Activity / Fragment 本身就很特殊:

它们既负责:

  • 生命周期;
  • UI 展示;
  • 用户交互;
  • 页面跳转;
  • 权限处理;
  • 数据恢复;
  • 系统回调。

如果再把业务逻辑也写进去,就会导致 Activity / Fragment 变成巨大的类。

所以 Android 中的 MVC 很容易变成:

Model + XML + 巨大的 Activity

这也是为什么后来 Android 开发中 MVP 曾经非常流行。


二、Android 中的 MVP

1. Android MVP 分层

MVP 在 Android 中非常经典。

MVP 角色 Android 中常见对应
Model Repository、网络请求、数据库、数据实体
View Activity / Fragment,实现 View 接口
Presenter 业务逻辑层,负责连接 View 和 Model

MVP 的核心:

Activity / Fragment 只负责 UI 展示,业务逻辑交给 Presenter。


2. Android MVP 通信方式

流程一般是:

用户点击按钮
        ↓
Activity / Fragment 调用 Presenter
        ↓
Presenter 调用 Model 请求数据
        ↓
Model 返回数据
        ↓
Presenter 处理结果
        ↓
Presenter 调用 View 接口更新 UI

也就是:

View <——> Presenter <——> Model

View 和 Model 不直接通信。


3. Android MVP 示例

View 接口

public interface LoginView {
    void showLoading();
    void hideLoading();
    void showLoginSuccess();
    void showLoginError(String message);
}

Presenter

public class LoginPresenter {

    private LoginView view;
    private UserRepository repository;

    public LoginPresenter(LoginView view) {
        this.view = view;
        this.repository = new UserRepository();
    }

    public void login(String username, String password) {
        if (username.isEmpty() || password.isEmpty()) {
            view.showLoginError("用户名或密码不能为空");
            return;
        }

        view.showLoading();

        repository.login(username, password, new Callback() {
            @Override
            public void onSuccess() {
                view.hideLoading();
                view.showLoginSuccess();
            }

            @Override
            public void onError(String message) {
                view.hideLoading();
                view.showLoginError(message);
            }
        });
    }

    public void detachView() {
        view = null;
    }
}

Activity 作为 View

public class LoginActivity extends AppCompatActivity implements LoginView {

    private LoginPresenter presenter;
    private EditText etUsername;
    private EditText etPassword;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        presenter = new LoginPresenter(this);

        etUsername = findViewById(R.id.etUsername);
        etPassword = findViewById(R.id.etPassword);

        findViewById(R.id.btnLogin).setOnClickListener(v -> {
            String username = etUsername.getText().toString();
            String password = etPassword.getText().toString();

            presenter.login(username, password);
        });
    }

    @Override
    public void showLoading() {
        // 显示加载框
    }

    @Override
    public void hideLoading() {
        // 隐藏加载框
    }

    @Override
    public void showLoginSuccess() {
        Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showLoginError(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.detachView();
    }
}

4. MVP 的优点

在 Android 中,MVP 的优点是:

  • Activity / Fragment 代码变少;
  • 业务逻辑放到 Presenter;
  • View 和 Model 解耦;
  • Presenter 可以单独测试;
  • 结构比 MVC 清晰。

5. MVP 的缺点

但是 MVP 也有明显缺点:

1. 接口类太多

每个页面可能都要写:

LoginContract.View
LoginContract.Presenter
LoginPresenter
LoginActivity

代码量增加。


2. Presenter 容易臃肿

复杂页面中,Presenter 会负责:

  • 参数校验;
  • 请求数据;
  • 处理回调;
  • 控制 Loading;
  • 控制 Toast;
  • 控制页面状态;
  • 调用 View 更新 UI。

久了之后 Presenter 也会变成“大胖类”。


3. 容易内存泄漏

Presenter 持有 Activity 引用:

private LoginView view;

如果没有在 onDestroy() 中释放,就可能导致 Activity 泄漏。

所以通常要写:

presenter.detachView();

三、Android 中的 MVVM

现在 Android 官方更推荐 MVVM,尤其是配合 Jetpack 使用。


1. Android MVVM 分层

MVVM 角色 Android 中常见对应
Model Repository、Room、Retrofit、DataStore、数据实体
View Activity / Fragment / XML / Compose
ViewModel AndroidX ViewModel

Android 中的 MVVM 通常是:

Activity / Fragment
        ↓ 观察数据
ViewModel
        ↓ 调用
Repository
        ↓
网络 / 数据库 / 本地缓存

2. Android MVVM 核心思想

Activity / Fragment 只做 UI 相关的事情:

  • 初始化 View;
  • 监听点击事件;
  • 观察 ViewModel 数据;
  • 根据数据更新 UI。

ViewModel 负责:

  • 保存页面状态;
  • 处理业务逻辑;
  • 调用 Repository 获取数据;
  • 暴露 LiveData / StateFlow 给页面观察。

Repository 负责:

  • 网络请求;
  • 数据库操作;
  • 缓存策略;
  • 数据来源统一管理。

四、Android MVVM 典型结构

一个常见结构是:

ui
 └── login
      ├── LoginActivity.kt
      └── LoginViewModel.kt

data
 ├── model
 │    └── User.kt
 ├── repository
 │    └── UserRepository.kt
 ├── remote
 │    └── ApiService.kt
 └── local
      └── UserDao.kt

五、Android MVVM 示例,Kotlin 版

1. Repository

class UserRepository {

    suspend fun login(username: String, password: String): Result<User> {
        return try {
            // 模拟网络请求
            if (username == "admin" && password == "123456") {
                Result.success(User("1", "admin"))
            } else {
                Result.failure(Exception("用户名或密码错误"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

2. ViewModel

class LoginViewModel : ViewModel() {

    private val repository = UserRepository()

    private val _loginState = MutableLiveData<LoginState>()
    val loginState: LiveData<LoginState> = _loginState

    fun login(username: String, password: String) {
        if (username.isBlank() || password.isBlank()) {
            _loginState.value = LoginState.Error("用户名或密码不能为空")
            return
        }

        _loginState.value = LoginState.Loading

        viewModelScope.launch {
            val result = repository.login(username, password)

            _loginState.value = result.fold(
                onSuccess = { LoginState.Success(it) },
                onFailure = { LoginState.Error(it.message ?: "登录失败") }
            )
        }
    }
}

3. UI 状态类

sealed class LoginState {
    object Loading : LoginState()
    data class Success(val user: User) : LoginState()
    data class Error(val message: String) : LoginState()
}

4. Activity

class LoginActivity : AppCompatActivity() {

    private val viewModel by viewModels<LoginViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        val etUsername = findViewById<EditText>(R.id.etUsername)
        val etPassword = findViewById<EditText>(R.id.etPassword)
        val btnLogin = findViewById<Button>(R.id.btnLogin)

        btnLogin.setOnClickListener {
            val username = etUsername.text.toString()
            val password = etPassword.text.toString()

            viewModel.login(username, password)
        }

        viewModel.loginState.observe(this) { state ->
            when (state) {
                is LoginState.Loading -> {
                    // 显示 Loading
                }

                is LoginState.Success -> {
                    Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
                    // 跳转首页
                }

                is LoginState.Error -> {
                    Toast.makeText(this, state.message, Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

六、Android MVVM 的数据流

MVVM 中数据流一般是单向的:

用户操作
   ↓
View 调用 ViewModel 方法
   ↓
ViewModel 调用 Repository
   ↓
Repository 获取数据
   ↓
ViewModel 更新 LiveData / StateFlow
   ↓
View 观察数据并刷新 UI

也就是:

View → ViewModel → Model
View ← ViewModel ← Model

View 不直接操作 Model。

Model 也不直接操作 View。


七、Android 中 ViewModel 的作用

Android 的 ViewModel 不只是普通的 ViewModel,它还有一个很重要的特点:

ViewModel 可以感知生命周期,配置变化时不会轻易销毁。

比如手机旋转屏幕时:

  • Activity 会销毁并重建;
  • ViewModel 可以保留下来;
  • 页面数据不会丢失。

例如:

private val viewModel by viewModels<LoginViewModel>()

这个 ViewModel 会跟随 Activity 生命周期管理。


八、Android MVVM 常用组件

Android MVVM 通常会配合 Jetpack 组件使用:

组件 作用
ViewModel 保存 UI 状态,处理页面逻辑
LiveData 可观察数据,生命周期安全
StateFlow Kotlin 协程中的状态流,现代项目更常用
Room 本地数据库
Retrofit 网络请求
Repository 统一管理数据来源
DataBinding XML 和数据绑定
ViewBinding 安全访问 View
Navigation 页面导航
Hilt 依赖注入
Paging 分页加载
WorkManager 后台任务

九、LiveData 和 StateFlow

现在 Android MVVM 中常见两种状态暴露方式:

1. LiveData

适合传统 Android 项目:

private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user

Activity 中观察:

viewModel.user.observe(this) { user ->
    // 更新 UI
}

2. StateFlow

现代 Kotlin 项目更推荐:

private val _uiState = MutableStateFlow(LoginState())
val uiState: StateFlow<LoginState> = _uiState

Activity / Fragment 中收集:

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state ->
            // 更新 UI
        }
    }
}

十、Android 中 MVVM 和 DataBinding 的关系

很多人会误以为:

MVVM = DataBinding

其实不是。

MVVM 是架构思想。

DataBinding 只是实现 MVVM 的一种工具。

在 Android 中,你可以:

  • MVVM + DataBinding;
  • MVVM + ViewBinding;
  • MVVM + Jetpack Compose;
  • MVVM + LiveData;
  • MVVM + StateFlow。

它们都可以。


十一、Android 三种模式对比

架构 Android 中 View 是谁 业务逻辑在哪里 优点 缺点
MVC XML + Activity Activity / Fragment 简单直接 Activity 容易臃肿
MVP Activity / Fragment Presenter 解耦清晰,方便测试 接口多,Presenter 臃肿
MVVM Activity / Fragment / Compose ViewModel 生命周期友好,数据驱动,官方推荐 需要理解 Jetpack 和状态管理

十二、面试中可以这样回答

如果面试官问:

Android 中 MVC、MVP、MVVM 怎么理解?

可以这样回答:

在 Android 中,MVC 通常把 XML 当作 View,数据实体和数据操作作为 Model,而 Activity 或 Fragment 充当 Controller。但由于 Activity 本身还要处理生命周期、UI 事件和系统回调,所以如果再把业务逻辑写进去,很容易导致 Activity 变得臃肿。

MVP 是为了解决这个问题,把 Activity 或 Fragment 作为 View,只负责 UI 展示和用户交互;Presenter 负责业务逻辑和调用 Model;Model 负责数据来源。View 和 Model 不直接通信,而是通过 Presenter 中转。这样解耦更好,也方便测试,但缺点是接口类较多,Presenter 容易变大,而且 Presenter 持有 View 引用时需要注意内存泄漏。

MVVM 是现在 Android 更推荐的架构。Activity 或 Fragment 作为 View,只负责观察状态和更新 UI;ViewModel 保存页面状态、处理业务逻辑并调用 Repository;Repository 负责统一管理网络、数据库和缓存。ViewModel 通过 LiveData 或 StateFlow 暴露数据,View 观察数据变化后自动刷新 UI。它相比 MVP 更适合 Android 生命周期,也更符合 Jetpack 的设计思想。


十三、简短版

Android 中:

MVC:
XML 是 View,Activity/Fragment 是 Controller,Model 是数据层。
问题是 Activity 很容易变成大杂烩。

MVP:
Activity/Fragment 是 View,Presenter 处理业务逻辑,Model 处理数据。
优点是解耦,缺点是 Presenter 和接口类容易很多。

MVVM:
Activity/Fragment 是 View,ViewModel 保存状态和处理逻辑,Repository 负责数据。
View 通过 LiveData/StateFlow 观察 ViewModel。
这是现在 Android 官方更推荐的方式。

如果是现代 Android 项目,推荐:

MVVM + ViewModel + StateFlow/LiveData + Repository + Retrofit + Room + Hilt

如果使用 Jetpack Compose,则更常见:

Compose UI + ViewModel + StateFlow + Repository
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇