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截图模式
|
* 🔑 启用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,调整缓存和格式
|
// ✅ 创建ImageReader,bufferCount至少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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user