在 Android 开发初期,很多开发者会把所有逻辑塞进 Activity—— 网络请求、数据处理、UI 更新全堆在一起,导致代码超过数千行,改一个按钮点击都要翻半天。这种 “面条式代码” 的根源是缺乏架构设计。随着应用复杂度提升,MVC、MVP、MVVM 三种架构逐渐成为主流,它们通过 “分层设计” 解决代码耦合问题。本文将从核心思想、代码实现到适用场景,全面解析这三种架构的设计逻辑,帮你找到适合项目的架构方案。
一、架构设计的核心目标
无论哪种架构,最终目的都是解决三个核心问题:
- 解耦:分离 UI、业务逻辑、数据处理,避免 “改一处动全身”;
- 可测试:业务逻辑可独立于 UI 测试(如无需启动 Activity 就能测试登录逻辑);
- 可维护:分层清晰,新人能快速定位代码位置(如 “UI 相关找 View 层,网络请求找 Model 层”)。
形象比喻:架构就像 “餐厅分工”—— 厨师(Model)负责做菜(数据处理),服务员(Presenter/ViewModel)负责传递需求(业务逻辑),顾客(View)只负责点餐(UI 交互),各司其职才高效。
二、MVC 架构:最基础的分层思想
MVC(Model-View-Controller)是最早普及的分层架构,核心是 “将数据、UI、逻辑分离”。在 Android 中,MVC 的实现有其特殊性 —— 因 Activity 同时承担部分 View 和 Controller 职责,与传统 MVC 略有差异。
2.1 MVC 核心结构与职责
层级 |
核心职责 |
Android 中的载体 |
示例操作 |
Model |
数据管理(网络请求、数据库、实体) |
JavaBean、Repository、Dao |
调用登录接口、从数据库查用户信息 |
View |
展示 UI、接收用户输入 |
XML 布局、Activity(部分)、View |
显示登录按钮、输入用户名密码 |
Controller |
处理业务逻辑、协调 Model 和 View |
Activity(主要)、Fragment |
点击登录后调用 Model 校验,通知 View 显示结果 |
2.2 Android MVC 的实现(登录案例)
以 “登录功能” 为例,MVC 的代码结构如下:
(1)Model 层:数据与数据处理
// 1. 数据实体(User.java)
public class User {
private String username;
private String password;
// 构造方法、getter、setter
}
// 2. 数据处理(登录接口调用,LoginModel.java)
public class LoginModel {
// 模拟网络请求
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.onFail("用户名或密码错误");
}
} catch (InterruptedException e) {
callback.onFail("网络异常");
}
}).start();
}
// 回调接口(Model通知Controller结果)
public interface LoginCallback {
void onSuccess(String msg);
void onFail(String msg);
}
}
(2)View 层:UI 展示(XML 布局)
<!-- activity_login.xml -->
<LinearLayout>
<EditText
android:id="@+id/et_username"
hint="用户名"/>
<EditText
android:id="@+id/et_password"
hint="密码"
inputType="textPassword"/>
<Button
android:id="@+id/btn_login"
text="登录"/>
<TextView
android:id="@+id/tv_result"/>
</LinearLayout>
(3)Controller 层:逻辑协调(Activity)
public class LoginActivity extends AppCompatActivity implements LoginModel.LoginCallback {
private EditText etUsername;
private EditText etPassword;
private TextView tvResult;
private LoginModel loginModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 初始化View
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
tvResult = findViewById(R.id.tv_result);
loginModel = new LoginModel();
// 登录按钮点击(用户输入触发Controller)
findViewById(R.id.btn_login).setOnClickListener(v -> {
String username = etUsername.getText().toString();
String password = etPassword.getText().toString();
// 调用Model处理数据
loginModel.login(new User(username, password), this);
});
}
// Model回调:更新UI(Controller通知View)
@Override
public void onSuccess(String msg) {
runOnUiThread(() -> tvResult.setText(msg));
}
@Override
public void onFail(String msg) {
runOnUiThread(() -> tvResult.setText(msg));
}
}
2.3 MVC 的优缺点与适用场景
优势:
- 简单直观:无需额外类和接口,新手易上手;
- 开发快速:适合小型项目(如工具类 APP),无需复杂设计。
缺陷:
- Activity 职责过重:既做 Controller(逻辑)又做 View(UI),代码易膨胀(上千行很常见);
- 耦合度较高:View 和 Controller 通过 Activity 强耦合,难以单独测试(测登录逻辑需启动 Activity);
- 复用性差:逻辑与 Activity 绑定,换个界面(如从 Activity 换成 Dialog)需重写逻辑。
适用场景:
- 小型项目(如单个 Activity 的工具 APP);
- 快速原型开发(需快速验证功能,不考虑长期维护)。
三、MVP 架构:解耦 View 与逻辑的中间层
MVP(Model-View-Presenter)是为解决 MVC 中 “Activity 职责过重” 而诞生的架构。其核心是引入Presenter 作为中间层,彻底分离 View(UI)和业务逻辑,让 Activity 只专注于 UI 展示。
3.1 MVP 核心结构与职责
MVP 在 MVC 基础上拆分出 Presenter,各层职责更清晰:
层级 |
核心职责 |
Android 中的载体 |
核心交互 |
Model |
数据管理(与 MVC 一致) |
JavaBean、Repository |
登录接口调用、数据校验 |
View |
纯 UI 层(展示、用户输入) |
Activity、Fragment、XML 布局 |
显示加载框、暴露更新 UI 的方法 |
Presenter |
业务逻辑核心、协调 Model 和 View |
Presenter 类(独立于 Android 框架) |
接收 View 的登录请求→调用 Model→通知 View 更新 |
核心改进:
- View 与 Presenter 通过接口交互(View 只暴露 UI 方法,不包含逻辑);
- Presenter 完全独立于 Android 框架(不持有 Activity 上下文),可单独测试。
3.2 Android MVP 的实现(登录案例)
同样以登录功能为例,MVP 通过 “接口定义交互” 实现解耦:
(1)Model 层:与 MVC 一致(复用 LoginModel)
// 复用MVC中的LoginModel和User,无需修改
public class LoginModel {
public void login(User user, LoginCallback callback) { ... }
// 回调接口
public interface LoginCallback { ... }
}
(2)View 层:定义 UI 接口 + 实现
// 1. View接口(定义UI操作,与Presenter交互)
public interface LoginView {
// 显示加载状态
void showLoading();
// 隐藏加载状态
void hideLoading();
// 更新登录结果
void showResult(String msg);
// 获取用户输入
String getUsername();
String getPassword();
}
// 2. View实现(Activity只做UI,不处理逻辑)
public class LoginActivity extends AppCompatActivity implements LoginView {
private EditText etUsername;
private EditText etPassword;
private TextView tvResult;
private ProgressDialog loadingDialog;
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 初始化UI
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
tvResult = findViewById(R.id.tv_result);
loadingDialog = new ProgressDialog(this);
loadingDialog.setMessage("登录中...");
// 创建Presenter,传入View接口
presenter = new LoginPresenter(this);
// 登录按钮点击(View只通知Presenter,不处理逻辑)
findViewById(R.id.btn_login).setOnClickListener(v -> presenter.login());
}
// 实现LoginView接口的UI方法
@Override
public void showLoading() {
loadingDialog.show();
}
@Override
public void hideLoading() {
loadingDialog.dismiss();
}
@Override
public void showResult(String msg) {
tvResult.setText(msg);
}
@Override
public String getUsername() {
return etUsername.getText().toString();
}
@Override
public String getPassword() {
return etPassword.getText().toString();
}
// 生命周期管理:避免内存泄漏
@Override
protected void onDestroy() {
super.onDestroy();
presenter.detachView(); // 断开Presenter与View的引用
}
}
(3)Presenter 层:逻辑核心
public class LoginPresenter {
// 持有View接口(而非具体Activity)和Model
private LoginView loginView;
private LoginModel loginModel;
// 弱引用(避免Presenter持有Activity导致内存泄漏)
private WeakReference<LoginView> viewRef;
// 构造方法:关联View和Model
public LoginPresenter(LoginView view) {
this.viewRef = new WeakReference<>(view);
this.loginModel = new LoginModel();
}
// 登录逻辑(核心)
public void login() {
LoginView view = viewRef.get();
if (view == null) return;
// 1. 通知View显示加载
view.showLoading();
// 2. 获取View的输入数据
String username = view.getUsername();
String password = view.getPassword();
// 3. 调用Model处理登录
loginModel.login(new User(username, password), new LoginModel.LoginCallback() {
@Override
public void onSuccess(String msg) {
// 4. 通知View更新结果
if (viewRef.get() != null) {
viewRef.get().hideLoading();
viewRef.get().showResult(msg);
}
}
@Override
public void onFail(String msg) {
if (viewRef.get() != null) {
viewRef.get().hideLoading();
viewRef.get().showResult(msg);
}
}
});
}
// 断开View引用(避免内存泄漏)
public void detachView() {
if (viewRef != null) {
viewRef.clear();
viewRef = null;
}
}
}
3.3 MVP 的核心改进与优缺点
核心改进:
- 完全解耦:View 只做 UI,Presenter 只做逻辑,修改 UI 不影响逻辑;
- 可测试性:Presenter 不依赖 Android 框架,可通过 JUnit 直接测试(无需启动 APP);
// 测试Presenter(纯Java测试,不依赖Android) public class LoginPresenterTest { @Test public void testLoginSuccess() { // 模拟View LoginView mockView = Mockito.mock(LoginView.class); // 模拟输入 Mockito.when(mockView.getUsername()).thenReturn("admin"); Mockito.when(mockView.getPassword()).thenReturn("123456"); LoginPresenter presenter = new LoginPresenter(mockView); presenter.login(); // 验证逻辑:是否调用了显示加载和隐藏加载 Mockito.verify(mockView).showLoading(); Mockito.verify(mockView).hideLoading(); Mockito.verify(mockView).showResult("登录成功"); } }
- 复用性提升:Presenter 可搭配不同 View(如用同一 LoginPresenter 支持 Activity 和 Fragment)。
缺陷:
- 代码量增加:需定义大量接口(View 接口、回调),简单功能也需多个类;
- 生命周期管理复杂:Presenter 需手动处理 View 的生命周期(如detachView),否则易内存泄漏;
- 接口冗余:View 接口可能定义大量方法(如 10 个 UI 更新方法),维护成本高。
适用场景:
- 中型项目(如多模块应用,需团队协作);
- 需频繁迭代的项目(逻辑与 UI 分离,便于维护);
- 对测试有要求的项目(需单元测试覆盖核心逻辑)。
四、MVVM 架构:数据驱动 UI 的响应式设计
MVVM(Model-View-ViewModel)是当前 Android 主流架构,借助 “数据绑定(DataBinding)” 和 “响应式数据(如 LiveData)” 实现 “数据驱动 UI”——UI 自动响应数据变化,无需手动调用更新方法。
4.1 MVVM 核心结构与职责
MVVM 的核心是ViewModel 与 View 的数据绑定,各层职责如下:
层级 |
核心职责 |
Android 中的载体 |
核心交互 |
Model |
数据管理(与前两种架构一致) |
JavaBean、Repository、Room |
登录接口、数据库操作 |
View |
UI 层(自动响应数据变化) |
Activity、Fragment、XML+DataBinding |
声明式绑定数据,无需手动更新 |
ViewModel |
持有可观察数据、处理业务逻辑 |
ViewModel(Jetpack 组件) |
调用 Model 获取数据→更新 LiveData→View 自动刷新 |
核心优势:
- 数据与 UI 通过 DataBinding 绑定,省去大量setText等更新代码;
- ViewModel 生命周期独立于 Activity(屏幕旋转时不重建),数据自动保留;
- 响应式编程(LiveData 自动通知数据变化),逻辑更清晰。
4.2 Android MVVM 的实现(登录案例)
结合 Jetpack 组件(ViewModel、LiveData、DataBinding)实现登录功能:
(1)Model 层:数据与仓库(引入 Repository 模式)
// 1. 数据实体(User.java)
public class User { ... }
// 2. 数据源(登录接口,LoginDataSource.java)
public class LoginDataSource {
public void login(User user, LoginCallback callback) {
// 模拟网络请求(与MVP的Model一致)
new Thread(() -> {
try {
Thread.sleep(1000);
if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) {
callback.onSuccess("登录成功");
} else {
callback.onFail("用户名或密码错误");
}
} catch (InterruptedException e) {
callback.onFail("网络异常");
}
}).start();
}
public interface LoginCallback { ... }
}
// 3. 仓库(统一管理数据源,LoginRepository.java)
public class LoginRepository {
private static LoginRepository instance;
private LoginDataSource dataSource;
// 单例仓库(可同时管理网络和本地数据源)
public static LoginRepository getInstance() {
if (instance == null) {
instance = new LoginRepository(new LoginDataSource());
}
return instance;
}
private LoginRepository(LoginDataSource dataSource) {
this.dataSource = dataSource;
}
// 暴露登录接口给ViewModel
public void login(User user, LoginDataSource.LoginCallback callback) {
dataSource.login(user, callback);
}
}
(2)ViewModel 层:持有 LiveData 与逻辑
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class LoginViewModel extends ViewModel {
// 可观察数据(登录结果,View会自动监听)
private MutableLiveData<String> loginResult = new MutableLiveData<>();
// 加载状态
private MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
// 仓库引用
private LoginRepository repository;
public LoginViewModel() {
repository = LoginRepository.getInstance();
}
// 暴露给View的只读LiveData(防止View直接修改)
public LiveData<String> getLoginResult() {
return loginResult;
}
public LiveData<Boolean> getIsLoading() {
return isLoading;
}
// 登录逻辑
public void login(String username, String password) {
isLoading.setValue(true); // 通知加载开始
User user = new User(username, password);
repository.login(user, new LoginDataSource.LoginCallback() {
@Override
public void onSuccess(String msg) {
isLoading.postValue(false); // 子线程用postValue
loginResult.postValue(msg);
}
@Override
public void onFail(String msg) {
isLoading.postValue(false);
loginResult.postValue(msg);
}
});
}
}
(3)View 层:DataBinding 绑定数据
<!-- activity_login.xml(启用DataBinding) -->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 数据变量定义 -->
<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">
<EditText
android:id="@+id/et_username"
android:hint="用户名"/>
<EditText
android:id="@+id/et_password"
android:hint="密码"
android:inputType="textPassword"/>
<Button
android:text="登录"
android:onClick="@{() -> activity.login()}"/>
<TextView
android:text="@{viewModel.loginResult}" /> <!-- 自动绑定结果 -->
<ProgressBar
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" /> <!-- 自动绑定加载状态 -->
</LinearLayout>
</layout>
(4)Activity:关联 ViewModel 与 DataBinding
public class LoginActivity extends AppCompatActivity {
private ActivityLoginBinding binding; // DataBinding自动生成的类
private LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 1. 初始化DataBinding
binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
// 2. 获取ViewModel(通过ViewModelProvider,确保生命周期正确)
loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class);
// 3. 绑定ViewModel到布局
binding.setViewModel(loginViewModel);
binding.setActivity(this);
// 绑定生命周期所有者(让LiveData感知Activity生命周期)
binding.setLifecycleOwner(this);
}
// 登录按钮点击(调用ViewModel的登录方法)
public void login() {
String username = binding.etUsername.getText().toString();
String password = binding.etPassword.getText().toString();
loginViewModel.login(username, password);
}
}
4.3 MVVM 的核心优势与优缺点
核心优势:
- 数据驱动 UI:通过 LiveData+DataBinding,数据变化自动更新 UI,省去runOnUiThread和setText;
- 生命周期安全:ViewModel 在屏幕旋转时不重建(数据保留),避免重复请求网络;
- 低耦合:View 只绑定数据,ViewModel 只处理逻辑,Model 只管数据,修改 UI 不影响逻辑;
- 可测试性:ViewModel 独立于 Android 框架,可直接测试(如验证登录逻辑是否正确更新 LiveData)。
缺陷:
- 学习成本高:需掌握 DataBinding、LiveData、ViewModel 等 Jetpack 组件;
- 调试难度增加:数据绑定是黑盒操作,UI 异常时需排查绑定关系;
- 简单功能冗余:小功能(如单个按钮)用 MVVM 显得繁琐。
适用场景:
- 大型项目(如电商 APP、社交 APP,需长期维护);
- 频繁更新 UI 的场景(如列表刷新、实时数据展示);
- 团队协作项目(架构规范统一,新人易接手)。
五、三种架构对比与选择指南
维度 |
MVC |
MVP |
MVVM |
核心思想 |
分层但 View 与 Controller 耦合 |
Presenter 中间层解耦 |
数据绑定 + 响应式数据驱动 |
代码量 |
少(无额外接口) |
中(需定义 View 接口) |
多(需 ViewModel 和绑定) |
耦合度 |
高(Activity 承担多重职责) |
中(接口交互,需手动管理) |
低(数据绑定,自动响应) |
可测试性 |
低(需依赖 Activity) |
高(Presenter 可独立测试) |
高(ViewModel 独立测试) |
维护成本 |
高(后期改不动) |
中(接口清晰但需维护) |
低(分层明确,数据驱动) |
Android 适配 |
原生支持(简单但粗糙) |
需手动实现接口和生命周期管理 |
依赖 Jetpack(官方推荐) |
5.1 架构选择建议
- 按项目规模选择:
- 小型项目(<5 个 Activity)→ MVC(快速开发);
- 中型项目(5-20 个页面)→ MVP(平衡开发效率和维护性);
- 大型项目(>20 个页面)→ MVVM(长期维护,团队协作)。
- 按团队情况选择:
- 新手团队→ MVC(降低学习成本);
- 有经验团队→ MVVM(利用 Jetpack 提升效率)。
- 按功能复杂度选择:
- 简单功能(如设置页面)→ MVC 或 MVP;
- 复杂功能(如首页列表 + 购物车 + 实时消息)→ MVVM。
六、架构设计的本质:灵活应变
无论 MVC、MVP 还是 MVVM,都不是 “银弹”。实际开发中不必严格遵守某一种架构,可根据需求混合使用:
- 小型项目用 MVC,但抽取工具类减少 Activity 代码;
- MVP 中引入 DataBinding 简化 View 更新;
- MVVM 中保留 Presenter 的部分逻辑(如复杂表单校验)。
架构的本质是 “解决当前问题”—— 能让团队高效开发、代码易于维护的就是好架构。随着项目演进,架构也可逐步升级(如从 MVC 重构为 MVVM),关键是保持 “分层清晰、职责单一” 的核心原则。
掌握这三种架构后,你会发现:优秀的 Android 代码不是 “堆功能”,而是通过合理设计让每一行代码都有明确的位置和职责 —— 这也是从 “会写代码” 到 “能设计系统” 的关键一步。