如何解决使用GlobalScope.launch仍出现的ANR问题及结果获取方法
解决ANR问题及后台任务结果获取方案
核心问题分析
你的ANR本质是三个问题叠加导致:
- 使用
GlobalScope无生命周期绑定,线程管理不可控,易引发资源泄漏或线程池耗尽 - 后台任务中的三个蓝牙操作以
GlobalScope.launch异步启动,未等待全部完成就提前触发UI更新,实际阻塞任务仍在后台占用资源 - UI更新逻辑未处理异常,导致任务失败时进度条无法隐藏,同时ANR触发后系统加载资源时因主线程卡死抛出次生异常
一、重构协程生命周期管理
废弃GlobalScope,改用与Activity生命周期绑定的CoroutineScope,避免泄漏并可控取消任务:
// 在Activity中定义 private val coroutineScope = CoroutineScope(Dispatchers.Main + Job()) // Activity销毁时取消所有协程 override fun onDestroy() { super.onDestroy() coroutineScope.cancel() }
二、重写后台任务逻辑
将蓝牙相关阻塞操作全部放在IO线程,并确保等待三个子任务全部完成后再返回结果:
1. 修改Demo类的后台方法为挂起函数
class Demo { suspend fun testBackGround(): String { return coroutineScope { // 三个蓝牙任务并行执行,等待全部完成 val listTask = async(Dispatchers.IO) { listBluetoothDevices() } val connectTask = async(Dispatchers.IO) { connectBluetoothDevice() } val channelTask = async(Dispatchers.IO) { establishChannelConnection() } // 等待所有任务结束 listTask.await() connectTask.await() channelTask.await() "蓝牙操作全部完成" } } // 三个具体阻塞任务,确保在IO线程执行 private fun listBluetoothDevices() { // 列出蓝牙设备的同步阻塞代码 } private fun connectBluetoothDevice() { // 连接蓝牙设备的同步阻塞代码 } private fun establishChannelConnection() { // 建立通道连接的同步阻塞代码 } }
2. 修改Activity中的后台调用函数为挂起函数
private suspend fun backGroundCall(): String { return Demo().testBackGround() }
三、按钮点击事件的正确实现
在主线程启动协程,切换到IO线程执行后台任务,完成后安全更新UI,并处理异常:
button.setOnClickListener { // 先显示进度条,隐藏按钮 activityBinding.progressBlock.visibility = View.VISIBLE activityBinding.buttnRow.visibility = View.GONE coroutineScope.launch { try { // 切换到IO线程执行所有后台任务 val status = withContext(Dispatchers.IO) { backGroundCall() } // 回到主线程更新UI activityBinding.pairLogs.visibility = View.VISIBLE activityBinding.pairLogs.text = status activityBinding.demostatus.visibility = View.VISIBLE } catch (e: Exception) { // 捕获异常,更新UI显示错误信息 activityBinding.pairLogs.text = "操作失败:${e.message}" } finally { // 无论成功失败,都隐藏进度条、显示按钮 activityBinding.progressBlock.visibility = View.GONE activityBinding.buttnRow.visibility = View.VISIBLE } } }
四、之前尝试方案的问题解析
- Thread+runOnUIThread未触发:大概率是Thread执行时抛出未捕获异常,导致后续UI更新代码未执行。需给Thread添加异常捕获逻辑,但这种方式生命周期管理繁琐,不如协程简洁。
- GlobalScope.launch(Main)+withContext未触发:后台任务未标记为挂起函数,阻塞代码直接在主线程执行,导致主线程卡死,withContext逻辑根本无法运行。
- async+await仍ANR:
GlobalScope线程不受控,且三个蓝牙任务是fire-and-forget模式,实际阻塞操作仍在后台占用资源,最终引发线程池耗尽或主线程卡死。
五、次生资源加载异常说明
你遇到的ResourcesManager加载APK失败异常是ANR的次生问题:系统弹出ANR对话框时需要加载应用资源,但此时主线程已卡死,无法处理资源加载请求,因此抛出该异常。解决ANR后,此异常会自动消失。
内容的提问来源于stack exchange,提问作者Kalai Selvi
相关产品推荐
相关产品推荐

