Android MVVM模式下ViewModel变量变更后打开弹窗/启动Activity异常
问题根因定位
- 共享ViewModel生命周期绑定宿主Activity,
currentDevice变更时如果第二个Fragment处于未初始化/不可见/View已销毁状态,回调触发的后续逻辑会持有无效的Context/View引用 - LiveData监听未使用
viewLifecycleOwner,而是用Fragment实例作为LifecycleOwner,Fragment View销毁后监听依然存活,IO操作的残余协程会连带上下文失效 - 搭载Android 10的小米设备对Dialog/Activity启动的上下文校验规则比原生系统更严格,只要上下文关联的Window/Display处于无效状态,就会抛出异常
解决方案
1. 修正LiveData监听规则
第二个FragmentonViewCreated中监听共享ViewModel必须使用viewLifecycleOwner,并且指定仅当Fragment处于活跃状态时才接收回调,避免无效触发:
// 错误写法 sharedViewModel.currentDevice.observe(this) { ... } // 正确写法,仅Fragment RESUMED状态下接收回调 sharedViewModel.currentDevice.observe(viewLifecycleOwner, Lifecycle.State.RESUMED) { device -> localViewModel.updateCurrentDevice(device) localViewModel.fetchChannels() }
如果是ViewPager承载Fragment,还可以额外加可见性校验:
if (isResumed && !isHidden && isMenuVisible) { // 执行后续逻辑 }
2. 协程绑定View生命周期
fetchChannels必须使用viewLifecycleOwner.lifecycleScope启动,不要用全局作用域,View销毁时自动取消未完成的IO请求,避免协程泄漏:
// Fragment中调用拉取通道逻辑 viewLifecycleOwner.lifecycleScope.launch { val channels = localViewModel.fetchChannels(device) // UI更新逻辑写在这里,自动绑定生命周期 } // ViewModel中的拉取逻辑改造 suspend fun fetchChannels(device: Device) = withContext(Dispatchers.IO) { sdk.getChannelsAsync(device) }
3. 新增异常捕获与校验
给SDK调用加异常捕获,排查SDK内部是否隐藏了上下文相关操作:
suspend fun fetchChannels(device: Device): List<Channel>? { return runCatching { withContext(Dispatchers.IO) { sdk.getChannelsAsync(device) } }.onFailure { Log.e("ChannelFetch", "拉取失败", it) }.getOrNull() }
启动Activity/显示Dialog前增加上下文有效性校验,适配小米Android 10的严格校验规则:
// 显示Dialog前校验 if (isAdded && activity?.isFinishing == false && activity?.window?.decorView?.display != null) { AlertDialog.Builder(requireContext()) .setTitle("操作提示") .setMessage("操作完成") .show() } // 启动Activity前校验 if (isAdded && activity?.isFinishing == false) { startActivity(Intent(requireContext(), TargetActivity::class.java)) }
内容的提问来源于stack exchange,提问作者anon37894203
相关产品推荐
相关产品推荐

