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