diff --git a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt index 5d28c14..2fbb0ee 100644 --- a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt +++ b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt @@ -317,21 +317,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { /** * 切换到无障碍截图模式(由服务端指令触发) - * 停止当前采集,切换到 AccessibilityService.takeScreenshot + * 已禁用:不再允许服务端黑帧检测触发模式切换,避免误判导致权限回退 */ fun switchToAccessibilityMode() { - if (useAccessibilityScreenshot) { - Log.d(TAG, "已经在无障碍截图模式,跳过切换") - return - } - Log.i(TAG, "切换到无障碍截图模式") - // 停止当前采集 - stopCapture() - // 清理 MediaProjection 资源 - cleanupVirtualDisplayOnly() - // 启用无障碍截图并重新开始 - enableAccessibilityScreenshotMode() - startCapture() + Log.i(TAG, "收到切换无障碍截图模式指令,已忽略(禁止服务端触发模式切换)") } /** @@ -483,33 +472,47 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { var consecutiveFailures = 0 virtualDisplayRebuildCount = 0 - // 最小有效帧大小阈值:正常480×854 JPEG即使最低质量也>5KB + // 最小有效帧大小阈值:正常480x854 JPEG即使最低质量也>5KB // 低于此值的帧几乎肯定是黑屏/空白帧(VirtualDisplay未刷新) val MIN_VALID_FRAME_SIZE = 5 * 1024 // 5KB - // 连续黑屏帧计数,用于判断是否应该切换到无障碍截图 + // 连续黑屏帧计数 - 不再用于触发回退,仅用于日志统计 var consecutiveBlackFrames = 0 - val MAX_BLACK_FRAMES_BEFORE_FALLBACK = 30 // 连续30个黑屏帧后切换到无障碍截图 while (isCapturing) { try { // MediaProjection对象有效性检测: - // onStop回调会将Holder中的对象清理为null, - // 此时继续采集毫无意义,应立即回退到无障碍截图 + // 如果本地引用为null,尝试从Holder重新获取 if (mediaProjection == null) { - Log.w(TAG, "MediaProjection对象已失效(被系统回收或onStop清理),立即回退到无障碍截图") - cleanupVirtualDisplayOnly() - fallbackToAccessibilityCapture() - return@launch + Log.w(TAG, "MediaProjection本地引用为null,尝试从Holder重新获取") + mediaProjection = MediaProjectionHolder.getMediaProjection() + if (mediaProjection == null) { + Log.w(TAG, "Holder中也无有效MediaProjection,等待权限恢复,发送缓存帧") + val cachedJpeg = compressCachedFrame(30000) + if (cachedJpeg != null) { + sendFrameToServer(cachedJpeg) + } + delay(3000) + continue + } + Log.i(TAG, "从Holder重新获取到MediaProjection,继续采集") } - // Surface有效性检测:BufferQueue被系统abandoned后立即回退 + // Surface有效性检测:BufferQueue被系统abandoned后尝试重建 val currentSurface = imageReader?.surface if (currentSurface == null || !currentSurface.isValid) { - Log.w(TAG, "ImageReader Surface已失效(null=${currentSurface == null}, isValid=${currentSurface?.isValid}), 回退到无障碍截图") + Log.w(TAG, "ImageReader Surface已失效, 尝试重建VirtualDisplay") cleanupVirtualDisplayOnly() - fallbackToAccessibilityCapture() - return@launch + delay(500) + if (ensureMediaProjection()) { + setupMediaProjectionResources() + } + val rebuiltSurface = imageReader?.surface + if (rebuiltSurface == null || !rebuiltSurface.isValid) { + Log.w(TAG, "Surface重建失败,等待下次循环重试") + } + delay(1000) + continue } // 安全获取Image,防止maxImages溢出 @@ -541,25 +544,17 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { consecutiveBlackFrames = 0 Log.d(TAG, "MediaProjection 有效帧: ${jpegData.size} bytes") } else { - // 疑似黑屏帧 + // 疑似黑屏帧,仅记录日志,不触发回退 + // MediaProjection仍在工作,黑屏帧可能是短暂的屏幕状态变化 consecutiveBlackFrames++ - consecutiveFailures++ - Log.w(TAG, "黑屏帧(${jpegData.size}B < ${MIN_VALID_FRAME_SIZE}B),连续${consecutiveBlackFrames}次") - - // 连续黑屏帧过多,MediaProjection在此设备上不可靠,切换到无障碍截图 - if (consecutiveBlackFrames >= MAX_BLACK_FRAMES_BEFORE_FALLBACK) { - Log.w(TAG, "MediaProjection连续${consecutiveBlackFrames}个黑屏帧,切换到无障碍截图模式") - safeRecycleBitmap(bitmap) - cleanupVirtualDisplayOnly() - fallbackToAccessibilityCapture() - return@launch + if (consecutiveBlackFrames % 50 == 1) { + Log.w(TAG, "黑屏帧(${jpegData.size}B < ${MIN_VALID_FRAME_SIZE}B), 连续${consecutiveBlackFrames}次, 发送缓存帧") } - // Send cached valid frame to maintain continuity (if available) + // 发送缓存的有效帧保持画面连续性 val cachedJpeg = compressCachedFrame(10000) if (cachedJpeg != null && cachedJpeg.size >= MIN_VALID_FRAME_SIZE) { sendFrameToServer(cachedJpeg) - Log.d(TAG, "Used cached frame instead of black frame") } } @@ -583,26 +578,24 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { virtualDisplayRebuildCount++ Log.w(TAG, "连续 ${consecutiveFailures} 次无法获取有效帧,重建 VirtualDisplay (${virtualDisplayRebuildCount}/${MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS})") - // 重建次数超限,回退到无障碍截图 - if (virtualDisplayRebuildCount > MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS) { - Log.w(TAG, "重建次数已达上限,回退到无障碍截图") - cleanupVirtualDisplayOnly() - fallbackToAccessibilityCapture() - return@launch - } - cleanupVirtualDisplayOnly() delay(500) + + // 重新检查MediaProjection是否可用 + if (!ensureMediaProjection()) { + Log.w(TAG, "MediaProjection不可用,等待恢复,继续发送缓存帧") + consecutiveFailures = 0 + continue + } + setupMediaProjectionResources() consecutiveFailures = 0 // 重建后验证VirtualDisplay和Surface有效性 val rebuiltSurface = imageReader?.surface if (virtualDisplay == null || rebuiltSurface == null || !rebuiltSurface.isValid) { - Log.w(TAG, "VirtualDisplay 重建后无效(vd=${virtualDisplay != null}, surface=${rebuiltSurface != null}, valid=${rebuiltSurface?.isValid}), 回退到无障碍截图") - cleanupVirtualDisplayOnly() - fallbackToAccessibilityCapture() - return@launch + Log.w(TAG, "VirtualDisplay 重建后无效,等待下次重试") + // 不回退,继续循环,下次会再尝试重建 } } } @@ -719,19 +712,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } - // 截图成功时按帧率延迟,失败时等待最小截图间隔后重试 - if (consecutiveFailures == 0) { - delay(1000 / dynamicFps.toLong()) + // 无障碍截图统一使用固定3秒间隔,不区分成功/失败 + // 系统对无障碍截图有约3秒的最小间隔限制,低于此值会报错 + val elapsed = System.currentTimeMillis() - lastScreenshotTime + val remaining = MIN_CAPTURE_INTERVAL - elapsed + if (remaining > 0) { + delay(remaining) } else { - // 无障碍截图有系统级最小间隔限制(约3秒), - // 失败时等待剩余间隔时间,避免"截图间隔太短"错误 - val elapsed = System.currentTimeMillis() - lastScreenshotTime - val remaining = MIN_CAPTURE_INTERVAL - elapsed - if (remaining > 0) { - delay(remaining) - } else { - delay(100) - } + delay(MIN_CAPTURE_INTERVAL) } } catch (e: CancellationException) { @@ -739,7 +727,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } catch (e: Exception) { Log.w(TAG, "屏幕捕获失败: ${e.message}") consecutiveFailures++ - delay(100) // 出错时进一步缩短间隔,保持流畅度(从200ms改为100ms) + delay(MIN_CAPTURE_INTERVAL) // 异常时也等待3秒再重试 } } }