ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android MVVM实战:ViewModel+LiveData+协程构建清晰架构

Android MVVM实战:ViewModel+LiveData+协程构建清晰架构 简介Android MVVM 示例Demo是一份面向Android初中级开发者的架构实践项目围绕MVVM模式核心思想通过数据绑定、LiveData与ViewModel的配合完整演示了如何在Android中实现视图与数据分离有效解决传统开发中UI更新繁琐、业务逻辑耦合、代码难以测试等问题。压缩包内共737个文件包体大小20.65MB以XML布局、Java/Kotlin源码、Gradle构建脚本、Jar依赖、JSON配置等类型为主并附带可直接安装的APK便于对照真机运行。项目分层清晰覆盖实体类、仓库类、ViewModel、绑定适配器等多个关键模块代码注释详细既适合初学者一点点读懂MVVM运作原理也适合团队直接抽取模板复用。目前已有196人学习下载通过研读Demo可快速掌握MVVM架构的搭建流程理解LiveData的生命周期感知特性与Data Binding的自动更新机制从而提升Android应用的模块化、可维护性与可测试性。 先聊点实际的。做了这么多年Android开发我见过太多把Activity当成“万能工具箱”的写法网络请求、解析数据、更新UI、存缓存全塞一起一个类动不动上千行。刚开始还能跑需求一变就开始改一处崩三处。后来项目引入MVVM架构情况才明显好转。如果你也想把代码理清楚、把状态管理搞明白或者正准备面试、想拿一个能讲清楚架构的项目出来那这个“Android MVVM 示例Demo”就值得你花一晚上去搭一遍。这个Demo不是什么高深框架也不是三件套堆砌而是把Jetpack家族的ViewModel、LiveData、Repository、协程等组件串起来跑一个完整的“请求数据→状态管理→界面更新”链路。它能让你直观看到哪一层负责什么、数据怎么流动、页面重建时数据为什么不会丢。无论你是刚学会写RecyclerView的新手还是写了两三年业务但一直靠硬编码撑着的进阶开发者这套Demo都能让你少踩很多坑。1. 为什么我劝你把MVVM拆成一个独立Demo来做1.1 MVVM到底解决了什么痛点先看我们以前最熟悉的MVC写法用户点了“加载”Activity发起请求拿到数据后直接操作控件去刷新。听起来没问题但项目一变大Activity就得同时扮演“控制器”和“视图”两个角色UI逻辑和业务逻辑全都缠在一起。比如一个登录功能既要处理输入校验又要管理请求状态还要处理Token缓存只要UI一重建转屏、从后台回前台请求结果就丢了代码里就充满各种if (view ! null)的判断。MVVM的核心思路是把状态和视图剥离开界面只管把状态“展示”出来所有业务数据和状态变化都由ViewModel持有。这样Activity只负责“观察”LiveData收到新数据就刷新UI不用再关心数据从哪来、请求怎么处理。ViewModel本身持有数据生命周期比Activity长转屏重建后数据还在这也是MVVM最直观带来的好处之一你不需要自己去做状态保存。1.2 为什么选LiveData而不是其他方案现在框架选择挺多的RxJava、Flow、LiveData甚至StateFlow。我在这套Demo里用了LiveData作为核心状态载体理由很简单它不是最强大最全能的但它是原生、可控、Demo阶段最不容易跑偏的。LiveData自带生命周期感知界面在后台时不会回调也不会因为Activity销毁造成崩溃这能帮你少写一大截防御代码。我也在Demo里留了协程Flow的接口位置方便你后面升级替换。实际面试或做正式项目时很多人会问“LiveData和Flow选哪个”这没有标准答案但如果你能把自己的Demo讲清楚“为什么这里用LiveData要是换Flow你会怎么改”就已经比大多数人强了。说白了Demo的意义不是教你选最流行的而是让你能回答出“在什么场景下用哪种方案更合适”。1.3 适合谁照着抄这套Demo适合以下几类人已经能熟练写页面但对“代码组织”比较头疼的开发者想看看分层到底怎么分。正在准备面试想找一个小而完整、能讲出架构亮点的项目。团队内部要推广MVVM需要一份可直接拿去做Code Review的样例。自学Android有一段时间但一直停留在控件和API层面想往更高层次走的学习者。2. 示例Demo的工程整体设计与依赖选型2.1 包结构怎么划分才不会“打架”很多新手写Demo最容易翻车的地方是把所有文件都放在一层里Activity、Adapter、Bean、Api全缠在同一个包下美其名曰“快速实现”。这种写法在Demo阶段还不显但一旦数据层和UI层改起来就会互相影响。我这个Demo采用的是按“职责”分包后面扩展和排查都要轻松得多data负责数据来源包括网络接口、本地缓存和Repository实现。model只放实体类不掺任何逻辑。repository作为数据层的统一出口对上层屏蔽“数据来自网络还是缓存”。ui放Activity/Fragment和Adapter只做界面展示和事件转发。viewmodel用于连接数据层和UI层始终不持有Activity引用。这个分包结构的好处是你一眼就能看出某个类的职责归位。ViewModel想拿数据只找RepositoryActivity想显示内容只观察ViewModel的LiveDataRepository想返回结果内部随便走网络还是本地缓存。哪怕后面这个Demo做大改造比如把LiveData换成Flow也不会动Activity那一层的代码。2.2 依赖清单与版本选择参考这份依赖清单是按现在稳定主流的方式整理的。你在实际创建项目时建议打开官方文档看看最新版本号别直接照抄我的数字// 基础组件 implementation androidx.core:core-ktx:1.12.0 implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.11.0 implementation androidx.constraintlayout:constraintlayout:2.1.4 // 生命周期与ViewModel、LiveData implementation androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0 implementation androidx.lifecycle:lifecycle-livedata-ktx:2.7.0 // 网络请求与数据解析 implementation com.squareup.okhttp3:okhttp:4.12.0 implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation com.google.code.gson:gson:2.10.1 // 协程 implementation org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3注意版本号选型要基于你本地的Android Studio和Gradle插件版本不要一上来就拉最新或最老版本。如果项目同步时报依赖冲突优先去Google官方文档页面查看“当前稳定版本”而不是直接选择最新版本。2.3 为什么Demo首选用Retrofit而不是手写网络我知道很多小Demo喜欢直接用HttpURLConnection或者OkHttp的Callback图个简单。但既然做的是MVVM架构学习网络层就必须做到“可替换”。Retrofit配合Gson解析可以用接口的方式把“请求方法”和“URL”定义得明明白白返回的数据结构也一目了然这在架构演示里非常友好。等你看懂了Retrofit的接口定义以后换任何网络库只需要改Repository内部实现其他层完全不受影响。3. 核心代码实现与关键细节解读3.1 搭建ViewModel与LiveData的核心数据链路首先从ViewModel开始它是MVVM的“中枢”。注意ViewModel绝对不直接持有Context也不要放View引用这是原则问题。它内部只放数据对外暴露LiveDataUI层通过这些LiveData去观察状态。下面是我在这套Demo里的核心链路用户在首页点击一个列表项ViewModel立刻触发一次异步请求数据回来后更新到LiveData中Activity通过观察LiveData来刷新页面。整个过程数据单向流动谁改状态谁负责不绕弯。class MainViewModel( private val repository: MainRepository MainRepository() ) : ViewModel() { // UI状态加载中、成功、失败 private val _uiState MutableLiveDataUiState() val uiState: LiveDataUiState get() _uiState fun loadData() { viewModelScope.launch { _uiState.value UiState.Loading // 先给界面一个“加载中”状态 val result repository.fetchData() _uiState.value when { result.isSuccess - UiState.Success(result.data) else - UiState.Error(result.message) } } } } sealed class UiState { object Loading : UiState() data class Success(val data: ListItemModel) : UiState() data class Error(val message: String) : UiState() }你可能会发现这里是用了密封类来定义UI状态。有人觉得用三个LiveData一个放加载、一个放数据、一个放错误更方便但我强烈建议用单个状态类去收口。好处在于界面永远是“多选一”的状态不会同时出现Loading和Success叠加的混乱。这种用sealed class管理状态的方式也是现在官方样例和主流项目的通用做法面试时能聊上几句非常好用。3.2 Repository层如何屏蔽“数据从哪来”接着看Repository层。它的存在就是为了让上层不知道数据到底从哪里拿来的。Demo里我先做了一个“模拟数据源”跑起来可以不用依赖后端还能确保返回速度够快。这个模拟源内部实际上就是延时1000毫秒后返回几行测试数据这样不会卡住UI线程又能让你看到Loading的状态变化。真实项目里这一层会换成本地Room数据库加Retrofit网络请求的组合。比如“先查缓存缓存没有再去请求网络拿到网络结果再写回缓存”这些都是Repository内部该干的事ViewModel完全感知不到。在这套Demo里你可以模仿这个“读缓存优先”的结构来写方便以后直接扩展。class MainRepository(private val api: MainApi MainApi()) { suspend fun fetchData(): ResultListItemModel { return try { val response api.getItems() if (response.isSuccessful) { Result.success(response.body() ?: emptyList()) } else { Result.failure(Exception(Server Error: ${response.code()})) } } catch (e: Exception) { Result.failure(e) } } }提示在真实项目里请务必在Repository层处理“网络错误提示的转换”不要把原始异常直接抛给UI。UI层只需要知道“现在出错了”而不需要理解SocketTimeoutException还是ConnectException。你可以设计一个统一的“业务错误码”把可能出现的异常收敛到几种提示上。3.3 UI层观察数据时最容易忽略的问题Activity或Fragment这边不要做任何请求操作也不要直接改ViewModel里的LiveData只做观察和状态渲染就够了。下面这段代码是这套Demo的核心界面逻辑class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.uiState.observe(this) { state - when (state) { is UiState.Loading - showLoading() is UiState.Success - showList(state.data) is UiState.Error - showError(state.message) } } viewModel.loadData() } }有几个地方我想单独提醒一下不要用observeForever去观察LiveData如果你不了解生命周期它会导致回调在界面不可见时依然执行容易引发状态更新错乱和内存泄漏问题。Demo里务必用observe。不要把整个Activity传给Adapter让Adapter只接收数据List和一个点击回调接口。这样Adapter就是可复用的换页面也能用。不要在onCreate里重复调用loadData()配合by viewModels()ViewModel在转屏后不会重建所以onCreate再次执行时不会重新发起请求。这就是MVVM相对Activity传统写法最大的优势你省掉了所有“判断之前有没有请求过”的临时变量。4. 常见问题与排查技巧实录4.1 LiveData数据倒灌界面反复显示旧状态这个问题几乎每个用LiveData的人都会遇到。比如你从列表页进入详情页详情页返回后列表的Loading又闪了一下这是因为LiveData的特性新观察者订阅时会立刻收到当前持有的最新值哪怕这个值是你几秒钟前设置的“旧值”。解决方案在Demo里可以做得很干净使用SingleLiveEvent这类只消费一次的事件包装类。或者改用协程Flow的SharedFlow加replay 0让新订阅者收不到历史值。对于Demo来说我建议你在代码注释里把这个坑写明白然后给出一个最简单方案把页面级状态和一次性事件分开状态用LiveData事件的提示信息用SingleLiveEvent或者直接放在点击回调里处理。4.2 异步操作在界面销毁后仍在运行如果你用老办法thread.start()去请求数据很可能Activity都销毁了线程还在跑最后回来更新界面时直接崩溃。而使用viewModelScope.launch协程的生命周期会跟随ViewModelViewModel销毁时会自动取消协程不需要我们手动干预。这里有个细节不要在ViewModel里用GlobalScope一旦用了它协程就变成全局的了页面销毁也取消不掉这和直接开线程没什么区别。如果你遇到“页面关掉后还有日志打印”多半就是这个原因。4.3 添加依赖后项目无法构建或Gradle同步失败这是个比较常见的环境问题。Android Studio在创建新项目时会自动生成对应版本的Gradle插件和Kotlin版本。如果你手动添加了我前面列出的依赖可能因为版本冲突导致同步失败。我的处理经验是三步走查看build.gradle里com.android.tools.build:gradle插件版本确认SDK版本匹配。去Google官方Maven仓库页面确认你填写的依赖版本存在。如果一直报错把gradle-wrapper.properties里的Gradle版本和插件版本的对应关系核对一遍Gradle版本和AGP版本必须匹配。注意Android Studio国内网络环境下载Gradle依赖可能会很慢。如果你在同步依赖时卡在Download状态建议使用镜像仓库配置把repositories里的google()和mavenCentral()顺序调整优先走国内可访问的仓库地址。如果公司已有自有Maven私服也可以配置到settings.gradle里。4.4 RecyclerView列表在数据刷新后出现闪烁或乱序这是列表数据更新时的常见问题。如果你每次都直接提交新的List给Adapter并调用notifyDataSetChanged()数据量小时能看到闪烁数据量大时还会卡顿。Demo里我用的是最简单的做法先让Adapter持有的List发生变化再调用通知方法。但在实际项目或更复杂的Demo里推荐使用ListAdapter配DiffUtil它能精确算出哪一条变了、哪一条删了只更新变化的那一项性能和视觉体验都会好很多。如果你在Demo里把这段也做出来就能给面试官展示了。4.5 自测时如何模拟慢网络和失败场景我自己做这个Demo时为了验证Loading和Error状态专门在Repository里写了一个“是否开启慢速模拟”的开关。一开始没有这个开关总是等1秒数据就刷出来了Loading状态根本来不及看。后来我把数据源接口加了一个随机延时在调试模式下手动构造超时异常就能稳定复现Error状态了。这个小技巧非常实用在数据层加一个可控的“假失败开关”会让你的调试效率高出很多也不影响正常逻辑。5. 这套Demo后续还可以怎么扩展做完基本链路之后我给这份Demo总结过几条扩展的方向按“性价比”排序加入Room做本地缓存Repository先查Room没有数据再走网络然后把网络结果写入Room。这个扩展能把MVVM分层优势发挥得更彻底。把LiveData替换成Flow用StateFlow代替LiveData再用StateIn做状态管理。你会发现UI层几乎不用改只是观察方式从observe变成collect。引入Hilt做依赖注入当多个ViewModel都需要同一个Repository时手动传参会越来越麻烦这时用Hilt自动注入会顺手很多。加入Paging 3做列表分页当你的列表数据量大了以后Paging 3配合PageSource可以让整个加载过程更加平滑。这些扩展方向不用全做挑一个你最感兴趣的去折腾就已经能让这个Demo从“能跑”升级到“能讲”。面试官问你“这个项目有什么亮点”你完全可以说“我加了Room缓存把数据层做成了网络缓存双通道”这一句话的分量比你贴一百行代码都重。写在最后我的一点心声MVVM这套架构就算你学得再熟真到项目里还是会被各种需求打得措手不及。所以我特别建议你别光看也别光存收藏夹赶紧建个新项目照着Demo自己动手敲一遍。你可能会发现依赖版本对不上、Gradle跑不动、数据返回时顺序不对这些都正常。我当年折腾第一个MVVM Demo时光处理LiveData倒灌就花了两天。但正因为我踩过坑后面写真实项目时心里特别有底。拿到这个Demo请你一边跑、一边断点跟、一边故意改坏它试试看到崩溃日志能大概猜出是哪一层的问题才说明这套架构真属于你了。本文还有配套的精品资源点击获取
返回列表