fix: 修复ImageReader maxImages溢出和协程取消异常

- ImageReader bufferCount从2提升到4(Android15为5),防止acquireLatestImage内部缓冲区不足
- captureWithMediaProjection重试循环添加IllegalStateException捕获,缓冲区满时执行drainImageReader清空
- startMediaProjectionCapture流式采集循环同样添加acquireLatestImage安全防护
- 新增drainImageReader方法,通过acquireNextImage+close循环释放所有已acquired的Image
- 队列处理协程正确传播CancellationException,避免协程取消时误报异常日志
This commit is contained in:
wdvipa
2026-02-14 23:53:07 +08:00
parent 18a1efbfc7
commit de9aa4430c

View File

@@ -174,6 +174,33 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
} }
} }
/**
* <20> 清空ImageReader中所有已acquired的Image释放缓冲区槽位
* 当acquireLatestImage抛出maxImages异常时调用
*/
private fun drainImageReader() {
try {
val reader = imageReader ?: return
var drained = 0
while (true) {
val img = try {
reader.acquireNextImage()
} catch (e: IllegalStateException) {
// 缓冲区仍然满说明有Image未被正确close
Log.w(TAG, "⚠️ drainImageReader: acquireNextImage也失败重建ImageReader")
break
} ?: break
try { img.close() } catch (_: Exception) {}
drained++
}
if (drained > 0) {
Log.i(TAG, "🔧 已清空ImageReader缓冲区: 释放${drained}个Image")
}
} catch (e: Exception) {
Log.e(TAG, "❌ drainImageReader失败: ${e.message}")
}
}
/** /**
* 🔑 启用AccessibilityService截图模式 * 🔑 启用AccessibilityService截图模式
* 用于绕过黑屏遮罩让Web端能够正常显示画面 * 用于绕过黑屏遮罩让Web端能够正常显示画面
@@ -406,7 +433,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
while (isCapturing) { while (isCapturing) {
try { try {
val image = imageReader?.acquireLatestImage() // 安全获取Image防止maxImages溢出
val image = try {
imageReader?.acquireLatestImage()
} catch (e: IllegalStateException) {
Log.w(TAG, "⚠️ 流式采集ImageReader缓冲区已满清空: ${e.message}")
drainImageReader()
null
}
if (image != null) { if (image != null) {
try { try {
val bitmap = convertImageToBitmap(image) val bitmap = convertImageToBitmap(image)
@@ -960,32 +994,36 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
var newBitmap: Bitmap? = null var newBitmap: Bitmap? = null
// ✅ Android 15优化首先尝试获取新图像增加重试次数和等待时间 // ✅ 尝试获取新图像用安全方式防止maxImages溢出
var retryCount = 0 var retryCount = 0
val maxRetries = if (Build.VERSION.SDK_INT >= 35) 5 else 2 // Android 15需要更多重试 val maxRetries = if (Build.VERSION.SDK_INT >= 35) 5 else 2
while (retryCount < maxRetries) { while (retryCount < maxRetries) {
val image = imageReader?.acquireLatestImage() var image: android.media.Image? = null
try {
image = imageReader?.acquireLatestImage()
} catch (e: IllegalStateException) {
// maxImages已满先清空所有已acquired的Image再重试
Log.w(TAG, "⚠️ ImageReader缓冲区已满执行紧急清空: ${e.message}")
drainImageReader()
retryCount++
continue
}
if (image != null) { if (image != null) {
// 🔧 跟踪Image资源
val trackedImage = trackImage(image)
try { try {
newBitmap = convertImageToBitmap(trackedImage) newBitmap = convertImageToBitmap(image)
if (newBitmap != null) { if (newBitmap != null) {
// 🔧 跟踪新创建的Bitmap
trackBitmap(newBitmap) trackBitmap(newBitmap)
Log.d(TAG, "MediaProjection获取新图像: ${newBitmap.width}x${newBitmap.height}") Log.d(TAG, "MediaProjection获取新图像: ${newBitmap.width}x${newBitmap.height}")
// ✅ 成功获取图像,重置失败计数
if (consecutiveImageFailures > 0) { if (consecutiveImageFailures > 0) {
Log.d(TAG, "✅ 图像获取成功,重置失败计数(之前${consecutiveImageFailures}次)") Log.d(TAG, "✅ 图像获取成功,重置失败计数(之前${consecutiveImageFailures}次)")
consecutiveImageFailures = 0 consecutiveImageFailures = 0
} }
// 更新缓存
safeRecycleLastValidBitmap() safeRecycleLastValidBitmap()
lastValidBitmap = newBitmap.copy(Bitmap.Config.ARGB_8888, false) lastValidBitmap = newBitmap.copy(Bitmap.Config.ARGB_8888, false)
// 🔧 跟踪缓存的Bitmap副本
lastValidBitmap?.let { trackBitmap(it) } lastValidBitmap?.let { trackBitmap(it) }
lastCaptureTime = currentTime lastCaptureTime = currentTime
@@ -994,29 +1032,24 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "转换图像失败", e) Log.e(TAG, "转换图像失败", e)
} finally { } finally {
trackedImage.close() // 确保Image被close释放ImageReader缓冲区槽位
try { image.close() } catch (_: Exception) {}
} }
} }
// ✅ Android 15增加等待时间因为图像生成可能需要更长时间
val waitTime = if (Build.VERSION.SDK_INT >= 35) { val waitTime = if (Build.VERSION.SDK_INT >= 35) {
if (retryCount == 0) 50L else (retryCount * 30L) // 首次50ms后续递增 if (retryCount == 0) 50L else (retryCount * 30L)
} else { } else {
20L 20L
} }
Thread.sleep(waitTime) Thread.sleep(waitTime)
retryCount++ retryCount++
// Android 15特殊处理在重试期间记录更多调试信息
if (Build.VERSION.SDK_INT >= 35 && retryCount > 1) {
Log.d(TAG, "Android 15图像获取重试 $retryCount/$maxRetries,等待${waitTime}ms")
}
} }
// 如果获取不到新图像,检查是否可以使用缓存 // 如果获取不到新图像,检查是否可以使用缓存
if (lastValidBitmap != null) { if (lastValidBitmap != null) {
val timeSinceLastCapture = currentTime - lastCaptureTime val timeSinceLastCapture = currentTime - lastCaptureTime
if (timeSinceLastCapture < 30000) { // 延长到30秒内的缓存有效 if (timeSinceLastCapture < 30000) {
Log.d(TAG, "使用缓存图像 (${timeSinceLastCapture}ms前) - 静止页面") Log.d(TAG, "使用缓存图像 (${timeSinceLastCapture}ms前) - 静止页面")
return lastValidBitmap!!.copy(Bitmap.Config.ARGB_8888, false) return lastValidBitmap!!.copy(Bitmap.Config.ARGB_8888, false)
} else { } else {
@@ -1150,11 +1183,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
// 只清理VirtualDisplay和ImageReader保留MediaProjection权限 // 只清理VirtualDisplay和ImageReader保留MediaProjection权限
cleanupVirtualDisplayOnly() cleanupVirtualDisplayOnly()
// ✅ Android 15优化创建ImageReader调整缓存和格式 // ✅ 创建ImageReaderbufferCount至少4个
// acquireLatestImage()内部需要先acquireNextImage再遍历找最新帧
// bufferCount=2时如果有1个未close的Image就会触发maxImages异常
val bufferCount = if (Build.VERSION.SDK_INT >= 35) { val bufferCount = if (Build.VERSION.SDK_INT >= 35) {
3 // Android 15需要更多缓存确保图像连续性 5 // Android 15需要更多缓存确保图像连续性
} else { } else {
2 // 其他版本减少缓存避免内存问题 4 // 其他版本也需要足够缓存避免acquireLatestImage的maxImages异常
} }
imageReader = android.media.ImageReader.newInstance( imageReader = android.media.ImageReader.newInstance(
@@ -1705,22 +1740,29 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
if (queueProcessingStarted.compareAndSet(false, true)) { if (queueProcessingStarted.compareAndSet(false, true)) {
Log.d(TAG, "🚀 启动屏幕数据队列处理协程") Log.d(TAG, "🚀 启动屏幕数据队列处理协程")
queueProcessorJob = serviceScope.launch { queueProcessorJob = serviceScope.launch {
while (isCapturing && isActive) { try {
try { while (isCapturing && isActive) {
val frameData = screenDataQueue.poll() try {
if (frameData != null) { val frameData = screenDataQueue.poll()
processFrameData(frameData) if (frameData != null) {
} else { processFrameData(frameData)
// 队列为空短暂休眠避免CPU占用过高 } else {
delay(10) delay(10)
}
} catch (e: CancellationException) {
// 协程取消是正常流程,直接传播退出循环
throw e
} catch (e: Exception) {
Log.e(TAG, "❌ 队列处理协程异常", e)
delay(100)
} }
} catch (e: Exception) {
Log.e(TAG, "❌ 队列处理协程异常", e)
delay(100) // 出错时延长休眠时间
} }
} catch (e: CancellationException) {
Log.d(TAG, "🛑 队列处理协程被取消(正常停止)")
} finally {
Log.d(TAG, "🛑 屏幕数据队列处理协程结束")
queueProcessingStarted.set(false)
} }
Log.d(TAG, "🛑 屏幕数据队列处理协程结束")
queueProcessingStarted.set(false)
} }
} }
} }