移动应用架构设计:MVVM实战,一个海归码农的血泪踩坑日记
上周五晚上九点半,我瘫在工位上盯着屏幕上那串红得发紫的 Crashlytics 报错日志,心里一万只羊驼奔腾而过。
“又是因为 ViewModel 生命周期没对齐 Activity 重建……” 我叹了口气,顺手抓起桌上已经凉透的瑞幸——这已经是今天第三杯了。
大家好,我是 Alex,刚从英国读完分布式系统硕士回来半年,目前在北京一家做数字身份认证的创业公司当 Android 开发(没错,就是那个天天喊着要“赋能 Web3”的团队)。通勤一小时,每天在国贸和回龙观之间穿梭,地铁上刷 LeetCode 的时间比吃饭还长。
回国前我以为自己会去搞高并发、搞共识算法,结果入职第一天就被 PM 拉进会议室:“Alex 啊,我们这个新 App 要支持链上身份核验,但 UI 得像微信一样丝滑,下周 demo 给投资人看,你来搭架构吧。”
我?一个连 Jetpack 都还没摸熟的“海归理论派”?当时真想掏出护照原地买张机票回伦敦。
为什么非得用 MVVM?
其实一开始我偷偷用了 MVP —— 老实说,在学校做课程项目时 MVP 写起来简单粗暴,逻辑清晰。但上线第一周就翻车了。
那天是去年双11前夕,产品突然要求加一个“区块链交易状态实时同步”功能。用户发起链上操作后,App 要轮询节点,一旦交易上链成功,立即更新 UI 并推送通知。
我吭哧吭哧在 Presenter 里塞了一堆协程 + Retrofit + WebSocket 逻辑,结果一测试:旋转屏幕一次,回调监听多注册一次;退出再进,内存泄漏直接 OOM。
Crashlytics 后台警报响得跟火警似的。运维小哥半夜打电话过来:“兄弟,你们 Android 端是不是在挖矿?CPU 占用率 98%!”
那一刻我悟了:在现代移动开发里,光有“能跑”远远不够,还得“稳如老狗”。
而 MVVM(Model-View-ViewModel)正好能解决这些问题:
- 生命周期感知:ViewModel 自动绑定 Activity/Fragment 生命周期,避免内存泄漏
- 数据驱动 UI:通过 LiveData 或 StateFlow,UI 自动响应数据变化,告别手动
setText()堆砌 - 可测试性强:业务逻辑集中在 ViewModel,单元测试覆盖率蹭蹭涨(虽然我们组测试覆盖率还是只有 30%,别问,问就是 deadline 逼的)
更重要的是,我们产品要做的不是普通 App,而是要和区块链交互的“可信入口”。用户每点一次“授权”,背后都是一笔真实的 Gas 费。如果因为架构混乱导致重复提交、状态不一致,那可真是“代码一运行,ETH 就不见”。
实战:从零搭建一个链上身份验证模块
我们的场景很简单:用户点击“连接钱包” → 弹出二维码 → 手机扫码确认 → 监听链上交易 → 成功后跳转主页。
第一步:定义数据模型(Model)
首先抽象出核心数据结构。注意,这里不要直接把 API 返回的 JSON 对象当 Model!我吃过这亏——后端某天改个字段名,整个 App 崩溃。
// domain/model/IdentityVerification.kt
data class IdentityVerification(
val requestId: String,
val status: VerificationStatus, // PENDING, CONFIRMED, FAILED
val blockchainTxHash: String? = null,
val timestamp: Long
)
enum class VerificationStatus {
PENDING, CONFIRMED, FAILED
}
接着写 Repository 层,负责和链上节点 & 后端 API 交互:
// data/repository/VerificationRepository.kt
class VerificationRepository(
private val apiService: ApiService,
private val blockchainClient: BlockchainClient // 这是我们封装的 Web3j 客户端
) {
suspend fun requestVerification(): String {
return apiService.createVerificationRequest().requestId
}
fun observeTransactionStatus(requestId: String): Flow<IdentityVerification> {
return blockchainClient.observeTransactionByRequestId(requestId)
}
}
这里有个坑:区块链交易确认可能需要几十秒甚至几分钟。如果用 Callback,很容易因为页面重建丢失监听。所以必须用 Kotlin Flow + SharedFlow / StateFlow 来保持状态。
第二步:ViewModel 是灵魂
ViewModel 负责协调 Model 和 View,绝不持有任何 View 引用(这是 MVP 的典型反模式)。
// presentation/viewmodel/VerificationViewModel.kt
@HiltViewModel
class VerificationViewModel @Inject constructor(
private val repository: VerificationRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<VerificationUiState>(VerificationUiState.Loading)
val uiState: StateFlow<VerificationUiState> = _uiState.asStateFlow()
fun startVerification() {
viewModelScope.launch {
try {
val requestId = repository.requestVerification()
// 切换到监听状态
_uiState.value = VerificationUiState.WaitingForScan(requestId)
// 监听链上状态变更
repository.observeTransactionStatus(requestId)
.onEach { verification ->
when (verification.status) {
VerificationStatus.CONFIRMED ->
_uiState.value = VerificationUiState.Success(verification)
VerificationStatus.FAILED ->
_uiState.value = VerificationUiState.Error("Transaction failed")
else -> Unit
}
}
.launchIn(viewModelScope) // 注意:launchIn 绑定 viewModelScope
} catch (e: Exception) {
_uiState.value = VerificationUiState.Error(e.message ?: "Unknown error")
}
}
}
}
sealed class VerificationUiState {
object Loading : VerificationUiState()
data class WaitingForScan(val requestId: String) : VerificationUiState()
data class Success(val verification: IdentityVerification) : VerificationUiState()
data class Error(val message: String) : VerificationUiState()
}
关键点来了:
✅ 用 StateFlow 替代 LiveData(Jetpack Compose 更推荐 Flow)
✅ 所有异步操作都在 viewModelScope 中启动,自动随 ViewModel 销毁而取消
✅ UI 状态用 Sealed Class 表达,避免 if-else 地狱
第三步:View 层只负责“展示”和“事件转发”
在 Activity/Fragment 中,我们只做两件事:观察状态 + 发送事件。
// ui/VerificationActivity.kt
class VerificationActivity : AppCompatActivity() {
private lateinit var binding: ActivityVerificationBinding
private val viewModel by viewModels<VerificationViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityVerificationBinding.inflate(layoutInflater)
setContentView(binding.root)
// 观察 UI 状态
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
render(state)
}
}
}
// 用户点击“开始验证”
binding.btnStart.setOnClickListener {
viewModel.startVerification()
}
}
private fun render(state: VerificationUiState) {
when (state) {
is VerificationUiState.Loading -> showLoading()
is VerificationUiState.WaitingForScan -> showQrCode(state.requestId)
is VerificationUiState.Success -> navigateToHome()
is VerificationUiState.Error -> showError(state.message)
}
}
}
这里特别注意 repeatOnLifecycle(Lifecycle.State.STARTED) —— 这是 Google 官方推荐的 Flow 收集方式,避免在后台收集数据浪费资源。
踩过的坑 & 性能优化
坑 1:旋转屏幕导致重复请求
一开始我没用 viewModelScope,而是直接在 Activity 里 launch 协程。结果一旋转屏幕,ViewModel 被重建,又发起一次 requestVerification(),用户钱包里莫名其妙多了两笔待确认交易。
解决方案:所有副作用(side effect)放在 ViewModel,且用 viewModelScope 管理生命周期。
坑 2:链上监听没取消,内存泄漏
早期用 Callback 监听交易,退出页面后回调还在执行,导致 ViewModel 无法释放。
解决方案:改用 Flow,并通过 launchIn(viewModelScope) 绑定生命周期。
性能优化:减少不必要的重组(Compose 用户注意)
如果你用 Jetpack Compose,记得把状态拆细:
// ❌ 不要这样
val uiState by viewModel.uiState.collectAsState()
// ✅ 应该这样
val isLoading by viewModel.isLoading.collectAsState()
val qrCode by viewModel.qrCode.collectAsState()
或者用 derivedStateOf 避免频繁 recomposition。
MVVM vs 其他架构:一张表说清楚
| 架构 | 适合场景 | 生命周期管理 | 测试难度 | 与 Compose 兼容性 |
|---|---|---|---|---|
| MVC | 超小型 Demo | 差(容易内存泄漏) | 高 | 差 |
| MVP | 中小型项目 | 一般(需手动解绑) | 中 | 一般 |
| MVVM | 中大型、需长期维护 | 优秀(自动感知) | 低 | 极佳 |
| MVI | 状态极其复杂(如金融交易) | 优秀 | 中 | 优秀 |
我们团队现在新项目一律 MVVM + Compose,老项目也在逐步迁移。虽然初期学习成本高点,但省下的 debug 时间足够你多喝十杯瑞幸。
最后:关于“产品”、“区块链”和“综合能力”
回国这半年,我最大的感受是:纯技术思维行不通了。
产品经理昨天又来找我:“Alex,能不能让用户扫完码后,加个‘正在上链’的动画?显得更可信。”
我说:“可以,但得等交易真正广播出去再显示,不然就是欺骗用户。”
他说:“那不行,投资人要看‘流畅体验’。”
你看,技术决策背后全是产品逻辑。而我们做的又是区块链相关产品——每一行代码都关联着真实资产。这时候,一个健壮、可追溯、状态清晰的架构,就不再是“炫技”,而是“责任”。
MVVM 让我能清晰回答这些问题:
- 当前用户到底处于哪个验证阶段?
- 如果 App 被杀后台,重新进入能否恢复状态?
- 交易失败是因为网络问题,还是 Gas 不足?
这些,在传统 MVC 里可能要翻半天日志才能定位。
写在最后
现在我的 App 已经稳定运行三个月,线上 Crash 率从 5% 降到 0.2%,连测试妹子都说“你们 Android 组最近 bug 少多了”。
虽然每天还是被 PM 追着改需求,被运维吐槽包体积太大,但至少——我不用再担心旋转屏幕炸掉用户的钱包了。
如果你也在做涉及区块链、金融、或任何“不能出错”的移动产品,真心建议你试试 MVVM。它可能不会让你一夜暴富,但至少能让你在周五晚上准点下班(梦想还是要有的)。
对了,下周我要去参加 GDG Beijing 的架构分享会,主题就是《MVVM 在 Web3 移动端的实践》,欢迎来现场一起吐槽 PM!
P.S. 回国找工作真的卷,但只要你能把“分布式系统理论”和“移动端实战”结合起来,offer 还是有的。共勉!

评论 0