You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决使用GlobalScope.launch仍出现的ANR问题及结果获取方法

解决ANR问题及后台任务结果获取方案

核心问题分析

你的ANR本质是三个问题叠加导致:

  1. 使用GlobalScope无生命周期绑定,线程管理不可控,易引发资源泄漏或线程池耗尽
  2. 后台任务中的三个蓝牙操作以GlobalScope.launch异步启动,未等待全部完成就提前触发UI更新,实际阻塞任务仍在后台占用资源
  3. 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
        }
    }
}

四、之前尝试方案的问题解析

  1. Thread+runOnUIThread未触发:大概率是Thread执行时抛出未捕获异常,导致后续UI更新代码未执行。需给Thread添加异常捕获逻辑,但这种方式生命周期管理繁琐,不如协程简洁。
  2. GlobalScope.launch(Main)+withContext未触发:后台任务未标记为挂起函数,阻塞代码直接在主线程执行,导致主线程卡死,withContext逻辑根本无法运行。
  3. async+await仍ANR:GlobalScope线程不受控,且三个蓝牙任务是fire-and-forget模式,实际阻塞操作仍在后台占用资源,最终引发线程池耗尽或主线程卡死。

五、次生资源加载异常说明

你遇到的ResourcesManager加载APK失败异常是ANR的次生问题:系统弹出ANR对话框时需要加载应用资源,但此时主线程已卡死,无法处理资源加载请求,因此抛出该异常。解决ANR后,此异常会自动消失。

内容的提问来源于stack exchange,提问作者Kalai Selvi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:22:40