一、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