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

Kotlin中Handler引发UI线程冻结问题排查

UI线程卡顿排查:HandlerThread+AsyncTask循环调用导致ProgressBar动画冻结

问题概述

  • 需求:特定场景下每隔1秒重复调用purchaseCheck函数,通过AsyncTask执行API状态检查
  • 异常现象:此前调用逻辑正常,近期出现UI线程冻结,ProgressBar动画明显滞后;移除afterDelay延迟逻辑后,UI卡顿消失
  • 已尝试优化:将App类中的Handler改用HandlerThread实现,但UI卡顿问题仍未解决

核心代码片段

purchaseCheck函数

protected fun purchaseCheck(transactionId: String){
    app.sysLog("Wait for purchase...")
    task = asyncTask({
        api.checkPaymentStatus(transactionId)
    }, taskName = "Purchase Status") { r ->
        r.js?.let {
            when(r.httpCode){
                HTTP_PAYMENT_CHECK_PENDING -> {
                    App.log("purchaseCheck: response pending purchase - try again")
                    MainActivity.afterDelay(1000){
                        purchaseCheck(transactionId)
                    }
                }
                else -> {
                    App.log("purchaseCheck: response purchase success")
                    onPurchaseSuccessfullyCompleted()
                }
            }
        }?:kotlin.run {
            when(r.httpCode){
                HTTP_PAYMENT_CARD_EXPIRED -> {
                    App.log("purchaseCheck: response card expired")
                    showApiErrorAndRetry(r, App.getString("err_purchase_card_expired"))
                }
                else -> {
                    App.log("purchaseCheck: response error (retry)")
                    MainActivity.afterDelay(1000){
                        purchaseCheck(transactionId)
                    }
                }
            }
        }
    }
}

afterDelay相关工具函数

fun afterDelay(delay: Int, body: () -> Unit): Cancellable {
    class DelayRun : Runnable, Cancellable {
        override fun run() = body()

        override fun cancel() {
            removePost(this)
        }
    }
    return DelayRun().also {
        post(delay, it)
    }
}

fun removePost(runnable: Runnable) {
    App.handler.removeCallbacks(runnable)
}

fun post(delay: Int, runnable: Runnable){
    App.handler.postDelayed(runnable, delay.toLong())
}

App类中的Handler配置

val handler: Handler by lazy {
    HandlerThread("MyHandlerThread").let {
        it.start()
        Handler(it.looper)
    }
}

问题根源分析

  1. AsyncTask线程池任务积压:Android 3.0+的AsyncTask默认使用串行线程池,循环调用purchaseCheck会不断向线程池提交新任务。若API响应较慢,任务会持续积压,占用大量CPU资源,间接压低UI线程的调度优先级,导致ProgressBar动画无法及时刷新。
  2. 无节制的任务触发逻辑:当前逻辑不管前一个API请求是否完成,都会在1秒后发起新调用,导致后台任务队列无限增长,CPU持续高负载,UI线程的帧渲染被抢占。
  3. HandlerThread未解决核心矛盾:HandlerThread仅解决了Handler的线程归属问题,但无法控制AsyncTask任务的积压,后台任务的密集执行依然会影响系统整体调度。

修复方案

方案1:控制任务唯一性,避免积压

在发起新任务前取消旧任务,确保同一时间只有一个检查任务在执行:

protected fun purchaseCheck(transactionId: String){
    app.sysLog("Wait for purchase...")
    // 取消未完成的旧任务,避免重复执行
    task?.cancel(true)
    task = asyncTask({
        api.checkPaymentStatus(transactionId)
    }, taskName = "Purchase Status") { r ->
        r.js?.let {
            when(r.httpCode){
                HTTP_PAYMENT_CHECK_PENDING -> {
                    App.log("purchaseCheck: response pending purchase - try again")
                    MainActivity.afterDelay(1000){
                        purchaseCheck(transactionId)
                    }
                }
                else -> {
                    App.log("purchaseCheck: response purchase success")
                    onPurchaseSuccessfullyCompleted()
                }
            }
        }?:kotlin.run {
            when(r.httpCode){
                HTTP_PAYMENT_CARD_EXPIRED -> {
                    App.log("purchaseCheck: response card expired")
                    showApiErrorAndRetry(r, App.getString("err_purchase_card_expired"))
                }
                else -> {
                    App.log("purchaseCheck: response error (retry)")
                    MainActivity.afterDelay(1000){
                        purchaseCheck(transactionId)
                    }
                }
            }
        }
    }
}

方案2:改用协程替代AsyncTask+Handler(推荐)

AsyncTask已被官方废弃,协程的调度机制更高效,能更优雅地处理后台任务与延迟逻辑,且对UI线程影响更小:

// 在Activity中声明协程作用域
private val checkScope = CoroutineScope(Dispatchers.Main + Job())

protected fun purchaseCheck(transactionId: String){
    app.sysLog("Wait for purchase...")
    checkScope.launch {
        // 切换到IO线程执行API请求
        val r = withContext(Dispatchers.IO) {
            api.checkPaymentStatus(transactionId)
        }
        r.js?.let {
            when(r.httpCode){
                HTTP_PAYMENT_CHECK_PENDING -> {
                    App.log("purchaseCheck: response pending purchase - try again")
                    delay(1000)
                    purchaseCheck(transactionId)
                }
                else -> {
                    App.log("purchaseCheck: response purchase success")
                    onPurchaseSuccessfullyCompleted()
                }
            }
        }?:kotlin.run {
            when(r.httpCode){
                HTTP_PAYMENT_CARD_EXPIRED -> {
                    App.log("purchaseCheck: response card expired")
                    showApiErrorAndRetry(r, App.getString("err_purchase_card_expired"))
                }
                else -> {
                    App.log("purchaseCheck: response error (retry)")
                    delay(1000)
                    purchaseCheck(transactionId)
                }
            }
        }
    }
}

// Activity销毁时取消协程,避免内存泄漏
override fun onDestroy() {
    super.onDestroy()
    checkScope.cancel()
}

方案3:优化HandlerThread的任务提交逻辑

避免短时间内重复提交相同任务,确保每次只有一个待执行的检查任务:

// App类中优化HandlerThread配置
val handlerThread by lazy { HandlerThread("MyHandlerThread").apply { start() } }
val handler: Handler by lazy { Handler(handlerThread.looper) }

// 新增变量记录待执行的检查任务
private var pendingCheckRunnable: Runnable? = null

fun afterDelay(delay: Int, body: () -> Unit): Cancellable {
    // 移除之前未执行的任务
    pendingCheckRunnable?.let {
        App.handler.removeCallbacks(it)
    }
    val runnable = Runnable {
        body()
        pendingCheckRunnable = null
    }
    pendingCheckRunnable = runnable
    App.handler.postDelayed(runnable, delay.toLong())
    return object : Cancellable {
        override fun cancel() {
            App.handler.removeCallbacks(runnable)
            pendingCheckRunnable = null
        }
    }
}

总结

核心问题在于循环提交的AsyncTask任务积压导致CPU负载过高,HandlerThread无法解决这一核心矛盾。推荐使用协程重构任务逻辑,既能简化代码,又能更高效地控制任务调度;若坚持使用现有方案,需严格控制任务的唯一性,避免任务队列无限增长。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 11:05:23