跨页面传值导致Pokédex应用崩溃问题排查求助
宝可梦图鉴应用跨页面传值导航崩溃问题
项目背景
开发一款类Pokédex的应用,支持查询并展示任意宝可梦的详细信息。
问题现象
点击PokemonInfoLayout.kt里的「Find Location」按钮后应用直接崩溃,只有在跨页面传值导航的时候才会触发;不带参数的页面跳转完全正常。已经确认两个关键点:
- 传给位置页面的参数绝对不为空
- 网络数据获取功能没问题,崩溃只和传值导航的场景绑定
崩溃日志
进程触发SIG:9信号,后续出现Channel损坏提示,补充日志显示存在Handler向已销毁线程发送消息的异常。
相关代码
// PokedexScreen Composable @Composable fun PokedexScreen(navController: NavController) { // 原代码内容 } // PokemonLocationDetailScreen Composable @Composable fun PokemonLocationDetailScreen(navController: NavController, pokemonName: String?) { // 原代码内容 } // PokemonInfoLayout Composable @Composable fun PokemonInfoLayout(pokemon: Pokemon, navController: NavController) { Button(onClick = { navController.navigate("location_detail/${pokemon.name}") }) { Text("Find Location") } // 其他布局代码 } // AppNavigation @Composable fun AppNavigation() { val navController = rememberNavController() NavHost(navController = navController, startDestination = "pokedex") { composable("pokedex") { PokedexScreen(navController) } composable("location_detail/{pokemonName}") { backStackEntry -> val pokemonName = backStackEntry.arguments?.getString("pokemonName") PokemonLocationDetailScreen(navController, pokemonName) } } }
问题排查方向与修复方案
大概率触发崩溃的原因
- 线程生命周期不匹配:日志里提到Handler向已销毁线程发消息,基本可以确定是在Composable里用了非主线程的Handler,或者后台线程持有了已销毁页面的引用。导航后原页面Composable被销毁,后台任务完成后还尝试更新UI或发消息,直接导致崩溃。
- Channel管理不当:Channel损坏提示说明Channel已经关闭,但还有代码在尝试往里面发数据。大概率是传值用的Channel没做好生命周期管理,导航后Channel被关闭,但后续操作还在访问它。
- 导航参数处理有线程安全问题:虽然参数非空,但接收页面可能在后台线程直接读取参数并执行操作,引发线程冲突。
具体修复方法
- 规范Handler使用:所有和UI相关的Handler必须绑定主线程Looper,别在后台线程瞎创建。用协程的话,就用
withContext(Dispatchers.Main)切回主线程再做UI操作或发消息:// 正确示例:绑定主线程Looper val handler = Handler(Looper.getMainLooper()) // 或者用协程更稳妥 viewModelScope.launch(Dispatchers.IO) { val locationData = fetchLocationData() withContext(Dispatchers.Main) { // 这里做UI更新或导航相关操作 } } - 管好Channel的生命周期:如果用Channel传数据,一定要在Composable销毁时(比如用
DisposableEffect)关闭Channel,避免后续操作访问已关闭的Channel:@Composable fun PokemonInfoLayout(pokemon: Pokemon, navController: NavController) { val locationChannel = remember { Channel<String>() } DisposableEffect(Unit) { onDispose { locationChannel.close() } } // 其他业务逻辑 } - 安全处理导航参数:在接收参数的页面Composable里,用
LaunchedEffect处理参数,确保数据加载逻辑在主线程执行,避免线程冲突:@Composable fun PokemonLocationDetailScreen(navController: NavController, pokemonName: String?) { LaunchedEffect(pokemonName) { pokemonName?.let { val locationData = fetchLocationData(it) // 更新UI状态 } } // 布局代码 } - 排查SIG:9的根源:SIG:9一般是进程被系统强制杀掉,大概率是内存泄漏或者ANR导致的。用Android Studio的Profiler查一下内存使用情况,看看是不是页面销毁后还持有大量对象引用。
内容的提问来源于stack exchange,提问作者Prakhar_Pathak
相关产品推荐
相关产品推荐

