android MVC/MVP/MVVM/MVI架构发展历程和编写范式

发布于:2025-08-02 ⋅ 阅读:(12) ⋅ 点赞:(0)

前言

Android的架构发展进程。诸位看了那么多帖子,都在说MVVM,MVI架构如何如何。看完我的帖子,结合你自己的开发,你认为你见过的代码真正完全遵循了MVVM, MVI等架构吗?
个人认为:架构只是一种思想,并且在android上它们的使用是模糊的。在官方的开发库的指引和约束下,才形成了现在的开发规范。比如liveData和Flow,ViewModel的开发框架之下,其实是有MVVM和MVI的影子的,但又不完全相同。我们主要是领悟其中部分的架构重点:
业务分层数据驱动单向数据流, 事件绑定监听, 生命周期感知关注内存泄露

MVC

View:视图层,android 中的 xml文件/Activity/Fragment;

Controller:控制层,但在开发过程中会把 Controller 层的任务也放到 Activity/Fragment 层,导致 View 层臃肿,耦合严重。

Model:模型层,一般是网络请求、数据库相关的操作。

让我们把时间拨回到蛮荒时代(10年前?),还没有jetpack的库,没有ViewModel + LiveData的蛮荒时代,刚刚接触android开发,常常一把梭代码都在Activity里面写完。大家也听说了window,前端,后端开发,都有MVC架构,于是把它引入到android上。

screenshot-20250801-103422

Demo:

class XXXActivity {
    private XXXController controller = new XXXController();
    
	void onCreate() {
        setContentView(R.layout.ui)
        findViewById(R.id.button).setOnClick() {
            controller.request() {
               mTextView.setText()
            }
        }

    }
}

class XXXController {
    String request() {
        apiModel.requestApi()
    }
}

class ApiModel {
    String requestApi()
}
  • 优点:
    职责分离清晰(视图/逻辑/数据)
    学习成本低,适合中小项目
    降低了业务耦合性

  • 缺点:
    视图逻辑与业务逻辑混杂
    臃肿的Activity/Fragment
    不利于复用,controller与Activity之间的深度绑定

天然的,android上就是xml编写布局,View自然而然的已经帮你分开。但是Activity又与xml和控件强相关,xml和Activity都充当了View;而Controller虽然你可能想到抽取成一个函数用来屏蔽Model数据的请求,但是结果往往需要直接设置上去,但是往往Activity也充当了部分controller的职责。controller与Activity之间界限模糊,代码相互耦合,不利于重构和抽象。

MVP

Model:模型层用于数据查询以及业务逻辑,跟MVC大同小异。
View:视图层用于展示与用户实现交互的页面,通常实现数据的输入和输出功能,跟MVC大同小异。
Presenter:表示器负责连接M层和V层,从而完成Model层与View层的交互,还可以进行一些业务逻辑的处理。

基于在android实现上,controller和View(activity)之间过于耦合交错的原因,于是引入了MVP架构。
它的引入本质是想彻底打断View与Model之间的耦合,交给Presenter层去管理V和M的交互。而且便于重用。
screenshot-20250801-103422

  • 优点:
    层次清晰:模块职责划分明显,接口功能清晰,Model层和View层分离,修改View而不影响Model。
    重用性高:功能复用度高,方便.一个Presenter可以复用于多个View,而不用更改Presenter的逻辑。
    开发职责互不影响:如果后台接口还未写好,但已知返回数据类型的情况下,完全可以写出此接口完整的功能。
    面向接口编程。

  • 缺点:
    由于View层和Model层都需要经过Presenter层,导致Presenter层过于复杂,维护麻烦
    面向接口编程,样板代码非常多。

Demo:如下我让AI帮我写的,是一个标准的MVP代码。
你想想一个简单的界面请求与状态变更竟然冒出一堆抽象的类,View也得提供函数,请求接口也得额外封装。追加代码的时候,简直是灾难。这种样板代码写起来过于繁琐。


public interface LoginContract {
    interface View {
        void showProgress();
        void hideProgress();
        void loginSuccess();
        void loginFail(String error);
    }

    interface Presenter {
        void login(String username, String password);
    }

    interface Model {
        void performLogin(String username, String password, OnLoginListener listener);
        
        interface OnLoginListener {
            void onSuccess();
            void onFailure(String error);
        }
    }
}

 class LoginModel implements LoginContract.Model {
    @Override
    public void performLogin(String username, String password, OnLoginListener listener) {
        // 模拟网络请求延迟
        new Handler().postDelayed(() -> {
            if ("admin".equals(username) && "123456".equals(password)) {
                listener.onSuccess();
            } else {
                listener.onFailure("用户名或密码错误");
            }
        }, 2000);
    }
}

 class LoginPresenter implements LoginContract.Presenter {
    private LoginContract.View view;
    private LoginContract.Model model;

    public LoginPresenter(LoginContract.View view) {
        this.view = view;
        this.model = new LoginModel();
    }

    @Override
    public void login(String username, String password) {
        view.showProgress();
        model.performLogin(username, password, new LoginContract.Model.OnLoginListener() {
            @Override
            public void onSuccess() {
                view.hideProgress();
                view.loginSuccess();
            }

            @Override
            public void onFailure(String error) {
                view.hideProgress();
                view.loginFail(error);
            }
        });
    }
}
 class LoginActivity extends AppCompatActivity implements LoginContract.View {
    private EditText etUsername, etPassword;
    private Button btnLogin;
    private ProgressBar progressBar;
    private LoginContract.Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        presenter = new LoginPresenter(this);

        btnLogin.setOnClickListener(v -> {
            ...
            presenter.login(username, password);
        });
    }

    @Override
    public void showProgress() {
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideProgress() {
        progressBar.setVisibility(View.GONE);
    }

    @Override
    public void loginSuccess() {
        ...
    }

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

MVVM

而MVVM的出现,它的思想可以追溯到window的WPF。

它的最重要的思想就是数据驱动模型。抛弃了以前的命令式事件驱动
screenshot-20250801-103422

数据与UI双向绑定,从而省去了模型数据改变后通知数据更新的步骤。核心就是UI上你输入的内容,立刻反应到变量上;而变量的数据发生变化,立刻自动更新UI。
screenshot-20250801-103422

我们来试图揣测下android官方的心路历程
当时MVC和MVP模式编写的代码,导致Activity/Fragment耦合臃肿,同时,它们又与Context强相关,Activity的旋转导致数据丢失,controller/presenter持有View引用,代码耦合,内存泄露等问题,都会让开发出来的app质量不高。

于是google官方推出jetpack系列库,设计了lifecycle+ViewModel+LiveData的官方框架:

  • lifecycle构架整个androidx基石,通过自动解除引用的观察者模式构建了Activity/Fragment等生命周期的基础;所有的viewModel,liveData, 以及后来的协程scope都与它脱不开;
  • viewModel要求脱离context/Activity/Fragment,解构控制层代码;
  • LiveData用来做监听器和更新数据,提供数据驱动(发展到kotlin,又有Flow)。

逐渐形成了如下的android编程范式:

  1. ViewModel持有liveData提供注册绑定:提前将ViewModel里面liveData在Activity/View层注册监听准备好。
  2. View → ViewModel:用户交互(如点击按钮)触发事件,通过接口或Data Binding传递给ViewModel。
  3. ViewModel → Model:ViewModel调用Model(数据库,网络Api等)的方法获取或处理数据。
  4. Model → ViewModel:Model通过回调或响应式编程将结果返回给ViewModel。
  5. ViewModel → View:ViewModel更新LiveData/StateFlow,View通过数据绑定自动更新UI。
  • 优点:
    分离关注点:View专注UI展示,ViewModel处理业务逻辑,Model管理数据,代码结构清晰。
    可测试性强:ViewModel不依赖Android框架,可通过单元测试验证逻辑。
    生命周期安全:ViewModel感知Activity/Fragment的生命周期,避免内存泄漏。

  • 缺点:
    数据绑定会消耗额外资源,占用更多的内存
    数据绑定会导致模型数据的变化变得难以追踪

MVI

我们来看,时下android kt+Flow+协程开发更接近的一种思想。
MVI即Model-View-Intent,最重要的就是单向数据流
它受Cycle.js前端框架的启发,提倡一种单向数据流的设计思想,非常适合数据驱动型的UI展示项目。
screenshot-20250801-103422

Model
与其他MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。
当前界面展示的内容无非就是UI状态的一个快照:例如数据加载过程、控件位置等都是一种UI状态
View
与MVVM/MVC等中的View一致,可能是一个Activity、Fragment或者任意UI承载单元。
MVI中的View通过订阅Intent的变化实现界面刷新(不是Activity的Intent、后面介绍)
Intent
此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model进行数据请求。

从界面发出的数据叫Intent,而界面接收的数据叫State,这样整个界面的刷新流程就形成一条Unidirectional Data Flow(UDF),即单向数据流。

  • 优点:
    强调单一数据流和不可变状态,易于调试和测试。
    状态集中管理,UI 只需渲染状态,逻辑清晰。
    Intent 和 State 的分离,使得代码更加模块化。

  • 缺点:
    对于简单的应用可能显得过于复杂。
    状态对象可能会变得非常庞大,管理起来比较麻烦。
    学习曲线较陡,特别是对不熟悉函数式编程和不可变状态的开发者。

这里用AI生成的demo:

sealed class CounterState {
//不推荐写成data class。原因看上一篇帖子 https://blog.csdn.net/jzlhll123/article/details/149749205
    data class Success(val count: Int) : CounterState()
    object Loading : CounterState()
    data class Error(val message: String) : CounterState()
}

sealed class CounterIntent {
    object Increment : CounterIntent()
    object Decrement : CounterIntent()
    object Reset : CounterIntent()
}

 CounterViewModel : ViewModel() {
    private val _state = MutableStateFlow<CounterState>(CounterState.Success(0))
    val state: StateFlow<CounterState> = _state

    fun processIntent(intent: CounterIntent) {
        when (intent) {
            is CounterIntent.Increment -> {
                val current = (_state.value as? CounterState.Success)?.count ?: 0
                _state.value = CounterState.Success(current + 1)
            }
            is CounterIntent.Decrement -> {
                val current = (_state.value as? CounterState.Success)?.count ?: 0
                _state.value = CounterState.Success(current - 1)
            }
            is CounterIntent.Reset -> {
                _state.value = CounterState.Success(0)
            }
        }
    }
}

class MainActivity : AppCompatActivity() {
    private val viewModel: CounterViewModel by viewModels()
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.state.collect { state ->
                    when (state) {
                        is CounterState.Success -> updateUI(state.count)
                        CounterState.Loading -> showLoading()
                        is CounterState.Error -> showError(state.message)
                    }
                }
            }
        }

        binding.btnIncrement.setOnClickListener {
            viewModel.processIntent(CounterIntent.Increment)
        }
        binding.btnDecrement.setOnClickListener {
            viewModel.processIntent(CounterIntent.Decrement)
        }
        binding.btnReset.setOnClickListener {
            viewModel.processIntent(CounterIntent.Reset)
        }
    }

    private fun updateUI(count: Int) {
        binding.tvCount.text = count.toString()
    }
}

开发范式为:

  1. ViewModel中持有Flow(数据对象),它们可以供View层去注册监听,刷新UI。
  2. 定义了所有的显示UI的State;
  3. 定义了所有的View层往ViewModel调用的Intent(非Android Intent类,这里理解为Action更为合适,以后用Action来表述);
  4. View->ViewModel:传递Action
  5. ViewModel->Model: 解析具体某个Action,执行对应的Model数据层请求,并转变得到State。
  6. ViewModel->View: State更新以后,触发View层的UI更新。

总结

收回开头个人感悟,只有理解了MVC,MVP,MVVM,MVI的核心思想:

业务分层数据驱动单向数据流, 事件绑定监听, 生命周期感知内存泄漏关注, …
这些开发原则才是重点。
再结合时下流程的开发框架,ViewModel+LiveData+Flow+协程,相信一定能开发出高质量,可维护性高的项目代码。

在下一篇文章会继续学习android官方架构开发指南,并给出一个比较合适的基础开发架构。