Thread.UncaughtExceptionHandler调用navigate()致App无响应问题求助
全局异常捕获后调用导航导致应用无响应的问题解决
问题场景
通过DmsExceptionHandler实现全局异常捕获,但捕获异常后调用navigateToSignIn()导航到登录页时,应用未崩溃却停止响应:UI组件(如底部导航栏)可点击并显示波纹,但无法切换页面。若不在uncaughtException中调用导航方法,应用不会出现该问题。
相关代码
异常处理器代码:
internal class DmsExceptionHandler @Inject constructor( private val context: Context, private val appState: DmsAppState, ) : Thread.UncaughtExceptionHandler { override fun uncaughtException( thread: Thread, exception: Throwable, ) { when (exception) { is CommonException.SignInRequired -> signInRequired() else -> { signInRequired() // todo replace } } } private fun signInRequired() { appState.navigateToSignIn() } }
导航逻辑代码:
internal fun DmsAppState.navigateToSignIn() { println("NAVIGATED TO SIGN IN") this.navController.navigate(DmsRoute.Auth.route) {/* popUpTo(this@navigateToSignIn.navController.graph.startDestinationId) { saveState = true }*/ } }
问题原因
- 线程状态异常:
uncaughtException方法在抛出异常的线程(通常是主线程)中执行,此时主线程已因未处理异常处于不稳定状态,消息队列的正常循环被打断。直接在该方法内执行导航这类UI操作,会导致主线程无法恢复正常事件循环。 - 未调用默认处理器:当前异常处理器覆盖了系统默认逻辑,但未调用默认的未捕获异常处理器,导致系统无法完成线程的异常清理,进一步加剧线程状态异常。
- 导航上下文无效:异常状态下的主线程无法正常处理导航栈更新、视图渲染等逻辑,仅能响应View本地的点击波纹动画。
解决方案
方案1:通过Handler调度导航并调用默认处理器
修改异常处理器,将导航操作通过Handler post到主线程正常消息队列,同时调用系统默认处理器完成线程清理:
internal class DmsExceptionHandler @Inject constructor( private val context: Context, private val appState: DmsAppState, ) : Thread.UncaughtExceptionHandler { // 保存系统默认异常处理器 private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() override fun uncaughtException( thread: Thread, exception: Throwable, ) { when (exception) { is CommonException.SignInRequired -> handleSignInRequired() else -> handleSignInRequired() // 替换为你的其他异常处理逻辑 } // 必须调用默认处理器,让系统完成线程异常清理 defaultHandler?.uncaughtException(thread, exception) } private fun handleSignInRequired() { // 将导航操作post到主线程正常消息队列 Handler(Looper.getMainLooper()).post { appState.navigateToSignIn() } } }
方案2:使用Coroutine在主线程安全执行导航
若项目使用Coroutine,可通过MainScope调度导航操作,效果与Handler一致:
internal class DmsExceptionHandler @Inject constructor( private val context: Context, private val appState: DmsAppState, ) : Thread.UncaughtExceptionHandler { private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() private val mainScope = MainScope() override fun uncaughtException( thread: Thread, exception: Throwable, ) { when (exception) { is CommonException.SignInRequired -> handleSignInRequired() else -> handleSignInRequired() } defaultHandler?.uncaughtException(thread, exception) } private fun handleSignInRequired() { mainScope.launch { appState.navigateToSignIn() } } }
额外优化:完善导航安全检查
在导航逻辑中增加状态检查,避免无效导航:
internal fun DmsAppState.navigateToSignIn() { println("NAVIGATED TO SIGN IN") // 检查导航控制器是否活跃,避免重复导航 if (navController.currentDestination?.id != DmsRoute.Auth.id) { navController.navigate(DmsRoute.Auth.route) { popUpTo(navController.graph.startDestinationId) { saveState = true } launchSingleTop = true } } }
内容的提问来源于stack exchange,提问作者JunJaBoy
相关产品推荐
相关产品推荐

