ARTICLE DETAIL

资讯详情

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

Android Jetpack架构实战:ViewModel+LiveData+DataBinding生命周期数据同步方案

Android Jetpack架构实战:ViewModel+LiveData+DataBinding生命周期数据同步方案 在Android开发里页面层的数据同步问题曾经是让我最头疼的事。Activity一转屏数据没了接口回调回来UI还没刷新稍不留神内存泄漏。后来我把项目的页面架构切换到Android Jetpack的ViewModel、LiveData和DataBinding这一套组合配合生命周期管理很多问题从根上被解决了。这篇文章把我在实际项目里的架构落地过程、踩坑记录和实践心得整理出来适合准备重构页面架构的团队也适合刚开始学Jetpack、想把LiveData和DataBinding这些组件真正用明白的开发者。1. 为什么页面架构要换成ViewModel加LiveData加DataBinding1.1 传统写法的三个问题传统页面代码基本上就是Activity里面写逻辑。启动时初始化控件和加载数据接口回调回来就findViewById、setText离开页面时在onDestroy里取消注册、释放资源。这套写法在小页面还能扛住一旦页面复杂三个问题会接连冒出来。第一个问题是配置变更导致的数据丢失。转屏的时候Activity重建所有成员变量归零如果你的网络请求是在旧Activity发的回调回来之后Activity已经销毁轻则空指针重则内存泄漏。第二个问题是手动同步UI状态状态一多就会漏。比如一个登录按钮要同时考虑用户名密码是否非空、是否正在请求、是否通过校验这些状态散落在多个回调里维护起来非常容易出错。第三个问题是View操作和业务逻辑纠缠在一起Activity越来越臃肿想拆不好拆。我前几年接手过一个提交表单页转屏后整个表单清空用户填了三分钟的东西没了气得直接在应用商店给一星。后来加了InstanceState硬存十几个字段每个都要手动存和恢复每次加字段都要改三四个方法。这种体验让我下定决心必须换一套能从设计上解决这些问题的架构。1.2 三个组件各自的定位Jetpack这套组合能解决上面的问题靠的是职责分离。ViewModel负责数据持有它把UI状态放进独立于Activity的对象里配置变更后同一个实例还能拿回来数据自然不丢。LiveData负责通知变化它是一种能感知生命周期的数据容器只在页面处于活跃状态时向观察者分发最新值页面销毁后能自动清理观察者。DataBinding负责视图和数据绑定把原本在代码里写的那套赋值、监听操作变成布局文件里的声明式表达式让数据和UI的关系一目了然。三个组件合在一起数据在ViewModel里住着变化通过LiveData广播布局通过DataBinding自动订阅。Activity里只需要把三者组装起来剩下交给框架。用我的话来说以前是页面拉数据、找控件、写监听现在是数据自己会跑到该去的控件上。1.3 选型前要确认的环境事项这套架构对项目环境有一定要求。Android Studio建议用较高版本Gradle插件至少4.1以上AndroidX依赖是必须的。targetSdk版本目前主流都到Android 12/13了实测DataBinding在这些系统上表现正常。需要注意Android 12开始系统对Activity启动、PendingIntent等有更严格限制但这和Jetpack本身不冲突不用过度担心。另外接入前最好先统一Kotlin版本和依赖库版本。如果项目里还有老support库要全部迁移到AndroidX。迁移过程本身可能有点繁琐但不迁移的话DataBinding自动生成的类会出现找不到AndroidX依赖的一堆编译错误。我建议在项目结构允许的情况下优先在新建的页面模块里实验这套架构跑通后再铺开。2. ViewModel把页面数据装进保险箱2.1 它怎么做到转屏不丢数据ViewModel的生命周期范围不是跟着Activity实例走而是跟着ViewModelStoreOwner走。Activity实现了ViewModelStoreOwner接口系统在Activity配置变更销毁时不会把ViewModelStore清掉而是保存起来等新Activity创建后重新取回。所以你在onCreate里拿到的ViewModel和转屏前是同一个对象里面的成员变量天然保留。简单记忆是Activity的finish和Configuration Change是两回事。普通销毁并重建时同一个ViewModel会被新Activity重新找到真正finish时ViewModelStore才会被清理。这个机制看着简单其实是Android系统为了实现“场景恢复”做的关键设计。2.2 onCleared的用途和注意点ViewModel在真正销毁时会调用onCleared()。这个回调官方文档写得很简略实际里非常有用。我习惯在ViewModel里保存协程的Job、RxJava的Disposable、网络请求的Cancelable然后在onCleared里统一取消或释放。这样页面离开后后台任务立即停止不会把结果抛给已销毁的UI。需要注意onCleared被调用的时候页面一般已经finish也就是说Activity已经不在活跃状态。如果在onCleared里尝试更新UI比如调用binding.tv.setText很可能空指针或白白执行多余操作。正确姿势是凡是UI相关操作都通过LiveData暴露出去让UI层自己响应。2.3 获取ViewModel的正确和错误方式Kotlin项目里最推荐的方式是private val viewModel: UserViewModel by viewModels()这是activity-ktx提供的扩展按懒加载方式创建并绑定到当前Activity的ViewModelStore。如果是在Fragment里写法一样但依赖fragment-ktx。Java项目用UserViewModel viewModel new ViewModelProvider(this).get(UserViewModel.class);它能拿到当前Activity对应的实例配置变更后重新执行get时返回的还是同一个。千万别自己new ViewModel。自己new出来的对象就是普通Java对象不挂在ViewModelStore上转屏必丢也不会收到onCleared。我见过有同事在基类里写了一个createViewModel()返回new出来的实例等于把Jetpack的救命机制又绕开了。2.4 带参数构造的ViewModel如果ViewModel构造函数需要参数比如用户ID就要通过Factory创建class UserViewModel(private val userId: String) : ViewModel() { class Factory(private val userId: String) : ViewModelProvider.Factory { override fun T : ViewModel create(modelClass: ClassT): T { return UserViewModel(userId) as T } } }Activity里用val viewModel: UserViewModel by viewModels { UserViewModel.Factory(intent.getStringExtra(USER_ID) ?: ) }这里的关键是Factory必须返回正确的ViewModel类型强转时要小心。如果你觉得工厂代码写起来啰嗦官方也提供了viewModelFactory { initializer { ... } }这个DSL写法但需要引入lifecycle-viewmodel-ktx等依赖。2.5 不要在ViewModel里存Activity的Context最后一个高频坑是存Context。ViewModel寿命比Activity长假如你为了弹Toast或拿资源把Activity引用放进了ViewModel那么Activity销毁后依然被ViewModel握住导致整个Activity以及它关联的所有View都泄漏。我见过最严重的案例是进入页面十几次后直接OOM。如果确实需要上下文优先用AndroidViewModel它接收的是Application这个单例不会泄漏。或者在方法调用时把Context作为参数短暂传进去用完就丢。总之一句话ViewModel只管数据别管UI和界面上下文。3. LiveData一种会看眼色的数据容器3.1 LiveData怎么感知生命周期LiveData的核心设计是持有数据和观察者观察者注册时带着LifecycleOwner。LiveData内部根据owner的当前状态决定是否允许派发数据只有状态是STARTED或RESUMED才会派发如果owner处于DESTROYED就会移除观察者让整个通知链路自动断掉。这就是“会看眼色”的意思。以前用EventBus和接口回调的时候数据分发不关心页面状态。页面退到后台通知照样来代码里判断不得不塞一堆if。LiveData把这些判断从业务代码移到了框架里页面不活跃就不打扰活跃后立刻拿到最新值。这个特性在配合DataBinding时尤其省心。3.2 setValue和postValue的区别LiveData提供了两个公开方法更新值。setValue只能在主线程调用调用后立即同步通知所有处于活跃状态的观察者postValue可以在任意线程调用内部会把更新动作post到主线程任务队列由主线程来真正更新。很多人搞不清什么时候用哪个我的建议是只要你在协程或回调里默认用postValue如果已经切回主线程用setValue。postValue有一个容易被坑的语义它只保留最后一个值连续post多个值时中间值可能会被合并丢弃。比如一个加载进度的例子1秒内从10%快速推进到90%UI最终可能只看到90%看不到中间状态。如果你的业务需要展示每个中间值直接给LiveData传一个进度数据类或者保证在主线程使用setValue把这个时序问题交给场景设计去规避。3.3 DataBinding为什么能省掉手动observe如果在XML里写{viewModel.userName}DataBinding会自动注册一个Observer到对应的LiveData上注册所用LifecycleOwner来自Binding的lifecycleOwner属性。这样我们就不用再在Activity里写viewModel.userName.observe(this) { name - binding.tvName.text name }关键点在于XML里绑定LiveData依赖binding.lifecycleOwner。有些人只设置了binding.viewModel忘了设置lifecycleOwner运行时总在奇怪为什么数据不自动刷新。设置完了LiveData的所有生命周期行为才会接管数据更新。我遇到过另一个问题XML里绑了LiveData代码里又手动observe了一遍结果同一个数据变化导致两次赋值虽然视觉上可能没区别但回调日志会暴露问题。建议能在XML里绑定的就用XML需要复杂逻辑的UI再在代码里observe并且XML里不要再绑定同一个字段。3.4 一次性事件怎么用Event包装LiveData的重放机制对恢复UI状态很方便但也会带来一个反直觉行为配置变更后新的observer会立刻收到上一次的值。像Snackbar、Toast、导航这类动作转屏后会重新执行一遍用户会觉得莫名其妙。处理方式一般是用Event包装类里面的数据在被消费一次后就不再下发。我常用的实现class Eventout T(private val content: T) { var hasBeenHandled false private set fun getContentIfNotHandled(): T? { return if (hasBeenHandled) null else { hasBeenHandled true content } } }ViewModel里用MutableLiveDataEventString()UI层observe后调用getContentIfNotHandled()。这段代码其实不是线程安全的但在UI主线程回调场景下基本够用。如果想要更安全可以用AtomicBoolean或者直接上Kotlin Flow的SharedFlow。4. DataBinding把UI声明成数据和XML的关系4.1 DataBinding和ViewBinding怎么选ViewBinding是Google后来提供的轻量绑定方案只生成View的引用没有表达式功能DataBinding不仅包含ViewBinding的功能还支持在XML里写数据表达式、双向绑定、事件绑定、BindingAdapter。如果打算走“ViewModel LiveData DataBinding”这套架构肯定要选DataBinding。现在新项目的官方推荐是优先ViewBinding但ViewBinding解决不了数据驱动渲染问题。我的建议是只要页面数据来自ViewModel且需要自动刷新就用DataBinding如果只是静态布局用ViewBinding甚至findViewById也不丢人。不要为了用而用。4.2 在Android Studio中启用DataBinding以现在的Android Studio来说模块build.gradle的写法是android { buildFeatures { dataBinding true } }开启后任何根标签为layout的XML都会在编译期生成对应的Binding类。比如activity_user.xml生成ActivityUserBinding。这个类里的字段就是XML中定义的那些控件以及setViewModel之类的方法。很多人在开启后遇到类找不到大概率是没做干净构建。我一般是改完gradle后直接Rebuild Project而不是只点Sync。另外如果项目里同时开了ViewBinding和DataBinding注意它们生成的类可能会重叠建议确认清楚依赖在哪避免命名冲突。4.3 从{ }到{ }单向绑定和双向绑定DataBinding表达式的入口在布局根标签layout xmlns:androidhttp://schemas.android.com/apk/res/android data variable nameviewModel typecom.example.UserViewModel / /data LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text{viewModel.userName} / EditText android:layout_widthmatch_parent android:layout_heightwrap_content android:text{viewModel.userName} / /LinearLayout /layout{viewModel.userName}表示数据变化会推给TextView这是从ViewModel到View的单向数据流。{viewModel.userName}多一个号表示双向绑定既会在数据变化时更新EditText也会在EditText输入时把字符串回写到ViewModel。这个多出来的等号我一开始老写漏一漏就陷入“界面改了但数据源没变”的迷局。表达式里还支持常见运算符。比如空合并android:text{viewModel.userName ?? 未登录}三目运算符android:visibility{viewModel.isVip ? View.VISIBLE : View.GONE}但要注意不要把复杂业务逻辑塞进XML。比如多次方法调用、操作集合这些应该放到ViewModel或BindingAdapter里XML只做声明。4.4 设置lifecycleOwner是必须的一步拿到Binding后三行代码不能少binding DataBindingUtil.setContentView(this, R.layout.activity_user) binding.lifecycleOwner this binding.viewModel viewModel第一行是绑定布局生成Binding。第二行是设置LifecycleOwner让LiveData表达式可以根据页面生命周期自动订阅和清理。第三行是传递ViewModel实例给布局变量。为什么不设置lifecycleOwner就会出现问题因为XML里括住的LiveData表达式本质上也要注册Observer而不带LifecycleOwner的Observer是observeForever的模式不受生命周期管控。页面销毁后这个观察者还挂着数据一变旧View就会被更新出现泄漏风险。跑几次内存分析就会看到一堆DataBinding的监听对象挂着Activity引用。4.5 事件绑定和BindingAdapterDataBinding里绑事件非常直接Button android:onClick{() - viewModel.onSave()} /如果列表点击需要带参数可以写{(user) - viewModel.onItemClick(user)}。自定义属性用BindingAdapter。比如图片加载BindingAdapter(app:imageUrl) fun loadImage(imageView: ImageView, url: String?) { if (url ! null) { Glide.with(imageView.context).load(url).into(imageView) } }BindingAdapter是个静态函数注意第一个参数是被绑定属性的那个View第二个参数是XML里表达式的值。所有使用该自定义属性的XML都会调用这个方法。方法名随意类型签名才是关键。用着用着会发现这个机制非常适合提取一些跨页面通用的UI逻辑比如为空显示默认图、时间格式化等。5. 组合实战从ViewModel到DataBinding的完整落地5.1 构建一个简单的用户信息页我用一个常见的用户页来串一遍完整流程。功能要求显示当前用户名用户点击保存后把用户名改成新值保存过程中显示一个ProgressBar。页面结构非常小但足够验证整套链路。ViewModel写起来极其简单class UserViewModel : ViewModel() { val userName MutableLiveData(老张) val isSaving MutableLiveData(false) fun onSave() { isSaving.value true userName.value 已保存的新昵称 isSaving.value false } }实际项目中可能会有网络请求、动态变化但这几行已经体现了DataBinding参与的写法viewModel直接暴露数据UI不用关心谁修改了它。5.2 布局里的绑定写法布局文件的核心部分layout xmlns:androidhttp://schemas.android.com/apk/res/android data variable nameviewModel typecom.example.app.UserViewModel / import typeandroid.view.View / /data LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationvertical TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text{viewModel.userName} / ProgressBar android:layout_widthwrap_content android:layout_heightwrap_content android:visibility{viewModel.isSaving ? View.VISIBLE : View.GONE} / Button android:layout_widthwrap_content android:layout_heightwrap_content android:text保存 android:onClick{() - viewModel.onSave()} / /LinearLayout /layout这里import引入View类后表达式里才能直接用View.VISIBLE。如果不加import就要写成android.view.View.VISIBLE太长了。onClick用了lambda表达式写法DataBinding会把点击事件映射到viewModel.onSave方法。5.3 Activity代码压缩到极致有了DataBindingActivity的onCreate只需要三行核心逻辑class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityUserBinding private val viewModel: UserViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding DataBindingUtil.setContentView(this, R.layout.activity_user) binding.lifecycleOwner this binding.viewModel viewModel } }不需要findViewById不需要setText不需要监听EditText。整个页面的初始数据和更新逻辑都转移到了ViewModel和XML里。如果之后要增加一个昵称输入框只需在布局里加个EditText并双向绑定到userNameActivity完全不用改。5.4 executePendingBindings什么时候用DataBinding默认会在下一帧布局前执行绑定这意味着在某些需要“立刻拿到绑定后控件状态”的场景数据会滞后一个帧。比如在onCreate里设置数据后马上判断控件宽度可能拿到旧值。这时可以调用binding.executePendingBindings()强制立即执行挂起的绑定任务。这个API我在列表页里用得比较多因为RecyclerView的item绑定后需要立刻算高度如果不执行可能会出现闪烁或item高度不对。日常的普通页面多数情况下不需要主动调。5.5 转屏和前后台切换的实测我实际测试的时候专门转了两次屏发现TextView上的昵称在转屏前是“新的昵称”转屏后依然是“新的昵称”。这个“依然”是LiveData的功劳新Activity创建后Binding里的LifecycleOwner把Observer注册上去LiveData立刻回放当前值所以UI没有任何延迟地恢复了。切到后台再回来因为页面可能进入过STOPPED状态LiveData会在进入STARTED后重新把最新值发给UI。如果值没有变化UI不会多次刷新。这一点比手写一套“保存状态恢复状态”要可靠得多。6. 生命周期管理与数据绑定联动的关键细节6.1 先有lifecycleOwner才有生命周期感知绑定布局后设置lifecycleOwner这件事怎么强调都不过分。LiveData在DataBinding中自动订阅用的是Binding内部注册的观察者而这个观察者最终挂在谁身上取决于lifecycleOwner属性。如果不设置lifecycleOwnerDataBinding仍会在值变化时刷新UI但那是通过Observable类的OnPropertyChangedCallback机制没有生命周期拦截。结果是页面已经进入后台数据一来UI照常更新页面销毁后观察者还留在那里。所以我在给团队定的代码规范里就有一条凡是在XML里绑LiveData的页面必须在onCreate里设置lifecycleOwner。6.2 配置变更时数据是怎么流动的当转屏发生时系统先暂停并销毁旧Activity但ViewModelStore被保留。新Activity创建后by viewModels()会从同一个Store里取出旧ViewModel然后执行onCreate生成新的Binding设置lifecycleOwner和ViewModel。这时候LiveData发现自己有了新的活跃观察者就把最新值又派发了一次。于是新界面自动显示旧数据。这个链路里没有一个方法是程序员手写的但每段都有框架兜底。理解了这个过程你就明白为什么LiveData重放机制、ViewModel保留机制、DataBinding绑定都是缺一不可的。6.3 内存泄漏的几个红线我排查过很多次内存泄漏和这套架构相关的常见泄漏有三个。第一ViewModel持有Activity或View这是最常见也最严重的。第二LiveData的observeForever忘了removeObserver这通常在不需要生命周期参与的场景出现但很多人直接用forever图省事最后把自己套进去。第三BindingAdapter里如果写成内部类而非顶层函数并且访问了外部Activity字段同样会持有Activity。我的建议是把BindingAdapter都写成顶层函数或者放在单独的文件里。ViewModel里只保留Application和可清理的资源。页面销毁流程做好后用Memory Profiler反复进出页面几次堆内存应该保持平稳而不是持续爬升。6.4 进程被杀后的恢复SavedStateHandle上面讲的都是配置变更但App被系统杀死是另一回事。进程一旦被回收ViewModelStore本身也没了数据必须从外部恢复。Jetpack为此提供了SavedStateHandle可以在ViewModel创建时注入一个类似Bundle的保存容器把少量、可序列化的关键状态放进去。做法是在Factory里使用SavedStateViewModelFactory或者用by viewModels时的默认工厂很多情况下会自动支持。然后在ViewModel里可以val userId savedStateHandle.getString(userId)等到下次进程重建这些状态自动恢复。SavedStateHandle非常适合保存当前Tab位置、搜索关键词、表单关键字段而不适合放大对象或复杂列表那些还是靠本地缓存恢复更合理。7. 常见问题与排查技巧实录7.1 ViewModel到底是不是同一个判断方法很直观在ViewModel构造函数里打日志或计数。转屏两次如果只打印一次说明是同一个实例。finish再进入会再打印一次说明新的生命周期开始。在Fragment里还要注意宿主是谁。如果两个Fragment挂在同一个Activity下用requireActivity()作为owner它们共享一个ViewModel数据天然互通。如果Fragment自己作为owner则各自独立。业务上要共享数据就用Activity作为owner。7.2 LiveData不回调的排查顺序遇到LiveData不回调我一般按这个顺序查先确认页面是否进入STARTED状态数据是否真的被set/Post过再确认是否在子线程setValue抛异常没被看到然后检查是否用错了变量比如XML绑定的是旧的observable代码里改的是新的。还有一个很隐蔽的情况如果你用observeForever并且在回调里remove了另一个observer容易引发ConcurrentModification异常可以改用postValue延迟或加锁。LiveData的粘性特性也可能让人困惑新观察者注册后会立刻收到旧值这常常被误认为“为什么我刚进来就触发一次”。如果你不想要这种粘性就把事件包装成Event或者使用Kotlin Flow中的SharedFlow。7.3 DataBinding生成类找不到怎么办标准排查流程确认build.gradle里开了dataBinding true确认XML根标签是layout确认没有在data外引用未定义的变量。然后执行Clean Project - Rebuild Project。如果还找不到检查是不是模块间的依赖没搭好比如Binding类在library模块中生成但主工程没有依赖这个library。还有一个小坑Android Studio的编辑器偶尔会标红找不到Binding类但只要compileSdkVersion和依赖没问题Rebuild之后就好了。别被编辑器红波浪线劝退以Gradle构建结果为准。7.4 EditText双向绑定不生效双向绑定不生效十有八九是把写成了。另一个可能是数据源不是可观察类型。双向绑定要求属性是可被观察且可被反向写回的用普通String根本编译不过去。出现这种情况要么改成MutableLiveDataString要么用ObservableFieldString。如果你是自定义View需要配合InverseBindingAdapter实现反向写回这个门槛更高但思路和BindingAdapter类似。7.5 带参ViewModel在Fragment和Activity中的写法带参ViewModel的Factory写法前面已经给了例子。在Fragment里可以用by viewModels外加Factoryprivate val vm: UserViewModel by viewModels { UserViewModel.Factory(arguments?.getString(id) ?: ) }还有一种更省代码的方式是使用CreationExtras在默认工厂里通过SavedStateHandle自动接收arguments。我实际项目里推荐通过SavedStateHandle接收参数因为进程重建时参数也不会丢而手工Factory在进程重建时可能会因为参数来源失效而出问题。7.6 RecyclerView中DataBinding的注意点列表item用DataBinding时我在onBindViewHolder里一般这样写val binding ItemUserBinding.bind(itemView) binding.viewModel itemViewModel binding.executePendingBindings()executePendingBindings是为了让item的绑定数据立刻生效方便RecyclerView计算高度和避免闪屏。还有一个容易忽略的是item布局里的监听器可能被复用了要保证每次绑定时传入的是当前item不是旧引用。如果你用DiffUtil ListAdapter每次数据变化会多出binding重新绑定的开销但性能可控。真正要注意的是不要每次onBind都创建一个新的ViewModel或LiveData这样会造成大量临时对象。Item的ViewModel应该尽量复用或者干脆只绑定一个纯数据类。8. 这套架构的边界和后续演进8.1 什么时候不建议用DataBindingDataBinding不是银弹。如果页面是完全静态的或者只有两三个控件交互直接用ViewBinding甚至findViewById就够。DataBinding的表达式虽然方便但调试的时候断点难以覆盖到XML内部出现问题排查路径比较长。复杂的业务逻辑放到XML表达式里更要克制。一旦表达式中出现方法调用容易让布局难以阅读、单元测试困难。我见过一个item布局里塞了十几行表达式改一个需求要翻半天XML。要让XML只做映射逻辑都进ViewModel或BindingAdapter。8.2 配套依赖与调试建议在使用这套架构时我习惯引入lifecycle-livedata-ktx、lifecycle-viewmodel-ktx、lifecycle-runtime-ktx。如果要做页面级测试lifecycle-viewmodel-testing里提供InstantTaskExecutor方便单元测试里控制LiveData的异步派发。DataBinding和Kotlin协程配合时可以考虑把网络请求放在ViewModel的viewModelScope里用LiveData把结果抛给UI。近年Google也推荐用Kotlin Flow替代LiveData但LiveData在简单场景下的生命周期感知能力还是很有优势。我的原则是状态恢复类数据适合LiveData需要事件流和复杂变换的时候切Flow。8.3 后续还可以尝试的演进路线如果你的页面进一步复杂化可以引入StateFlow Compose或者继续使用LiveData DataBinding两条路线都能走。现在不少新项目已经切到Jetpack Compose写法更声明式但我的经验是现有维护中的项目迁移到Compose成本不小反而把ViewModel LiveData DataBinding这套组合吃透在中期项目里依然很有价值。从维护角度下一步可以逐步把仓库层改成Repository模式让ViewModel只面向Repository接口取数据再配合Hilt做依赖注入。总之页面架构只是起点数据层、依赖注入层可以按团队节奏慢慢演进。最后聊点个人经验吧。这套架构真正落地后我最深的体会是少写了大量胶水代码。以前Activity动不动几百行现在很多页面代码不超过四五十行剩下的都在ViewModel和XML声明里。调试的时候只要想清楚“数据在谁手里”“谁在观察它”“生命周期是否匹配”绝大多数运行时问题都能快速定位。如果你刚接触这套组合建议不要同时引入太多概念。先在项目里把ViewModel用起来再给列表加LiveData最后把Activity的XML改成DataBinding。一步步来遇到问题时排查范围会小很多。我现在带新人也是用这个顺序带基本几周就能从只会findViewById过渡到能独立写架构完整的新页面。等你完全适应这种“数据驱动UI”的写法再回头去看那堆手动同步代码应该会有种回不去了的感觉。
返回列表