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

人脸检测运行一段时间后导致Android相机画面冻结

相机画面冻结问题排查与解决

问题详情

  • 实现逻辑:通过Kotlin循环完成相机画面显示、人脸检测及人脸边界框绘制,依赖workReducer计数器每0.5秒触发一次人脸检测
  • 异常表现:
    • 首次启动相机后数秒内画面冻结
    • 点击屏幕按钮重启相机后,约20秒左右画面冻结
    • 未检测到人脸时无冻结现象
    • 注释android.graphics.Rect相关代码后,冻结频率明显降低

问题代码片段

// 注释这段Rect创建逻辑后冻结频率降低
// android.graphics.Rect( face.boundingBox.left,
//                       face.boundingBox.top,
//                       face.boundingBox.right,
//                       face.boundingBox.bottom
//                    )

imageReader.setOnImageAvailableListener({ reader ->
    val image = reader.acquireLatestImage() ?: return@setOnImageAvailableListener
    val bitImage = recordJpeg(image, cx)
    
    bitImage?.let { bitmapCallback.onBitmapUpdated(it) }

    if (workReducer > workReducerLimit) {
        workReducer = 0
        val inputImage = InputImage.fromMediaImage(image, 270)
        faceDetector.process(inputImage).addOnSuccessListener { faces ->
            val rects = faces.map { face ->
                android.graphics.Rect(
                    face.boundingBox.left,
                    face.boundingBox.top,
                    face.boundingBox.right,
                    face.boundingBox.bottom
                )
            }
            faceRectsCallback.onFaceRectsUpdated(rects)
        }
        .addOnFailureListener { e ->
            image.close()
        }
        .addOnCompleteListener {
            image.close()
        }
    } else {
        image.close()
    }
    workReducer += 1
// 相机图像读取循环
}, Handler(context.mainLooper))

解决方案

1. 避免主线程阻塞

当前setOnImageAvailableListener绑定了mainLooper,人脸检测、Rect对象创建等耗时操作都在主线程执行,直接导致UI卡顿冻结。需将人脸检测逻辑移至后台线程执行,仅将最终的人脸框回调切换回主线程更新UI:

// 创建后台线程Handler执行图像处理逻辑
val backgroundHandler = Handler(Executors.newSingleThreadExecutor().looper)
imageReader.setOnImageAvailableListener({ reader ->
    val image = reader.acquireLatestImage() ?: return@setOnImageAvailableListener
    val bitImage = recordJpeg(image, cx)
    
    // 画面更新必须在主线程执行
    Handler(Looper.getMainLooper()).post {
        bitImage?.let { bitmapCallback.onBitmapUpdated(it) }
    }

    if (workReducer > workReducerLimit) {
        workReducer = 0
        val inputImage = InputImage.fromMediaImage(image, 270)
        faceDetector.process(inputImage).addOnSuccessListener { faces ->
            val rects = faces.map { face ->
                android.graphics.Rect(
                    face.boundingBox.left,
                    face.boundingBox.top,
                    face.boundingBox.right,
                    face.boundingBox.bottom
                )
            }
            // 人脸框更新切回主线程
            Handler(Looper.getMainLooper()).post {
                faceRectsCallback.onFaceRectsUpdated(rects)
            }
        }
        .addOnFailureListener { e ->
            image.close()
        }
        .addOnCompleteListener {
            image.close()
        }
    } else {
        image.close()
    }
    workReducer += 1
}, backgroundHandler)

2. 优化Rect对象创建

频繁创建Rect对象会触发频繁GC,加剧主线程卡顿。可以复用Rect对象池,减少对象创建开销:

// 提前创建Rect对象池
private val rectPool = mutableListOf<Rect>()

// 在人脸处理时复用对象
val rects = faces.map { face ->
    val rect = rectPool.removeFirstOrNull() ?: Rect()
    rect.set(face.boundingBox.left, face.boundingBox.top, face.boundingBox.right, face.boundingBox.bottom)
    rect
}
// 使用完成后回收Rect到池(UI更新完成后执行)
Handler(Looper.getMainLooper()).post {
    faceRectsCallback.onFaceRectsUpdated(rects)
    rectPool.addAll(rects)
}

3. 确保Image资源及时释放

当前代码中image.close()的时机存在风险,若人脸检测回调未及时执行,会导致Image资源长时间占用,引发内存压力和卡顿。可以将image.close()放到finally块中,确保无论分支如何都会释放资源:

val image = reader.acquireLatestImage() ?: return@setOnImageAvailableListener
try {
    val bitImage = recordJpeg(image, cx)
    // ... 其余逻辑不变
} finally {
    image.close()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 21:05:07