返回 Android
Android
6 分钟阅读

Android开发中常见的MVP封装形式

一个缩略版的 MVP 结构——现在很少用了,但搞懂它有助于理解后来的 MVVM。

为什么还写 MVP

这篇是 当我们在谈MVP、MVVM、MVI的时候,我们到底在谈什么? 的缩略版。

说实话,实际业务里我很少再主动用 MVP 了——新项目直接上 MVVM(或者 MVI)更省心。但 MVP 在 Android 架构演进里占过很长一段,而且它和 MVVM 的对比,能帮你把「Presenter 手动绑生命周期」和「ViewModel 自动扛旋转屏」这两件事想明白。

下面这段 Kotlin 代码,是我觉得还算能代表 MVP 核心思路的一种封装:契约接口 + Presenter 拉数据 + View 只负责渲染。不展开讲基类、泛型封装、AutoService 自动注册那些——那些能让 MVP 好用一点,但封装成本上去了,不如直接 MVVM。


契约长什么样

MVP 里最常见的一个套路,是先定 IContract,把 View 和 Presenter 能干什么写清楚:

  • IViewRender:View 对外暴露的渲染能力,比如收到字符串之后刷新 TextView
  • IPresenter:业务入口,负责拉数据;同时要有 attachView / detachView,把 View 的生命周期管起来
interface IContract {
    interface IViewRender {
        fun callback(message: String)
    }
 
    interface IPresenter {
        fun fetchData()
 
        // 绑定 View,Presenter 才知道往哪回调
        fun attachView(viewRender: IContract.IViewRender)
 
        fun detachView()
    }
}

接口多,是 Java 时代 MVP 常被吐槽「笨重」的原因之一。查一个业务的完整链路,往往要在 Contract、Presenter、View 几个文件之间跳。但反过来说,职责边界也写得比较死——适合当年那种「先把 Activity 里的逻辑往外搬」的诉求。


View 和 Presenter 怎么配合

View 侧(Activity / Fragment)实现 IViewRender,在合适的生命周期里挂上 Presenter、触发请求、收到回调后改 UI:

class MyView : IContract.IViewRender {
    private val presenter: Presenter = Presenter()
 
    override fun onViewCreated() {
        presenter.attachView(this)
        presenter.fetchData()
    }
 
    override fun onDestroy() {
        presenter.detachView()
    }
 
    override fun callback(message: String) {
        mBinding.tvInfo.text = message
    }
}

Presenter 侧持有 View 的弱引用(这里用可空变量示意),异步拿数据,完了通过 viewRender?.callback() 丢回去:

class Presenter : IContract.IPresenter {
 
    private var viewRender: IContract.IViewRender? = null
    private val mCompositeDisposable = CompositeDisposable()
 
    override fun fetchData() {
        Single.create<String> { emitter ->
            Thread.sleep(2000L) // 模拟耗时请求
            emitter.onSuccess("Hello World")
        }
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(object : SingleObserver<String> {
            override fun onSubscribe(disposable: Disposable) {
                mCompositeDisposable.add(disposable)
            }
 
            override fun onSuccess(result: String) {
                viewRender?.callback(result)
            }
 
            override fun onError(e: Throwable) = Unit
        })
    }
 
    override fun attachView(viewRender: IContract.IViewRender) {
        this.viewRender = viewRender
    }
 
    override fun detachView() {
        mCompositeDisposable.clear()
        viewRender = null
    }
}

上面两段拼在一起,就是 MVP 最典型的一条数据流:Presenter 访问上层拿 Data → 回调 View → View 刷 UI。业务逻辑从 Activity 里挪出去了,测试 Presenter 也比测一整坨 Activity 现实一些。


真正要命的是生命周期

很多团队封装 MVP,只做了「Presenter 能往 View 回调数据」这一半,attach / detach 写得马虎,或者干脆忘了在销毁时 clear() 掉 Rx 订阅。

于是常见一幕:页面已经 onDestroy 了,Presenter 里的网络请求还在跑;回调落地时,View 里的 mBinding 早就没了。Java 项目直接 NPE;Kotlin 有空安全,NPE 可能少了,但内存泄漏、无效刷新、偶发 ANR 照样会来。

所以示例里 onDestroydetachView()mCompositeDisposable.clear()viewRender = null,不是可有可无的锦上添花——这是 MVP 能不能在生产环境站住脚的分水岭。当年我见过不少「名义上是 MVP、实际和 MVC 没差」的项目,问题多半出在这里,而不是 Presenter 代码多几行。

当然,完整版还可以把 onStart / onStop、配置变更、Presenter 是否该用 WeakReference 再细分一层。这篇故意缩略,那些生命周期细节就不展开了。


和 MVVM 怎么选

我自己的结论是:如果你今天开一个新模块,直接 MVVM 就行,不必在 MVP 的 Contract + 手动绑生命周期上再投入封装成本。Jetpack 的 ViewModel 自带配置变更存活,LiveData / Flow 又自带生命周期感知,省掉的是一整套「每个页面自己记得 detach」的心智负担。

MVP 的价值更多在「读懂老项目」和「理解架构为什么要往 MVVM 走」。想看我这边 MVVM 的写法,可以去 如何在Android开发中实现MVVM的架构 那篇。

这篇就当作一个能跑的 MVP 最小样本留着。代码不漂亮,但够说明问题——把数据生成挪到 Presenter,把 UI 刷新留在 View,中间用契约和生命周期绑紧。能做到这三点,MVP 就算入门;做不到第三点,还不如别叫 MVP。

相关文章

Android
4 分钟
如何在Android开发中实现MVVM的架构
MVP 之后更常用的 MVVM——ViewModel 自动扛旋转屏,数据用 LiveData / Flow 回推。
Android
17 分钟
当我们在谈MVP、MVVM、MVI的时候,我们到底在谈什么?
Presentation Layer 架构模式的演进脉络、职责边界与数据流向——从 MVC 到 MVI 的选型与实践