Android ViewModel中lateinit LiveData初始化竞态问题优化方案
问题根因
lateinit var 只适合初始化时机确定、仅赋值时机晚于构造函数的非空属性,完全不适用你这种依赖协程异步计算结果的场景。不管加多少层回调,只要两个异步操作(回调赋值、协程初始化)没有强制的先后顺序保证,就永远存在竞态风险,而且你原有逻辑还存在分支漏赋值的隐患——如果两个let块的判空条件都不命中,协程执行完timeStamps依然是未初始化状态。
最优解决方案
直接使用 lifecycle-livedata-ktx 提供的liveData协程构建器,从根源上移除lateinit,不需要手动处理任何时序对齐:
- 首先确保项目依赖了对应版本的lifecycle-livedata-ktx组件(和你项目当前的lifecycle版本匹配即可)
- 重写ViewModel代码,把
timeStamps声明为不可变的val类型LiveData,所有异步计算逻辑都在liveData块内执行:
class MainViewModel : ViewModel() { val timeStamps: LiveData<List<TimeStamp>> = liveData { // 先发送初始空列表,UI可以根据这个状态展示加载态/空态 emit(emptyList()) val db = RoomDB.getInstance(getApplication<Application>()).timeStampDao() val lastTimeStamp = db.getLast() val targetTime = when { lastTimeStamp == null -> Calendar.getInstance().timeInMillis lastTimeStamp.instant < setToStartOfDay(Calendar.getInstance()).timeInMillis && lastTimeStamp.action == ACTION.END_WORK -> Calendar.getInstance().timeInMillis else -> { val lastStartWork = db.getLastAction(ACTION.START_WORK) if (lastStartWork == null) { emit(emptyList()) return@liveData } val startOfDay = setToStartOfDay(initCalendar(lastStartWork.instant)).timeInMillis val firstStartAfter = db.getFirstActionAfter(ACTION.START_WORK, startOfDay) firstStartAfter?.instant ?: run { emit(emptyList()) return@liveData } } } // 自动订阅Room返回的LiveData,数据更新时自动转发给UI,协程/ViewModel销毁时自动取消订阅无泄漏 emitSource(db.getAllAfterLive(targetTime)) } }
- Activity侧的代码不需要做任何特殊处理,直接在
onCreate里正常observe即可:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 其他初始化逻辑 viewModel.timeStamps.observe(this) { list -> recordsAdapter.submitList(list) } }
方案优势
- 不存在任何竞态问题:
timeStamps是val属性,ViewModel实例化完成后就可以直接访问,永远不会触发未初始化异常 - 生命周期安全:
liveData块的协程会在LiveData有活跃观察者时自动启动,没有活跃观察者时自动取消,emitSource会自动管理上游Room LiveData的注册与解绑,不会造成内存泄漏 - 逻辑闭环:语法层面强制你处理所有分支的返回值,不会出现原有逻辑中分支漏赋值的问题
- 代码更简洁:不需要定义额外的回调、标记位,所有逻辑收敛在LiveData的初始化块内
如果项目暂时无法引入lifecycle-livedata-ktx,可以退而求其次把timeStamps声明为初始值为空列表的MutableLiveData,在协程里计算出目标时间后,手动观察Room返回的LiveData并把数据转发到对外暴露的不可变LiveData中,注意要在onCleared里移除观察者避免泄漏,整体代码量会比用liveData构建器多不少。
内容的提问来源于stack exchange,提问作者user1785730
相关产品推荐
相关产品推荐

