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:
@@ -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,调整缓存和格式
|
||||
// ✅ 创建ImageReader,bufferCount至少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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔧 停止队列处理协程
|
||||
|
||||
Reference in New Issue
Block a user