Android Compose Navigation与ViewModel生命周期异常问题咨询
Jetpack Compose 导航与ViewModel生命周期异常问题
我刚接触Compose时,初看之下它和我非常喜欢的SwiftUI十分相似,但实际投入使用后很快遇到了诸多问题,我需要找到正确的使用方式才能发挥其能力,以下是我遇到的问题之一:
package org.test.android.kotlin.compose.ui import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import org.test.android.kotlin.compose.ui.theme.MbiKtTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MbiKtTheme { val navController = rememberNavController() // <Edit #1> // Navigator.route.collectAsState("").value.takeIf { it.isNotEmpty() }?.also { navController.navigate(it) } // Navigator.route.observe(this, { route -> navController.navigate(route) }) // </Edit #1> // <Edit #2> Navigator.route.collectAsState("").value.takeIf { it.isNotEmpty() }?.also { navController.popBackStack() navController.navigate(it) } // </Edit #2> Surface(color = MaterialTheme.colors.background) { NavHost( navController = navController, startDestination = "setup" ) { composable(route = "setup") { SetupScreen() } composable(route = "progress") { ProgressScreen() } } } } } } } // 该实现当前简单示例中并非必要,但在大型模块化项目中是必须的 object Navigator { // <Edit #1> val route = MutableSharedFlow<String>(0, 1, BufferOverflow.DROP_OLDEST) //val route: MutableLiveData<String> = MutableLiveData() // </Edit #1> } class SetupViewModel : ViewModel() { init { Log.d(toString(), "Create") } override fun onCleared() { Log.d(toString(), "Destroy") } override fun toString(): String { return "SetupViewModel" } } @Composable fun SetupScreen(model: SetupViewModel = viewModel()) { Column( modifier = Modifier .fillMaxSize() .padding(all = Dp(8f)) ) { Text(text = "Setup") Spacer(modifier = Modifier.weight(1f)) Button(onClick = { Navigator.route.tryEmit("progress") }, modifier = Modifier.fillMaxWidth()) { Text(text = "Register") } } } class ProgressViewModel : ViewModel() { init { Log.d(toString(), "Created") } override fun onCleared() { Log.d(toString(), "Cleared") } override fun toString(): String { return "ProgressViewModel" } } @Composable fun ProgressScreen(model: ProgressViewModel = viewModel()) { Column( modifier = Modifier .fillMaxSize() .padding(all = Dp(8f)) ) { Text(text = "Progress") Spacer(modifier = Modifier.weight(1f)) Button(onClick = { Navigator.route.tryEmit("setup") }, modifier = Modifier.fillMaxWidth()) { Text(text = "Abort") } } } @Preview(showBackground = true) @Composable fun DefaultPreview() { MbiKtTheme { SetupScreen() } }
问题复现与描述
- 在两个Compose页面之间导航并旋转屏幕,观察LogCat中两个ViewModel的创建、销毁日志
- 问题一:从一个页面导航到另一个页面时,前一个页面的ViewModel从未回调
onCleared销毁(显然因为Activity仍存活),这在大型项目中是完全不可接受的 - 问题二:只要完成过一次页面跳转,每次旋转屏幕ViewModel都会被重新创建,这同样不可接受
我了解Compose目前尚未完全成熟,部分组件仍处于alpha版本,不确定这是Compose本身的Bug,还是我对大型模块化项目中Compose的使用方式存在误解,希望得到解决方案。
问题更新
- 补充1(2021/09/05):通过相关文章指引,我已修复旋转屏幕时ViewModel重建的问题(暂不清楚背后原理),目前仅剩ViewModel生命周期不符合预期的问题。
- 补充2(2021/09/13):通过回答指引,我已实现ViewModel生命周期符合预期的效果,我禁用了回退栈,该功能在我的业务场景中本身不需要,还会导致UI和底层模型逻辑混乱。
内容的提问来源于stack exchange,提问作者Jiří Křivánek
相关产品推荐
相关产品推荐

