From af28985c29952cb0c60c1a052457d8f435ffc8da Mon Sep 17 00:00:00 2001 From: wdvipa Date: Sat, 14 Feb 2026 23:38:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9B=9E=E9=80=80=E5=88=B0d4f27bb?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E4=B8=8A=E5=81=9A=E6=9C=80=E5=B0=8F=E6=94=B9?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 回退之前所有激进修改(setSurface/Callback/Surface检查) - 仅保留: 重建阈值从10降到5, 重建次数上限3次 - 添加CancellationException正确传播 - 不修改cleanupVirtualDisplayOnly清理逻辑 - 不添加Surface有效性检查和VirtualDisplay Callback --- .../hikoncont/manager/ScreenCaptureManager.kt | 183 +++++------------- 1 file changed, 53 insertions(+), 130 deletions(-) diff --git a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt index fc01b89..27c2ca3 100644 --- a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt +++ b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt @@ -83,15 +83,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // ✅ 图像获取失败计数 private var consecutiveImageFailures = 0 - private val maxImageFailuresBeforeRecreation = 5 // 连续5次失败即考虑重新创建(从10降低,加速回退) + private val maxImageFailuresBeforeRecreation = 5 // 连续5次失败即重建(加速回退) - // ✅ VirtualDisplay重建次数限制,防止无限重建循环 + // ✅ VirtualDisplay重建次数限制 private var virtualDisplayRebuildCount = 0 - private val MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS = 3 // 最多重建3次,之后直接回退到无障碍截图 - - // ✅ BufferQueue abandoned 标志:由VirtualDisplay回调或Surface检测设置,采集循环读取 - @Volatile - private var bufferQueueAbandoned = false + private val MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS = 3 // 🔑 新增:AccessibilityService截图模式开关 private var useAccessibilityScreenshot = false @@ -368,7 +364,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { captureScope.launch { // 先确保 MediaProjection 可用 if (!ensureMediaProjection()) { - Log.w(TAG, "⚠️ MediaProjection 不可用,回退到无障碍截图") + Log.e(TAG, "❌ MediaProjection 不可用,回退到无障碍截图") // 回退到无障碍截图作为兜底 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() @@ -385,7 +381,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } if (virtualDisplay == null) { - Log.w(TAG, "⚠️ VirtualDisplay 创建失败,回退到无障碍截图") + Log.e(TAG, "❌ VirtualDisplay 创建失败,回退到无障碍截图") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() @@ -398,8 +394,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "✅ MediaProjection VirtualDisplay 就绪,开始连续帧采集循环") var consecutiveFailures = 0 - virtualDisplayRebuildCount = 0 // 重置重建计数 - bufferQueueAbandoned = false // 重置abandoned标志,确保采集循环正常启动 + virtualDisplayRebuildCount = 0 // ✅ 最小有效帧大小阈值:正常480×854 JPEG即使最低质量也>5KB // 低于此值的帧几乎肯定是黑屏/空白帧(VirtualDisplay未刷新) @@ -410,26 +405,6 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val MAX_BLACK_FRAMES_BEFORE_FALLBACK = 30 // 连续30个黑屏帧后切换到无障碍截图 while (isCapturing) { - // ✅ BufferQueue abandoned 检测:标志位 + Surface失效双重检查 - // bufferQueueAbandoned 由 VirtualDisplay.Callback.onStopped 设置,无延迟 - val surfaceValid = try { - !bufferQueueAbandoned && imageReader?.surface?.isValid == true - } catch (e: Exception) { - Log.w(TAG, "⚠️ Surface有效性检查异常: ${e.message}") - false - } - if (!surfaceValid) { - Log.w(TAG, "🚨 Surface已失效(BufferQueue abandoned=${bufferQueueAbandoned}),停止MediaProjection采集") - cleanupVirtualDisplayOnly() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Log.i(TAG, "🔄 回退到无障碍截图模式") - enableAccessibilityScreenshotMode() - startAccessibilityScreenCapture() - } else { - startFallbackCapture() - } - return@launch - } try { val image = imageReader?.acquireLatestImage() if (image != null) { @@ -507,14 +482,12 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 连续失败过多,尝试重建 VirtualDisplay if (consecutiveFailures >= maxImageFailuresBeforeRecreation) { virtualDisplayRebuildCount++ - Log.w(TAG, "⚠️ 连续 ${consecutiveFailures} 次无法获取有效帧,尝试重建 VirtualDisplay (第${virtualDisplayRebuildCount}/${MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS}次)") + Log.w(TAG, "⚠️ 连续 ${consecutiveFailures} 次无法获取有效帧,重建 VirtualDisplay (${virtualDisplayRebuildCount}/${MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS})") - // ✅ 重建次数超限,直接回退到无障碍截图 if (virtualDisplayRebuildCount > MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS) { - Log.w(TAG, "🚨 VirtualDisplay重建次数已达上限(${MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS}),MediaProjection在此设备不可靠") + Log.w(TAG, "🚨 重建次数已达上限,回退到无障碍截图") cleanupVirtualDisplayOnly() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Log.i(TAG, "🔄 回退到无障碍截图模式") enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() } else { @@ -528,16 +501,8 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { setupMediaProjectionResources() consecutiveFailures = 0 - // ✅ 重建后验证Surface有效性 - val rebuildSurfaceValid = try { - imageReader?.surface?.isValid == true - } catch (e: Exception) { - false - } - - if (virtualDisplay == null || !rebuildSurfaceValid) { - Log.w(TAG, "⚠️ VirtualDisplay 重建失败或Surface无效,回退到无障碍截图") - cleanupVirtualDisplayOnly() + if (virtualDisplay == null) { + Log.w(TAG, "⚠️ VirtualDisplay 重建失败,回退到无障碍截图") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() @@ -551,7 +516,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { delay(1000L / dynamicFps) } catch (e: CancellationException) { - throw e // 协程正常取消,向上传播 + throw e } catch (e: Exception) { Log.w(TAG, "⚠️ MediaProjection 帧采集异常: ${e.message}") consecutiveFailures++ @@ -591,7 +556,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } - Log.w(TAG, "⚠️ 无法获取 MediaProjection,权限可能未授予") + Log.e(TAG, "❌ 无法获取 MediaProjection,权限可能未授予") return false } @@ -663,11 +628,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } catch (e: CancellationException) { - throw e // 协程正常取消,向上传播 + throw e } catch (e: Exception) { Log.w(TAG, "⚠️ 屏幕捕获失败: ${e.message}") consecutiveFailures++ - delay(100) // ✅ 出错时进一步缩短间隔,保持流畅度(从200ms改为100ms) + delay(100) } } } @@ -836,7 +801,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.w(TAG, "⚠️ ScreenshotResult中的HardwareBuffer为null") // 🚨 Android 11+特殊处理:记录详细错误信息 if (Build.VERSION.SDK_INT >= 30) { - Log.w(TAG, "📱 Android 11+设备:HardwareBuffer为null,可能是权限问题") + Log.e(TAG, "📱 Android 11+设备:HardwareBuffer为null,可能是权限问题") } } } else { @@ -847,7 +812,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.e(TAG, "处理截图结果失败", e) // 🚨 Android 11+特殊处理:记录详细异常信息 if (Build.VERSION.SDK_INT >= 30) { - Log.d(TAG, "📱 Android 11+设备:处理截图结果时发生异常: ${e.message}") + Log.e(TAG, "📱 Android 11+设备:处理截图结果时发生异常", e) } } finally { latch.countDown() @@ -855,7 +820,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } override fun onFailure(failureErrorCode: Int) { - Log.w(TAG, "无障碍服务截图失败,错误码: $failureErrorCode") + Log.e(TAG, "无障碍服务截图失败,错误码: $failureErrorCode") errorCode = failureErrorCode // 🚨 Android 11+特殊处理:详细分析错误码 @@ -867,7 +832,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS -> "无障碍权限不足" else -> "未知错误($failureErrorCode)" } - Log.w(TAG, "📱 Android 11+设备:截图失败详情 - $errorMessage") + Log.e(TAG, "📱 Android 11+设备:截图失败详情 - $errorMessage") } latch.countDown() @@ -878,7 +843,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.e(TAG, "调用takeScreenshot失败", e) // 🚨 Android 11+特殊处理:记录调用失败详情 if (Build.VERSION.SDK_INT >= 30) { - Log.d(TAG, "📱 Android 11+设备:调用takeScreenshot时发生异常: ${e.message}") + Log.e(TAG, "📱 Android 11+设备:调用takeScreenshot时发生异常", e) } latch.countDown() } @@ -923,7 +888,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.e(TAG, "无障碍服务截图异常", e) // 🚨 Android 11+特殊处理:异常时返回null让主循环处理 if (Build.VERSION.SDK_INT >= 30) { - Log.d(TAG, "📱 Android 11+设备:无障碍截图异常,返回null让主循环处理: ${e.message}") + Log.e(TAG, "📱 Android 11+设备:无障碍截图异常,返回null让主循环处理", e) return null } // 出现异常时回退到MediaProjection @@ -968,13 +933,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "✅ Android 15静默恢复成功,继续屏幕捕获") } else { // 静默恢复失败,可能确实需要重新申请权限 - Log.w(TAG, "⚠️ Android 15静默恢复失败,MediaProjection权限确实丢失") + Log.e(TAG, "❌ Android 15静默恢复失败,MediaProjection权限确实丢失") triggerPermissionRecovery() return null } } else { // 其他版本的处理逻辑 - Log.w(TAG, "⚠️ MediaProjection权限丢失,触发自动权限恢复") + Log.e(TAG, "❌ MediaProjection权限丢失,触发自动权限恢复") triggerPermissionRecovery() return null } @@ -1205,41 +1170,18 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "ImageReader创建完成") - // 创建VirtualDisplay,注册Callback监听停止事件 - val displayCallback = object : android.hardware.display.VirtualDisplay.Callback() { - override fun onStopped() { - Log.w(TAG, "🚨 VirtualDisplay.Callback.onStopped: 系统已停止VirtualDisplay") - bufferQueueAbandoned = true - } - } - + // 创建VirtualDisplay virtualDisplay = mediaProjection?.createVirtualDisplay( "RemoteControlCapture", screenWidth, screenHeight, service.resources.displayMetrics.densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - imageReader?.surface, displayCallback, backgroundHandler + imageReader?.surface, null, null ) - // 重置abandoned标志(新的VirtualDisplay创建成功) - bufferQueueAbandoned = false - if (virtualDisplay != null) { Log.i(TAG, "VirtualDisplay创建成功") - // ✅ 创建后立即验证Surface有效性,防止BufferQueue abandoned - val surfaceValid = try { - imageReader?.surface?.isValid == true - } catch (e: Exception) { - Log.w(TAG, "⚠️ Surface有效性验证异常: ${e.message}") - false - } - if (!surfaceValid) { - Log.e(TAG, "🚨 VirtualDisplay创建后Surface已失效,清理资源") - cleanupVirtualDisplayOnly() - return - } - // ✅ Android 15:VirtualDisplay创建成功后触发权限申请处理 if (Build.VERSION.SDK_INT >= 35) { Log.i(TAG, "🔧 Android 15:VirtualDisplay创建成功,触发权限申请处理") @@ -1460,7 +1402,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "🔒 已标记Android 15 session为已使用") } } catch (e: Exception) { - Log.w(TAG, "⚠️ 标记Android 15 session失败", e) + Log.e(TAG, "标记Android 15 session失败", e) } } @@ -1483,7 +1425,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } catch (e: Exception) { - Log.w(TAG, "⚠️ 通知MediaProjection权限丢失失败", e) + Log.e(TAG, "通知MediaProjection权限丢失失败", e) } } @@ -1506,7 +1448,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "屏幕捕获已暂停,权限保留,状态已持久化") } catch (e: Exception) { - Log.w(TAG, "⚠️ 暂停屏幕捕获失败", e) + Log.e(TAG, "暂停屏幕捕获失败", e) } } @@ -1529,7 +1471,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { startCapture() } catch (e: Exception) { - Log.w(TAG, "⚠️ 恢复屏幕捕获失败", e) + Log.e(TAG, "恢复屏幕捕获失败", e) } } @@ -1665,7 +1607,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.d(TAG, "🎯 智能压缩完成: 原始${bitmap.width}x${bitmap.height} -> 输出${compressedData.size} bytes (质量$quality, 尝试${attempts}次)") } catch (e: Exception) { - Log.w(TAG, "⚠️ 智能压缩失败,使用基础压缩", e) + Log.e(TAG, "❌ 智能压缩失败,使用基础压缩", e) // 如果智能压缩失败,使用基础压缩 outputStream.reset() bitmap.compress(Bitmap.CompressFormat.JPEG, dynamicQuality, outputStream) @@ -1741,7 +1683,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { droppedFrameCount++ } if (!screenDataQueue.offer(frameData)) { - Log.w(TAG, "⚠️ 队列清理后仍无法添加新帧,跳过此帧") + Log.e(TAG, "❌ 队列清理后仍无法添加新帧,跳过此帧") droppedFrameCount++ return } @@ -1751,7 +1693,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { checkAndCleanMemory() } catch (e: Exception) { - Log.w(TAG, "⚠️ 发送帧数据失败", e) + Log.e(TAG, "❌ 发送帧数据失败", e) } } @@ -1762,30 +1704,22 @@ 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.w(TAG, "⚠️ 队列处理帧异常: ${e.message}") - delay(100) + while (isCapturing && isActive) { + try { + val frameData = screenDataQueue.poll() + if (frameData != null) { + processFrameData(frameData) + } else { + // 队列为空,短暂休眠避免CPU占用过高 + delay(10) } + } 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) } } } @@ -1807,7 +1741,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.d(TAG, "🗑️ 清空队列中剩余的${remainingFrames}帧数据") } } catch (e: Exception) { - Log.w(TAG, "⚠️ 停止队列处理协程失败", e) + Log.e(TAG, "❌ 停止队列处理协程失败", e) } } @@ -1837,7 +1771,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { frameData.fill(0) // 清空数组内容 } catch (e: Exception) { - Log.w(TAG, "⚠️ 处理帧数据失败", e) + Log.e(TAG, "❌ 处理帧数据失败", e) } } @@ -1881,7 +1815,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } catch (e: Exception) { - Log.w(TAG, "⚠️ 内存检查失败", e) + Log.e(TAG, "❌ 内存检查失败", e) } } @@ -1913,7 +1847,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.w(TAG, "✅ 紧急内存清理完成") } catch (e: Exception) { - Log.w(TAG, "⚠️ 紧急内存清理失败", e) + Log.e(TAG, "❌ 紧急内存清理失败", e) } } @@ -1961,7 +1895,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } catch (e: Exception) { - Log.w(TAG, "⚠️ 清理WeakReference失败", e) + Log.e(TAG, "❌ 清理WeakReference失败", e) } } @@ -2261,18 +2195,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private fun cleanupVirtualDisplayOnly() { try { - // 释放VirtualDisplay(停止生产者向Surface写帧) - // 注意:不使用setSurface(null),某些设备上会导致MediaProjection session失效 + // 清理VirtualDisplay virtualDisplay?.release() virtualDisplay = null - // 紧接着关闭ImageReader(销毁Surface,生产者自然停止dequeueBuffer) + // 清理ImageReader imageReader?.close() imageReader = null - // 标记abandoned状态 - bufferQueueAbandoned = true - // ✅ 安全清理缓存的Bitmap,防止多线程并发导致native crash (SIGSEGV) safeRecycleLastValidBitmap() lastCaptureTime = 0L @@ -2474,21 +2404,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "🔧 Android 15:ImageReader重新创建完成") - // 重新创建VirtualDisplay(带Callback监听停止事件) - val retryDisplayCallback = object : android.hardware.display.VirtualDisplay.Callback() { - override fun onStopped() { - Log.w(TAG, "🚨 Android 15重试VirtualDisplay.Callback.onStopped") - bufferQueueAbandoned = true - } - } + // 重新创建VirtualDisplay virtualDisplay = mediaProjection?.createVirtualDisplay( "RemoteControlCaptureRetry_${System.currentTimeMillis()}", screenWidth, screenHeight, service.resources.displayMetrics.densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - imageReader?.surface, retryDisplayCallback, backgroundHandler + imageReader?.surface, null, null ) - bufferQueueAbandoned = false if (virtualDisplay != null) { Log.i(TAG, "✅ Android 15 VirtualDisplay完全重新创建成功")