第一章:架构演进史:从MVC到MVP再到MVVM,为什么我们要不断重构?
说实话,我入行那会儿,Android开发还处在“野蛮生长”的阶段。
那时候写代码,Activity里塞满了几百行甚至上千行的逻辑。网络请求、数据解析、UI更新,全揉在一起。能跑就行,谁管什么架构?
但项目一复杂,问题就来了。改一个UI,可能牵动整个网络层。加一个新功能,得把Activity翻个底朝天。测试?那更是噩梦。
所以,架构的出现,本质上是为了解决一个问题:让代码变得好改、好测、好维护。
1.1 MVC:Android原生的“混乱”起点
MVC(Model-View-Controller)是最早被引入Android的架构思想。
- Model:数据层,负责业务逻辑和数据处理。
- View:视图层,对应XML布局和Activity/Fragment。
- Controller:控制层,在Android里,Activity/Fragment天然承担了这个角色。
听起来挺合理,对吧?但实际用起来,问题很大。
Activity既是View又是Controller,职责严重耦合。我早期做过一个电商App,一个商品详情Activity写了快2000行。里面既有UI初始化,又有网络请求回调,还有数据缓存逻辑。每次需求变更,我都得小心翼翼,生怕改崩了哪个角落。
MVC的核心痛点:
- Activity/Fragment过于臃肿,难以维护
- View和Controller耦合,单元测试困难
- 业务逻辑分散,复用性差
我记得有一次,产品经理说要改商品列表的排序逻辑。我翻了半天代码,才发现排序逻辑散落在三个不同的回调里。改完一个,忘了改另一个,结果线上出了bug。嗯,从那以后,我就开始认真思考架构问题了。
3.2 MVP:解耦的第一步,但代价不小
MVP(Model-View-Presenter)的出现,算是Android架构的一次重要进化。
它把Controller换成了Presenter,并且明确了职责:View只负责UI展示,Presenter负责业务逻辑。View和Presenter通过接口通信,实现了真正的解耦。
// View接口
public interface ILoginView {
void showLoading();
void hideLoading();
void onLoginSuccess(User user);
void onLoginError(String msg);
}
// Presenter
public class LoginPresenter {
private ILoginView mView;
private LoginModel mModel;
public LoginPresenter(ILoginView view) {
this.mView = view;
this.mModel = new LoginModel();
}
public void login(String username, String password) {
mView.showLoading();
mModel.login(username, password, new Callback<User>() {
@Override
public void onSuccess(User user) {
mView.hideLoading();
mView.onLoginSuccess(user);
}
@Override
public void onError(String msg) {
mView.hideLoading();
mView.onLoginError(msg);
}
});
}
}
你看,这样写,逻辑清晰多了。测试也方便,可以轻松mock View接口。
但MVP也有它的毛病。最明显的就是:接口爆炸。每个页面都要定义View和Presenter接口,项目大了,接口文件多到让人头疼。而且Presenter里持有View的引用,如果忘记在onDestroy里释放,很容易内存泄漏。
我的经验:用MVP时,我习惯在BasePresenter里做生命周期绑定,用弱引用持有View,避免内存泄漏。但说实话,每次都要手动管理,挺烦的。
3.3 MVVM:数据驱动的优雅方案
MVVM(Model-View-ViewModel)的出现,算是解决了MVP的大部分痛点。
它的核心思想是:数据驱动UI。ViewModel通过LiveData或StateFlow持有数据,View观察这些数据的变化,自动更新UI。ViewModel不持有View的引用,彻底解决了内存泄漏问题。
// ViewModel
public class LoginViewModel extends ViewModel {
private MutableLiveData<User> userLiveData = new MutableLiveData<>();
private MutableLiveData<String> errorLiveData = new MutableLiveData<>();
private LoginRepository repository;
public LoginViewModel() {
repository = new LoginRepository();
}
public void login(String username, String password) {
repository.login(username, password, new Callback<User>() {
@Override
public void onSuccess(User user) {
userLiveData.postValue(user);
}
@Override
public void onError(String msg) {
errorLiveData.postValue(msg);
}
});
}
public LiveData<User> getUserLiveData() {
return userLiveData;
}
public LiveData<String> getErrorLiveData() {
return errorLiveData;
}
}
// Activity中观察
loginViewModel.getUserLiveData().observe(this, user -> {
// 自动更新UI
updateUI(user);
});
你想想看,ViewModel不需要知道View的存在,它只管提供数据。View只管观察数据变化。职责清晰,测试也简单。
而且ViewModel在屏幕旋转时不会丢失数据,这一点比Presenter强太多了。
MVVM的优势:
- 数据驱动,UI自动更新
- ViewModel不持有View引用,无内存泄漏
- 生命周期感知,自动管理订阅
- 更易于单元测试
3.4 为什么我们要不断重构?
讲到这里,你可能会问:既然MVVM这么好,为什么还要提MVC和MVP?
原因很简单:没有银弹。
每个架构都有它的适用场景。MVC适合简单页面,MVP适合逻辑复杂的页面,MVVM则适合数据驱动型页面。但更重要的是,重构不是为了追求时髦,而是为了解决实际问题。
我曾经接手过一个老项目,用的是MVC,Activity里全是逻辑。我花了两个月,逐步把核心模块重构到MVVM。过程很痛苦,但重构完后,新功能开发效率提升了至少30%,bug率也明显下降。
注意:重构不是重写。不要为了用MVVM而强行重构。如果项目稳定、需求变化少,保持现状也是一种选择。重构的时机,应该是当你觉得“改不动了”的时候。
说白了,架构演进史,就是一部对抗复杂性的历史。从MVC到MVP,我们学会了解耦。从MVP到MVVM,我们学会了数据驱动。未来可能还会有MVI、Compose等新架构,但核心目标不变:让代码更清晰、更可测、更易维护。
我个人习惯,新项目直接上MVVM + Jetpack。老项目重构,则根据模块复杂度,选择MVP或MVVM。记住,架构是工具,不是目的。适合你的,才是最好的。
| 架构 | 核心思想 | 优点 | 缺点 |
|---|---|---|---|
| MVC | Model-View-Controller | 简单直接,适合小项目 | Activity臃肿,耦合严重 |
| MVP | Model-View-Presenter | 解耦清晰,易于测试 | 接口爆炸,内存泄漏风险 |
| MVVM | Model-View-ViewModel | 数据驱动,生命周期安全 | 学习曲线稍高,调试略复杂 |
嗯,这一章就到这里。下一章,我会带你深入MVVM的核心组件,看看LiveData、ViewModel和Repository到底该怎么配合,才能写出真正可维护的代码。