人脸检测运行一段时间后导致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
相关产品推荐
相关产品推荐

