From 87b84b01bbafe2d4c6c63060f046bf03c1d89d1e Mon Sep 17 00:00:00 2001 From: wdvipa Date: Sun, 15 Feb 2026 00:55:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BD=95=E5=B1=8F?= =?UTF-8?q?=E6=97=B6=E9=97=AA=E9=80=80=E9=87=8D=E5=90=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - screenshotExecutor被shutdownNow后无法恢复: stopCapture中关闭执行器后switchToAccessibilityMode再startCapture时向已关闭的ExecutorService提交任务抛出RejectedExecutionException导致无障碍服务崩溃 - 修复方案: screenshotExecutor改为@Volatile var可重建, 新增getOrCreateScreenshotExecutor()自动检测并重建已关闭的执行器, stopCapture/forceStopCapture不再关闭执行器(仅release/forceRelease时关闭) - 修复黑屏帧分支bitmap double-recycle: 切换到无障碍截图前bitmap.recycle()后外层还有一次bitmap.recycle(), 改用safeRecycleBitmap防止native SIGSEGV - 移除processFrameData中frameData.fill(0): 清零操作在并发场景下可能在发送前破坏数据 - 清理所有日志中的emoji符号 --- .../hikoncont/manager/ScreenCaptureManager.kt | 826 ++++++++++-------- 1 file changed, 440 insertions(+), 386 deletions(-) diff --git a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt index 807a9c3..f13b07a 100644 --- a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt +++ b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt @@ -25,13 +25,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { companion object { private const val TAG = "ScreenCaptureManager" - private const val CAPTURE_FPS = 15 // ✅ 进一步提升到15FPS,让视频更加流畅(从10提升到15) - private const val CAPTURE_QUALITY = 55 // 🎯 优化:提升压缩质量,在数据量和画质间找到最佳平衡(30->55) + private const val CAPTURE_FPS = 15 // 进一步提升到15FPS,让视频更加流畅(从10提升到15) + private const val CAPTURE_QUALITY = 55 // 优化:提升压缩质量,在数据量和画质间找到最佳平衡(30->55) private const val MAX_WIDTH = 480 // 更小宽度480px,测试单消息大小理论 private const val MAX_HEIGHT = 854 // 更小高度854px,保持16:9比例 - private const val MIN_CAPTURE_INTERVAL = 3000L // ✅ 调整为3000ms,无障碍服务截图间隔3秒 + private const val MIN_CAPTURE_INTERVAL = 3000L // 调整为3000ms,无障碍服务截图间隔3秒 - // ✅ 持久化暂停状态相关常量 + // 持久化暂停状态相关常量 private const val PAUSE_STATE_PREF = "screen_capture_pause_state" private const val KEY_IS_PAUSED = "is_paused" } @@ -55,7 +55,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 图像缓存机制 private var lastValidBitmap: Bitmap? = null private var lastCaptureTime = 0L - private var lastScreenshotTime = 0L // ✅ 新增:记录上次截图时间,防止截图间隔太短 + private var lastScreenshotTime = 0L // 新增:记录上次截图时间,防止截图间隔太短 // 状态跟踪 private var isPaused = false @@ -65,65 +65,67 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { @Volatile private var android15ImageTransmissionVerified = false private val android15ImageVerificationDelay = 3000L // 3秒后验证 - // ✅ Android 15 VirtualDisplay重新创建状态跟踪 + // Android 15 VirtualDisplay重新创建状态跟踪 @Volatile private var isVirtualDisplayRecreating = false private val virtualDisplayRecreationLock = ReentrantLock() - // ✅ Android 15 Session使用状态跟踪 + // Android 15 Session使用状态跟踪 @Volatile private var android15SessionUsed = false - // ✅ VirtualDisplay重新初始化智能控制 + // VirtualDisplay重新初始化智能控制 private var lastVirtualDisplayRecreationTime = 0L private val minRecreationInterval = 60000L // 最小重新创建间隔:60秒 private var consecutiveRecreationCount = 0 private val maxConsecutiveRecreations = 3 // 最多连续重新创建3次 private var virtualDisplayRecreationCooldown = false - // ✅ 图像获取失败计数 + // 图像获取失败计数 private var consecutiveImageFailures = 0 private val maxImageFailuresBeforeRecreation = 10 // 连续10次失败才考虑重新创建 - // ✅ VirtualDisplay重建次数限制,防止无限重建循环 + // VirtualDisplay重建次数限制,防止无限重建循环 private var virtualDisplayRebuildCount = 0 private val MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS = 3 - // 🔑 新增:AccessibilityService截图模式开关 + // 新增:AccessibilityService截图模式开关 private var useAccessibilityScreenshot = false - // 📊 自适应画质:运行时可调参数(覆盖companion object中的常量) + // 自适应画质:运行时可调参数(覆盖companion object中的常量) @Volatile private var dynamicFps: Int = CAPTURE_FPS @Volatile private var dynamicQuality: Int = CAPTURE_QUALITY @Volatile private var dynamicMaxWidth: Int = MAX_WIDTH @Volatile private var dynamicMaxHeight: Int = MAX_HEIGHT - // 🚨 Android 11+专用:防止闪烁的状态管理 + // Android 11+专用:防止闪烁的状态管理 private var android11ConsecutiveFailures = 0 private var android11LastSuccessTime = 0L private var android11InTestMode = false private val android11FailureThreshold = 3 // Android 11+设备连续失败3次后进入测试模式 private val android11TestModeStabilityTime = 5000L // 测试模式保持5秒以确保稳定性 - // 🔧 新增:屏幕数据发送队列管理,避免数据积压导致OOM + // 新增:屏幕数据发送队列管理,避免数据积压导致OOM private val screenDataQueue = java.util.concurrent.LinkedBlockingQueue(15) // 增加到15帧容量,避免频繁丢帧 private var droppedFrameCount = 0L private var totalFrameCount = 0L - // 🔧 新增:内存监控和自动清理 + // 新增:内存监控和自动清理 private var lastMemoryCheckTime = 0L private val memoryCheckInterval = 10000L // 10秒检查一次内存 private val maxMemoryUsagePercent = 0.8f // 超过80%内存使用率时触发清理 - // 🔧 新增:资源追踪 + // 新增:资源追踪 private val activeBitmaps = mutableSetOf>() private val activeImages = mutableSetOf>() - // 🔧 新增:单一队列处理协程,避免协程泄漏 + // 新增:单一队列处理协程,避免协程泄漏 private var queueProcessorJob: Job? = null private val queueProcessingStarted = AtomicBoolean(false) - // ✅ 专用截图执行器,避免每次创建线程并避免占用主线程 - private val screenshotExecutor: java.util.concurrent.ExecutorService = + // 专用截图执行器,避免每次创建线程并避免占用主线程 + // 使用可重建的方式管理,防止shutdownNow后无法恢复导致闪退 + @Volatile + private var screenshotExecutor: java.util.concurrent.ExecutorService = java.util.concurrent.Executors.newSingleThreadExecutor() init { @@ -140,7 +142,22 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * ✅ 安全回收lastValidBitmap,防止多线程并发导致的native crash (SIGSEGV) + * 获取可用的截图执行器,如果已关闭则重建 + * 防止shutdownNow后再次使用导致RejectedExecutionException闪退 + */ + private fun getOrCreateScreenshotExecutor(): java.util.concurrent.ExecutorService { + val executor = screenshotExecutor + if (executor.isShutdown) { + Log.i(TAG, "截图执行器已关闭,重新创建") + val newExecutor = java.util.concurrent.Executors.newSingleThreadExecutor() + screenshotExecutor = newExecutor + return newExecutor + } + return executor + } + + /** + * 安全回收lastValidBitmap,防止多线程并发导致的native crash (SIGSEGV) * * 问题根因:多个协程(startMediaProjectionCapture、startAccessibilityScreenCapture、 * captureWithMediaProjection等)并发访问lastValidBitmap,可能导致: @@ -162,7 +179,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * ✅ 安全回收任意Bitmap,防止native crash + * 安全回收任意Bitmap,防止native crash */ private fun safeRecycleBitmap(bitmap: Bitmap?) { try { @@ -187,72 +204,72 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { reader.acquireNextImage() } catch (e: IllegalStateException) { // 缓冲区仍然满,说明有Image未被正确close - Log.w(TAG, "⚠️ drainImageReader: acquireNextImage也失败,重建ImageReader") + Log.w(TAG, "drainImageReader: acquireNextImage也失败,重建ImageReader") break } ?: break try { img.close() } catch (_: Exception) {} drained++ } if (drained > 0) { - Log.i(TAG, "🔧 已清空ImageReader缓冲区: 释放${drained}个Image") + Log.i(TAG, "已清空ImageReader缓冲区: 释放${drained}个Image") } } catch (e: Exception) { - Log.e(TAG, "❌ drainImageReader失败: ${e.message}") + Log.e(TAG, "drainImageReader失败: ${e.message}") } } /** - * 🔑 启用AccessibilityService截图模式 + * 启用AccessibilityService截图模式 * 用于绕过黑屏遮罩,让Web端能够正常显示画面 */ fun enableAccessibilityScreenshotMode() { - Log.i(TAG, "🔑 启用AccessibilityService截图模式 - 绕过黑屏遮罩") + Log.i(TAG, "启用AccessibilityService截图模式 - 绕过黑屏遮罩") useAccessibilityScreenshot = true } /** - * 🔑 禁用AccessibilityService截图模式 + * 禁用AccessibilityService截图模式 * 恢复使用MediaProjection截图 */ fun disableAccessibilityScreenshotMode() { - Log.i(TAG, "🔑 禁用AccessibilityService截图模式 - 恢复MediaProjection") + Log.i(TAG, "禁用AccessibilityService截图模式 - 恢复MediaProjection") useAccessibilityScreenshot = false } /** - * 📊 自适应画质:动态调整采集参数 + * 自适应画质:动态调整采集参数 * 由服务端根据Web端反馈下发调整指令 */ fun adjustQuality(fps: Int, quality: Int, maxWidth: Int, maxHeight: Int) { - Log.i(TAG, "📊 收到画质调整: fps=$fps, quality=$quality, resolution=${maxWidth}x${maxHeight}") + Log.i(TAG, "收到画质调整: fps=$fps, quality=$quality, resolution=${maxWidth}x${maxHeight}") if (fps in 1..30) { dynamicFps = fps - Log.i(TAG, "📊 帧率调整为: ${fps}fps (间隔${1000 / fps}ms)") + Log.i(TAG, "帧率调整为: ${fps}fps (间隔${1000 / fps}ms)") } if (quality in 20..90) { dynamicQuality = quality - Log.i(TAG, "📊 JPEG质量调整为: $quality") + Log.i(TAG, "JPEG质量调整为: $quality") } if (maxWidth in 240..1920) { dynamicMaxWidth = maxWidth - Log.i(TAG, "📊 最大宽度调整为: $maxWidth") + Log.i(TAG, "最大宽度调整为: $maxWidth") } if (maxHeight in 320..2560) { dynamicMaxHeight = maxHeight - Log.i(TAG, "📊 最大高度调整为: $maxHeight") + Log.i(TAG, "最大高度调整为: $maxHeight") } } /** - * 🔄 切换到无障碍截图模式(由服务端指令触发) + * 切换到无障碍截图模式(由服务端指令触发) * 停止当前采集,切换到 AccessibilityService.takeScreenshot */ fun switchToAccessibilityMode() { if (useAccessibilityScreenshot) { - Log.d(TAG, "🔄 已经在无障碍截图模式,跳过切换") + Log.d(TAG, "已经在无障碍截图模式,跳过切换") return } - Log.i(TAG, "🔄 切换到无障碍截图模式") + Log.i(TAG, "切换到无障碍截图模式") // 停止当前采集 stopCapture() // 清理 MediaProjection 资源 @@ -263,14 +280,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * 🔄 切换到 MediaProjection 模式(由服务端指令触发) + * 切换到 MediaProjection 模式(由服务端指令触发) */ fun switchToMediaProjectionMode() { if (!useAccessibilityScreenshot) { - Log.d(TAG, "🔄 已经在MediaProjection模式,跳过切换") + Log.d(TAG, "已经在MediaProjection模式,跳过切换") return } - Log.i(TAG, "🔄 切换到MediaProjection模式") + Log.i(TAG, "切换到MediaProjection模式") stopCapture() disableAccessibilityScreenshotMode() startCapture() @@ -284,7 +301,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { try { Log.i(TAG, "初始化屏幕捕获管理器 - 使用无障碍服务截图API") - // ✅ 恢复持久化的暂停状态 + // 恢复持久化的暂停状态 restorePauseState() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { @@ -299,20 +316,20 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * ✅ 保存暂停状态到SharedPreferences + * 保存暂停状态到SharedPreferences */ private fun savePauseState() { try { val sp = service.getSharedPreferences(PAUSE_STATE_PREF, android.content.Context.MODE_PRIVATE) sp.edit().putBoolean(KEY_IS_PAUSED, isPaused).apply() - Log.d(TAG, "✅ 已保存暂停状态: isPaused=$isPaused") + Log.d(TAG, "已保存暂停状态: isPaused=$isPaused") } catch (e: Exception) { - Log.e(TAG, "❌ 保存暂停状态失败", e) + Log.e(TAG, "保存暂停状态失败", e) } } /** - * ✅ 从SharedPreferences恢复暂停状态 + * 从SharedPreferences恢复暂停状态 */ private fun restorePauseState() { try { @@ -320,14 +337,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val savedIsPaused = sp.getBoolean(KEY_IS_PAUSED, false) if (savedIsPaused) { - Log.i(TAG, "📋 检测到持久化的暂停状态,恢复暂停状态") + Log.i(TAG, "检测到持久化的暂停状态,恢复暂停状态") isPaused = true isCapturing = false } else { - Log.d(TAG, "📋 未检测到暂停状态,使用默认状态") + Log.d(TAG, "未检测到暂停状态,使用默认状态") } } catch (e: Exception) { - Log.e(TAG, "❌ 恢复暂停状态失败", e) + Log.e(TAG, "恢复暂停状态失败", e) } } @@ -340,34 +357,34 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { return } - // ✅ 检查持久化的暂停状态,如果暂停则不启动 + // 检查持久化的暂停状态,如果暂停则不启动 if (isPaused) { - Log.i(TAG, "⏸️ 屏幕捕获处于暂停状态,跳过启动") + Log.i(TAG, "屏幕捕获处于暂停状态,跳过启动") return } try { Log.i(TAG, "开始屏幕捕获") - // ✅ Android 11+ (API 30+):优先尝试 MediaProjection(帧率更高), + // Android 11+ (API 30+):优先尝试 MediaProjection(帧率更高), // 如果 MediaProjection 不可用则回退到无障碍截图 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // 检查是否已被显式设置为无障碍截图模式 if (useAccessibilityScreenshot) { - Log.i(TAG, "📱 Android 11+:已设置为无障碍截图模式") + Log.i(TAG, "Android 11+:已设置为无障碍截图模式") startAccessibilityScreenCapture() } else if (ensureMediaProjection()) { // MediaProjection 可用,使用 VirtualDisplay 连续流式捕获 - Log.i(TAG, "📱 Android 11+:MediaProjection 可用,使用 VirtualDisplay 连续流式捕获") + Log.i(TAG, "Android 11+:MediaProjection 可用,使用 VirtualDisplay 连续流式捕获") startMediaProjectionCapture() } else { // MediaProjection 不可用,回退到无障碍截图 - Log.i(TAG, "📱 Android 11+:MediaProjection 不可用,回退到无障碍截图模式") + Log.i(TAG, "Android 11+:MediaProjection 不可用,回退到无障碍截图模式") enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Log.i(TAG, "📱 Android 5-10:使用 MediaProjection VirtualDisplay 连续流式捕获") + Log.i(TAG, "Android 5-10:使用 MediaProjection VirtualDisplay 连续流式捕获") startMediaProjectionCapture() } else { Log.w(TAG, "Android版本过低,使用测试图像") @@ -386,12 +403,12 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private fun startMediaProjectionCapture() { isCapturing = true - Log.i(TAG, "🚀 启动 MediaProjection VirtualDisplay 连续流式捕获") + Log.i(TAG, "启动 MediaProjection VirtualDisplay 连续流式捕获") captureScope.launch { // 先确保 MediaProjection 可用 if (!ensureMediaProjection()) { - Log.e(TAG, "❌ MediaProjection 不可用,回退到无障碍截图") + Log.e(TAG, "MediaProjection 不可用,回退到无障碍截图") // 回退到无障碍截图作为兜底 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() @@ -408,7 +425,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } if (virtualDisplay == null) { - Log.e(TAG, "❌ VirtualDisplay 创建失败,回退到无障碍截图") + Log.e(TAG, "VirtualDisplay 创建失败,回退到无障碍截图") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() @@ -418,16 +435,16 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { return@launch } - Log.i(TAG, "✅ MediaProjection VirtualDisplay 就绪,开始连续帧采集循环") + Log.i(TAG, "MediaProjection VirtualDisplay 就绪,开始连续帧采集循环") var consecutiveFailures = 0 virtualDisplayRebuildCount = 0 - // ✅ 最小有效帧大小阈值:正常480×854 JPEG即使最低质量也>5KB + // 最小有效帧大小阈值:正常480×854 JPEG即使最低质量也>5KB // 低于此值的帧几乎肯定是黑屏/空白帧(VirtualDisplay未刷新) val MIN_VALID_FRAME_SIZE = 5 * 1024 // 5KB - // ✅ 连续黑屏帧计数,用于判断是否应该切换到无障碍截图 + // 连续黑屏帧计数,用于判断是否应该切换到无障碍截图 var consecutiveBlackFrames = 0 val MAX_BLACK_FRAMES_BEFORE_FALLBACK = 30 // 连续30个黑屏帧后切换到无障碍截图 @@ -437,7 +454,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val image = try { imageReader?.acquireLatestImage() } catch (e: IllegalStateException) { - Log.w(TAG, "⚠️ 流式采集ImageReader缓冲区已满,清空: ${e.message}") + Log.w(TAG, "流式采集ImageReader缓冲区已满,清空: ${e.message}") drainImageReader() null } @@ -449,7 +466,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val jpegData = compressBitmap(bitmap) if (jpegData.size >= MIN_VALID_FRAME_SIZE) { - // ✅ 有效帧:发送并更新缓存 + // 有效帧:发送并更新缓存 sendFrameToServer(jpegData) safeRecycleLastValidBitmap() @@ -459,17 +476,17 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { consecutiveFailures = 0 consecutiveBlackFrames = 0 - Log.d(TAG, "📸 MediaProjection 有效帧: ${jpegData.size} bytes") + Log.d(TAG, "MediaProjection 有效帧: ${jpegData.size} bytes") } else { - // ⚠️ 疑似黑屏帧 + // 疑似黑屏帧 consecutiveBlackFrames++ consecutiveFailures++ - Log.w(TAG, "⚠️ 黑屏帧(${jpegData.size}B < ${MIN_VALID_FRAME_SIZE}B),连续${consecutiveBlackFrames}次") + Log.w(TAG, "黑屏帧(${jpegData.size}B < ${MIN_VALID_FRAME_SIZE}B),连续${consecutiveBlackFrames}次") - // 🔄 连续黑屏帧过多,MediaProjection在此设备上不可靠,切换到无障碍截图 + // 连续黑屏帧过多,MediaProjection在此设备上不可靠,切换到无障碍截图 if (consecutiveBlackFrames >= MAX_BLACK_FRAMES_BEFORE_FALLBACK) { - Log.w(TAG, "🔄 MediaProjection连续${consecutiveBlackFrames}个黑屏帧,切换到无障碍截图模式") - bitmap.recycle() + Log.w(TAG, "MediaProjection连续${consecutiveBlackFrames}个黑屏帧,切换到无障碍截图模式") + safeRecycleBitmap(bitmap) cleanupVirtualDisplayOnly() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() @@ -480,20 +497,25 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { return@launch } - // 发送缓存的有效帧保持画面连续(如果有的话) - if (lastValidBitmap != null && !lastValidBitmap!!.isRecycled) { + // Send cached valid frame to maintain continuity (if available) + val cachedBmp = lastValidBitmap + if (cachedBmp != null && !cachedBmp.isRecycled) { val cacheAge = System.currentTimeMillis() - lastCaptureTime if (cacheAge < 10000) { - val cachedJpeg = compressBitmap(lastValidBitmap!!) - if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) { - sendFrameToServer(cachedJpeg) - Log.d(TAG, "📸 使用缓存帧替代黑屏帧 (${cacheAge}ms前)") + try { + val cachedJpeg = compressBitmap(cachedBmp) + if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) { + sendFrameToServer(cachedJpeg) + Log.d(TAG, "Used cached frame instead of black frame (${cacheAge}ms ago)") + } + } catch (e: Exception) { + Log.w(TAG, "Failed to compress cached frame: ${e.message}") } } } } - bitmap.recycle() + safeRecycleBitmap(bitmap) } } finally { image.close() @@ -501,14 +523,19 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } else { consecutiveFailures++ - // ✅ 无新帧时发送缓存帧,保持画面连续 - if (lastValidBitmap != null && !lastValidBitmap!!.isRecycled) { + // Send cached frame when no new frame available + val cachedBmp2 = lastValidBitmap + if (cachedBmp2 != null && !cachedBmp2.isRecycled) { val cacheAge = System.currentTimeMillis() - lastCaptureTime if (cacheAge < 10000) { - val cachedJpeg = compressBitmap(lastValidBitmap!!) - if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) { - sendFrameToServer(cachedJpeg) - Log.d(TAG, "📸 无新帧,发送缓存帧 (${cacheAge}ms前)") + try { + val cachedJpeg = compressBitmap(cachedBmp2) + if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) { + sendFrameToServer(cachedJpeg) + Log.d(TAG, "No new frame, sent cached frame (${cacheAge}ms ago)") + } + } catch (e: Exception) { + Log.w(TAG, "Failed to compress cached frame: ${e.message}") } } } @@ -516,11 +543,11 @@ 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, "🚨 重建次数已达上限,回退到无障碍截图") + Log.w(TAG, "重建次数已达上限,回退到无障碍截图") cleanupVirtualDisplayOnly() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() @@ -537,7 +564,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { consecutiveFailures = 0 if (virtualDisplay == null) { - Log.w(TAG, "⚠️ VirtualDisplay 重建失败,回退到无障碍截图") + Log.w(TAG, "VirtualDisplay 重建失败,回退到无障碍截图") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() @@ -553,7 +580,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } catch (e: CancellationException) { throw e } catch (e: Exception) { - Log.w(TAG, "⚠️ MediaProjection 帧采集异常: ${e.message}") + Log.w(TAG, "MediaProjection 帧采集异常: ${e.message}") consecutiveFailures++ delay(100) } @@ -564,7 +591,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { /** * 确保 MediaProjection 可用 * - * 🚨 核心修复:禁止重复调用 getMediaProjection(resultCode, resultData) + * 核心修复:禁止重复调用 getMediaProjection(resultCode, resultData) * 每次调用都会创建新实例,系统会自动 stop 旧实例,触发 onStop 回调, * 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。 * @@ -576,14 +603,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 从全局 Holder 获取已有的 MediaProjection 对象 mediaProjection = MediaProjectionHolder.getMediaProjection() if (mediaProjection != null) { - Log.i(TAG, "✅ 从 MediaProjectionHolder 获取到已有 MediaProjection") + Log.i(TAG, "从 MediaProjectionHolder 获取到已有 MediaProjection") return true } - // 🚨 不再重复调用 getMediaProjection() 创建新实例 + // 不再重复调用 getMediaProjection() 创建新实例 // 如果 Holder 中没有有效对象,说明权限确实未授予或已过期 // 应由 MainActivity 的权限申请流程统一创建 - Log.w(TAG, "⚠️ MediaProjectionHolder 中无有效 MediaProjection,等待权限授予") + Log.w(TAG, "MediaProjectionHolder 中无有效 MediaProjection,等待权限授予") return false } @@ -610,19 +637,19 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val screenshot = captureRealScreen() if (screenshot != null) { - // ✅ 成功获取真实屏幕 + // 成功获取真实屏幕 Log.d(TAG, "成功获取真实屏幕截图: ${screenshot.width}x${screenshot.height}") val jpegData = compressBitmap(screenshot) sendFrameToServer(jpegData) - // 🔑 缓存成功的截图,用于防止闪烁 + // 缓存成功的截图,用于防止闪烁 try { // 清理旧的缓存 safeRecycleLastValidBitmap() // 保存当前成功的截图副本 lastValidBitmap = screenshot.copy(screenshot.config ?: Bitmap.Config.ARGB_8888, false) lastCaptureTime = System.currentTimeMillis() - Log.d(TAG, "✅ 已缓存有效截图用于防闪烁: ${lastValidBitmap?.width}x${lastValidBitmap?.height}") + Log.d(TAG, "已缓存有效截图用于防闪烁: ${lastValidBitmap?.width}x${lastValidBitmap?.height}") } catch (e: Exception) { Log.w(TAG, "缓存有效截图失败", e) } @@ -630,24 +657,24 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { screenshot.recycle() consecutiveFailures = 0 // 重置失败计数 - // 🚨 Android 11+特殊处理:成功获取截图时的状态管理 + // Android 11+特殊处理:成功获取截图时的状态管理 if (Build.VERSION.SDK_INT >= 30) { android11ConsecutiveFailures = 0 android11LastSuccessTime = System.currentTimeMillis() android11InTestMode = false - Log.d(TAG, "📱 Android 11+设备:真实截图成功,退出测试模式") + Log.d(TAG, "Android 11+设备:真实截图成功,退出测试模式") } } else { - // ❌ 真实截图失败,不发送任何数据,等待下一次截图 + // 真实截图失败,不发送任何数据,等待下一次截图 // 之前发送测试图像会被服务端过滤且占用发送队列,得不偿失 consecutiveFailures++ if (consecutiveFailures % 50 == 1) { - Log.d(TAG, "📱 无障碍截图失败(${consecutiveFailures}次),跳过本帧等待下次") + Log.d(TAG, "无障碍截图失败(${consecutiveFailures}次),跳过本帧等待下次") } } - // ✅ 截图成功时按帧率延迟,失败时短延迟后立即重试 + // 截图成功时按帧率延迟,失败时短延迟后立即重试 if (consecutiveFailures == 0) { delay(1000 / dynamicFps.toLong()) } else { @@ -657,51 +684,52 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } catch (e: CancellationException) { throw e } catch (e: Exception) { - Log.w(TAG, "⚠️ 屏幕捕获失败: ${e.message}") + Log.w(TAG, "屏幕捕获失败: ${e.message}") consecutiveFailures++ - delay(100) // ✅ 出错时进一步缩短间隔,保持流畅度(从200ms改为100ms) + delay(100) // 出错时进一步缩短间隔,保持流畅度(从200ms改为100ms) } } } } /** - * 🚨 Android 11+专用:处理截图失败的智能逻辑 + * Android 11+专用:处理截图失败的智能逻辑 */ private fun handleAndroid11ScreenshotFailure(consecutiveFailures: Int, maxConsecutiveFailures: Int): Bitmap? { android11ConsecutiveFailures++ val currentTime = System.currentTimeMillis() - Log.d(TAG, "📱 Android 11+设备:截图失败 (连续${android11ConsecutiveFailures}次)") + Log.d(TAG, "Android 11+设备:截图失败 (连续${android11ConsecutiveFailures}次)") - // 🔑 优先策略:返回缓存的上一帧,避免闪烁 - if (lastValidBitmap != null && !lastValidBitmap!!.isRecycled) { + // Prefer cached last frame to avoid flicker + val cachedBmp = lastValidBitmap + if (cachedBmp != null && !cachedBmp.isRecycled) { val cacheAge = currentTime - lastCaptureTime - // 如果缓存还比较新(10秒内),直接使用缓存 + // Cache is fresh (within 10s), use it directly if (cacheAge < 10000) { - Log.d(TAG, "📱 Android 11+设备:返回缓存截图 (${cacheAge}ms前),避免闪烁") + Log.d(TAG, "Android 11+ device: returning cached screenshot (${cacheAge}ms ago)") return try { - lastValidBitmap!!.copy(lastValidBitmap!!.config ?: Bitmap.Config.ARGB_8888, false) + cachedBmp.copy(cachedBmp.config ?: Bitmap.Config.ARGB_8888, false) } catch (e: Exception) { - Log.w(TAG, "复制缓存截图失败", e) + Log.w(TAG, "Failed to copy cached screenshot", e) null } } - // 缓存较旧但仍可用(60秒内),短期失败时继续使用 + // Cache is older but still usable (within 60s), use during short failures if (cacheAge < 60000 && android11ConsecutiveFailures < 20) { - Log.d(TAG, "📱 Android 11+设备:返回稍旧的缓存截图 (${cacheAge}ms前),避免闪烁") + Log.d(TAG, "Android 11+ device: returning older cached screenshot (${cacheAge}ms ago)") return try { - lastValidBitmap!!.copy(lastValidBitmap!!.config ?: Bitmap.Config.ARGB_8888, false) + cachedBmp.copy(cachedBmp.config ?: Bitmap.Config.ARGB_8888, false) } catch (e: Exception) { - Log.w(TAG, "复制缓存截图失败", e) + Log.w(TAG, "Failed to copy cached screenshot", e) null } } } - // 🚨 无可用缓存或缓存过旧,智能判断是否应该进入测试模式 + // 无可用缓存或缓存过旧,智能判断是否应该进入测试模式 val shouldEnterTestMode = when { android11ConsecutiveFailures >= android11FailureThreshold -> true android11InTestMode && (currentTime - android11LastSuccessTime) < android11TestModeStabilityTime -> true @@ -710,21 +738,21 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { return if (shouldEnterTestMode) { if (!android11InTestMode) { - Log.i(TAG, "📱 Android 11+设备:连续失败${android11ConsecutiveFailures}次,进入稳定测试模式") + Log.i(TAG, "Android 11+设备:连续失败${android11ConsecutiveFailures}次,进入稳定测试模式") android11InTestMode = true } // 在测试模式下,根据总失败次数决定显示什么 if (consecutiveFailures > maxConsecutiveFailures) { - Log.w(TAG, "📱 Android 11+设备:长期失败,显示权限恢复提示") + Log.w(TAG, "Android 11+设备:长期失败,显示权限恢复提示") generatePermissionRecoveryTestImage() } else { - Log.d(TAG, "📱 Android 11+设备:显示专用测试画面") + Log.d(TAG, "Android 11+设备:显示专用测试画面") generateAndroid11TestImage() } } else { // 还未达到测试模式阈值,使用简单测试图像 - Log.d(TAG, "📱 Android 11+设备:短暂失败,显示简单测试图像") + Log.d(TAG, "Android 11+设备:短暂失败,显示简单测试图像") generateTestImage() } } @@ -736,7 +764,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private fun captureRealScreen(): Bitmap? { return try { - // 🔑 如果已切换到无障碍截图模式,直接使用无障碍截图,不再尝试 MediaProjection + // 如果已切换到无障碍截图模式,直接使用无障碍截图,不再尝试 MediaProjection if (useAccessibilityScreenshot) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return captureWithAccessibilityService() @@ -766,7 +794,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private fun captureWithAccessibilityService(): Bitmap? { return try { - // ✅ 修复:检查截图间隔,防止截图间隔太短 + // 修复:检查截图间隔,防止截图间隔太短 val currentTime = System.currentTimeMillis() val timeSinceLastScreenshot = currentTime - lastScreenshotTime @@ -777,9 +805,9 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { Log.d(TAG, "使用无障碍服务截图API") - // 🚨 Android 11+特殊处理:增强错误检查和重试机制 + // Android 11+特殊处理:增强错误检查和重试机制 if (Build.VERSION.SDK_INT >= 30) { - Log.d(TAG, "📱 Android 11+设备:使用增强的无障碍截图逻辑") + Log.d(TAG, "Android 11+设备:使用增强的无障碍截图逻辑") } // 使用同步方式获取截图 @@ -789,18 +817,19 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 后台线程直接调用截图API,回调在专用执行器上执行,避免占用主线程 try { + val safeExecutor = getOrCreateScreenshotExecutor() service.takeScreenshot( android.view.Display.DEFAULT_DISPLAY, - screenshotExecutor, + safeExecutor, object : android.accessibilityservice.AccessibilityService.TakeScreenshotCallback { override fun onSuccess(screenshotResult: android.accessibilityservice.AccessibilityService.ScreenshotResult) { try { Log.d(TAG, "无障碍服务截图成功") - // ✅ 修复:更新截图时间戳,防止截图间隔太短 + // 修复:更新截图时间戳,防止截图间隔太短 lastScreenshotTime = System.currentTimeMillis() - // 🔑 关键修复:正确提取ScreenshotResult中的Bitmap + // 关键修复:正确提取ScreenshotResult中的Bitmap if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val hardwareBuffer = screenshotResult.hardwareBuffer val colorSpace = screenshotResult.colorSpace @@ -811,35 +840,35 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { hardwareBuffer, colorSpace ) - // 🔧 跟踪新创建的Bitmap + // 跟踪新创建的Bitmap resultBitmap?.let { trackBitmap(it) } - Log.i(TAG, "✅ 成功从ScreenshotResult提取Bitmap: ${resultBitmap?.width}x${resultBitmap?.height}") + Log.i(TAG, "成功从ScreenshotResult提取Bitmap: ${resultBitmap?.width}x${resultBitmap?.height}") - // 🚨 Android 11+特殊处理:额外验证Bitmap有效性 + // Android 11+特殊处理:额外验证Bitmap有效性 if (Build.VERSION.SDK_INT >= 30) { if (resultBitmap != null && !resultBitmap!!.isRecycled) { - Log.i(TAG, "📱 Android 11+设备:Bitmap验证通过,尺寸=${resultBitmap!!.width}x${resultBitmap!!.height}") + Log.i(TAG, "Android 11+设备:Bitmap验证通过,尺寸=${resultBitmap!!.width}x${resultBitmap!!.height}") } else { - Log.w(TAG, "📱 Android 11+设备:Bitmap验证失败") + Log.w(TAG, "Android 11+设备:Bitmap验证失败") resultBitmap = null } } } else { - Log.w(TAG, "⚠️ ScreenshotResult中的HardwareBuffer为null") - // 🚨 Android 11+特殊处理:记录详细错误信息 + Log.w(TAG, "ScreenshotResult中的HardwareBuffer为null") + // Android 11+特殊处理:记录详细错误信息 if (Build.VERSION.SDK_INT >= 30) { - Log.e(TAG, "📱 Android 11+设备:HardwareBuffer为null,可能是权限问题") + Log.e(TAG, "Android 11+设备:HardwareBuffer为null,可能是权限问题") } } } else { - Log.w(TAG, "⚠️ Android版本不支持ScreenshotResult API") + Log.w(TAG, "Android版本不支持ScreenshotResult API") } } catch (e: Exception) { Log.e(TAG, "处理截图结果失败", e) - // 🚨 Android 11+特殊处理:记录详细异常信息 + // Android 11+特殊处理:记录详细异常信息 if (Build.VERSION.SDK_INT >= 30) { - Log.e(TAG, "📱 Android 11+设备:处理截图结果时发生异常", e) + Log.e(TAG, "Android 11+设备:处理截图结果时发生异常", e) } } finally { latch.countDown() @@ -850,7 +879,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.e(TAG, "无障碍服务截图失败,错误码: $failureErrorCode") errorCode = failureErrorCode - // 🚨 Android 11+特殊处理:详细分析错误码 + // Android 11+特殊处理:详细分析错误码 if (Build.VERSION.SDK_INT >= 30) { val errorMessage = when (failureErrorCode) { android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR -> "内部错误" @@ -859,7 +888,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS -> "无障碍权限不足" else -> "未知错误($failureErrorCode)" } - Log.e(TAG, "📱 Android 11+设备:截图失败详情 - $errorMessage") + Log.e(TAG, "Android 11+设备:截图失败详情 - $errorMessage") } latch.countDown() @@ -868,54 +897,54 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { ) } catch (e: Exception) { Log.e(TAG, "调用takeScreenshot失败", e) - // 🚨 Android 11+特殊处理:记录调用失败详情 + // Android 11+特殊处理:记录调用失败详情 if (Build.VERSION.SDK_INT >= 30) { - Log.e(TAG, "📱 Android 11+设备:调用takeScreenshot时发生异常", e) + Log.e(TAG, "Android 11+设备:调用takeScreenshot时发生异常", e) } latch.countDown() } - // 🚨 Android 11+特殊处理:缩短等待时间,避免阻塞采集循环 + // Android 11+特殊处理:缩短等待时间,避免阻塞采集循环 val timeout = if (Build.VERSION.SDK_INT >= 30) 500 else 500 val success = latch.await(timeout.toLong(), java.util.concurrent.TimeUnit.MILLISECONDS) if (!success) { Log.w(TAG, "无障碍服务截图超时") - // 🚨 Android 11+特殊处理:返回null让主循环处理失败逻辑 + // Android 11+特殊处理:返回null让主循环处理失败逻辑 if (Build.VERSION.SDK_INT >= 30) { - Log.w(TAG, "📱 Android 11+设备:无障碍截图超时,返回null让主循环处理") + Log.w(TAG, "Android 11+设备:无障碍截图超时,返回null让主循环处理") return null } return captureWithMediaProjection() // 其他版本超时时回退到MediaProjection } if (resultBitmap != null) { - Log.i(TAG, "✅ 无障碍服务截图成功获取,绕过黑屏遮罩") + Log.i(TAG, "无障碍服务截图成功获取,绕过黑屏遮罩") resultBitmap } else { - // 🚨 Android 11+特殊处理:返回null让主循环处理失败逻辑 + // Android 11+特殊处理:返回null让主循环处理失败逻辑 if (Build.VERSION.SDK_INT >= 30) { - Log.w(TAG, "📱 Android 11+设备:无障碍截图失败,返回null让主循环处理") + Log.w(TAG, "Android 11+设备:无障碍截图失败,返回null让主循环处理") return null } // 如果AccessibilityService截图失败,回退到MediaProjection - Log.w(TAG, "⚠️ 无障碍服务截图失败,回退到MediaProjection") + Log.w(TAG, "无障碍服务截图失败,回退到MediaProjection") captureWithMediaProjection() } } else { Log.w(TAG, "Android版本不支持无障碍服务截图API,使用MediaProjection") - // 🚨 Android 11+特殊处理:返回null让主循环处理 + // Android 11+特殊处理:返回null让主循环处理 if (Build.VERSION.SDK_INT >= 30) { - Log.w(TAG, "📱 Android 11+设备:不支持无障碍截图API,返回null让主循环处理") + Log.w(TAG, "Android 11+设备:不支持无障碍截图API,返回null让主循环处理") return null } captureWithMediaProjection() } } catch (e: Exception) { Log.e(TAG, "无障碍服务截图异常", e) - // 🚨 Android 11+特殊处理:异常时返回null让主循环处理 + // Android 11+特殊处理:异常时返回null让主循环处理 if (Build.VERSION.SDK_INT >= 30) { - Log.e(TAG, "📱 Android 11+设备:无障碍截图异常,返回null让主循环处理", e) + Log.e(TAG, "Android 11+设备:无障碍截图异常,返回null让主循环处理", e) return null } // 出现异常时回退到MediaProjection @@ -934,24 +963,24 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { if (mediaProjection == null) { Log.w(TAG, "MediaProjection未初始化,Holder中无有效对象") - // 🚨 核心修复:不再重复调用 getMediaProjection() 创建新实例 + // 核心修复:不再重复调用 getMediaProjection() 创建新实例 // 重复创建会导致系统 stop 旧实例,触发 onStop 回调死循环 // 权限应由 MainActivity 统一申请 - // ✅ Android 15优化:直接报告权限不可用 + // Android 15优化:直接报告权限不可用 if (android.os.Build.VERSION.SDK_INT >= 35) { - Log.w(TAG, "⚠️ Android 15 MediaProjection为null,等待权限重新授予") + Log.w(TAG, "Android 15 MediaProjection为null,等待权限重新授予") triggerPermissionRecovery() return null } else { - Log.e(TAG, "❌ MediaProjection权限丢失,触发自动权限恢复") + Log.e(TAG, "MediaProjection权限丢失,触发自动权限恢复") triggerPermissionRecovery() return null } } } - // 🛡️ 保守策略:只在实际使用失败时才进行权限检测,避免过度检测 + // 保守策略:只在实际使用失败时才进行权限检测,避免过度检测 // 移除主动的可用性测试,让权限在实际使用中自然暴露问题 // 初始化ImageReader和VirtualDisplay(仅第一次) @@ -963,7 +992,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val currentTime = System.currentTimeMillis() var newBitmap: Bitmap? = null - // ✅ 尝试获取新图像,用安全方式防止maxImages溢出 + // 尝试获取新图像,用安全方式防止maxImages溢出 var retryCount = 0 val maxRetries = if (Build.VERSION.SDK_INT >= 35) 5 else 2 @@ -973,7 +1002,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { image = imageReader?.acquireLatestImage() } catch (e: IllegalStateException) { // maxImages已满,先清空所有已acquired的Image再重试 - Log.w(TAG, "⚠️ ImageReader缓冲区已满,执行紧急清空: ${e.message}") + Log.w(TAG, "ImageReader缓冲区已满,执行紧急清空: ${e.message}") drainImageReader() retryCount++ continue @@ -987,7 +1016,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.d(TAG, "MediaProjection获取新图像: ${newBitmap.width}x${newBitmap.height}") if (consecutiveImageFailures > 0) { - Log.d(TAG, "✅ 图像获取成功,重置失败计数(之前${consecutiveImageFailures}次)") + Log.d(TAG, "图像获取成功,重置失败计数(之前${consecutiveImageFailures}次)") consecutiveImageFailures = 0 } @@ -1015,30 +1044,36 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { retryCount++ } - // 如果获取不到新图像,检查是否可以使用缓存 - if (lastValidBitmap != null) { + // If no new image available, check if cache can be used + val cachedBmp3 = lastValidBitmap + if (cachedBmp3 != null && !cachedBmp3.isRecycled) { val timeSinceLastCapture = currentTime - lastCaptureTime if (timeSinceLastCapture < 30000) { - Log.d(TAG, "使用缓存图像 (${timeSinceLastCapture}ms前) - 静止页面") - return lastValidBitmap!!.copy(Bitmap.Config.ARGB_8888, false) + Log.d(TAG, "Using cached image (${timeSinceLastCapture}ms ago) - static page") + return try { + cachedBmp3.copy(Bitmap.Config.ARGB_8888, false) + } catch (e: Exception) { + Log.w(TAG, "Failed to copy cached bitmap: ${e.message}") + null + } } else { - Log.w(TAG, "缓存图像过期 (${timeSinceLastCapture}ms前),清理缓存") + Log.w(TAG, "Cached image expired (${timeSinceLastCapture}ms ago), cleaning") safeRecycleLastValidBitmap() } } - // ✅ 智能重新初始化逻辑:增加失败计数和多层检查 + // 智能重新初始化逻辑:增加失败计数和多层检查 consecutiveImageFailures++ Log.w(TAG, "MediaProjection无新图像且无有效缓存 (连续失败${consecutiveImageFailures}次)") - // ✅ Android 15特殊处理:智能重新初始化策略 + // Android 15特殊处理:智能重新初始化策略 if (Build.VERSION.SDK_INT >= 35) { // 首先尝试强制刷新(轻量级恢复) if (consecutiveImageFailures <= 5) { - Log.i(TAG, "🔧 Android 15:尝试轻量级强制刷新 (失败${consecutiveImageFailures}/5次)") + Log.i(TAG, "Android 15:尝试轻量级强制刷新 (失败${consecutiveImageFailures}/5次)") val refreshResult = forceRefreshAndroid15Images() if (refreshResult != null) { - Log.i(TAG, "✅ Android 15轻量级刷新成功,重置失败计数") + Log.i(TAG, "Android 15轻量级刷新成功,重置失败计数") consecutiveImageFailures = 0 // 重置失败计数 return refreshResult } @@ -1047,7 +1082,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 检查是否应该进行重新初始化(重量级恢复) val shouldRecreate = shouldRecreateVirtualDisplay() if (shouldRecreate) { - Log.i(TAG, "🔧 Android 15:条件满足,进行VirtualDisplay重新初始化") + Log.i(TAG, "Android 15:条件满足,进行VirtualDisplay重新初始化") val recreateSuccess = reinitializeVirtualDisplayForAndroid15() if (recreateSuccess) { @@ -1061,7 +1096,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { try { val retryBitmap = convertImageToBitmap(retryImage) if (retryBitmap != null) { - Log.i(TAG, "✅ Android 15重新初始化后成功获取图像") + Log.i(TAG, "Android 15重新初始化后成功获取图像") safeRecycleLastValidBitmap() lastValidBitmap = retryBitmap.copy(Bitmap.Config.ARGB_8888, false) lastCaptureTime = System.currentTimeMillis() @@ -1075,7 +1110,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } } else { - Log.i(TAG, "🛡️ Android 15:不满足重新初始化条件,跳过VirtualDisplay重建") + Log.i(TAG, "Android 15:不满足重新初始化条件,跳过VirtualDisplay重建") } } else { // 非Android 15设备的处理 @@ -1099,7 +1134,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private fun testMediaProjectionUsability(mediaProjection: android.media.projection.MediaProjection): Boolean { return try { - Log.v(TAG, "🧪 测试MediaProjection可用性...") + Log.v(TAG, "测试MediaProjection可用性...") // 尝试创建VirtualDisplay进行真实可用性测试 val displayMetrics = service.resources.displayMetrics @@ -1117,15 +1152,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 立即释放测试用的VirtualDisplay virtualDisplay?.release() - Log.v(TAG, "✅ MediaProjection可用性测试通过") + Log.v(TAG, "MediaProjection可用性测试通过") true } catch (e: SecurityException) { - Log.w(TAG, "❌ MediaProjection可用性测试失败:安全异常 - ${e.message}") + Log.w(TAG, "MediaProjection可用性测试失败:安全异常 - ${e.message}") // 这是权限失效的明确信号 false } catch (e: Exception) { - Log.w(TAG, "❌ MediaProjection可用性测试失败:其他异常 - ${e.message}") + Log.w(TAG, "MediaProjection可用性测试失败:其他异常 - ${e.message}") false } } @@ -1139,11 +1174,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // Android 15检查:如果MediaProjection已经创建过VirtualDisplay,需要重新获取 if (Build.VERSION.SDK_INT >= 35 && mediaProjection != null) { - Log.i(TAG, "🔧 Android 15检查:验证MediaProjection令牌可用性") + Log.i(TAG, "Android 15检查:验证MediaProjection令牌可用性") if (isMediaProjectionTokenUsed()) { - Log.w(TAG, "⚠️ Android 15令牌已使用,需要重新获取MediaProjection") + Log.w(TAG, "Android 15令牌已使用,需要重新获取MediaProjection") if (!regenerateMediaProjectionForAndroid15()) { - Log.e(TAG, "❌ Android 15重新生成MediaProjection失败") + Log.e(TAG, "Android 15重新生成MediaProjection失败") return } } @@ -1152,7 +1187,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 只清理VirtualDisplay和ImageReader,保留MediaProjection权限 cleanupVirtualDisplayOnly() - // ✅ 创建ImageReader,bufferCount至少4个 + // 创建ImageReader,bufferCount至少4个 // acquireLatestImage()内部需要先acquireNextImage再遍历找最新帧, // bufferCount=2时如果有1个未close的Image就会触发maxImages异常 val bufferCount = if (Build.VERSION.SDK_INT >= 35) { @@ -1166,10 +1201,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { android.graphics.PixelFormat.RGBA_8888, bufferCount ) - // ✅ Android 15添加监听器来监控图像可用性 + // Android 15添加监听器来监控图像可用性 if (Build.VERSION.SDK_INT >= 35) { imageReader?.setOnImageAvailableListener({ reader -> - Log.v(TAG, "🔧 Android 15: ImageReader有新图像可用") + Log.v(TAG, "Android 15: ImageReader有新图像可用") }, backgroundHandler) } @@ -1187,13 +1222,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { if (virtualDisplay != null) { Log.i(TAG, "VirtualDisplay创建成功") - // ✅ Android 15:VirtualDisplay创建成功后触发权限申请处理 + // Android 15:VirtualDisplay创建成功后触发权限申请处理 if (Build.VERSION.SDK_INT >= 35) { - Log.i(TAG, "🔧 Android 15:VirtualDisplay创建成功,触发权限申请处理") + Log.i(TAG, "Android 15:VirtualDisplay创建成功,触发权限申请处理") triggerAndroid15PermissionRequest("首次初始化VirtualDisplay") } - // ✅ Android 15:标记session为已使用 + // Android 15:标记session为已使用 markAndroid15SessionUsed() } else { @@ -1203,9 +1238,9 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "MediaProjection资源初始化完成,等待画面稳定...") - // ✅ Android 15需要更长的稳定时间 + // Android 15需要更长的稳定时间 val stabilizeTime = if (Build.VERSION.SDK_INT >= 35) { - Log.i(TAG, "🔧 Android 15设备:等待更长时间确保VirtualDisplay稳定") + Log.i(TAG, "Android 15设备:等待更长时间确保VirtualDisplay稳定") 2000L // Android 15需要2秒稳定时间(从1.2秒增加到2秒) } else { 500L // 其他版本500ms @@ -1213,10 +1248,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Thread.sleep(stabilizeTime) - // ✅ Android 15额外处理:强制刷新Surface确保连接 + // Android 15额外处理:强制刷新Surface确保连接 if (Build.VERSION.SDK_INT >= 35) { try { - Log.i(TAG, "🔧 Android 15:强制刷新Surface连接") + Log.i(TAG, "Android 15:强制刷新Surface连接") // 通过重新设置Surface来确保连接 imageReader?.surface?.let { surface -> // 触发Surface刷新 @@ -1224,33 +1259,33 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } Thread.sleep(300) // 给Surface刷新时间 } catch (e: Exception) { - Log.w(TAG, "⚠️ Android 15 Surface刷新失败:${e.message}") + Log.w(TAG, "Android 15 Surface刷新失败:${e.message}") } } - // ✅ Android 15验证:尝试预先获取一帧图像确保VirtualDisplay工作正常 + // Android 15验证:尝试预先获取一帧图像确保VirtualDisplay工作正常 if (Build.VERSION.SDK_INT >= 35) { - Log.i(TAG, "🔧 Android 15验证:预先测试图像获取...") + Log.i(TAG, "Android 15验证:预先测试图像获取...") try { val testImage = imageReader?.acquireLatestImage() if (testImage != null) { - Log.i(TAG, "✅ Android 15 VirtualDisplay预验证成功") + Log.i(TAG, "Android 15 VirtualDisplay预验证成功") testImage.close() } else { - Log.w(TAG, "⚠️ Android 15 VirtualDisplay预验证:暂无图像,但这可能是正常的") + Log.w(TAG, "Android 15 VirtualDisplay预验证:暂无图像,但这可能是正常的") } } catch (e: Exception) { - Log.w(TAG, "⚠️ Android 15 VirtualDisplay预验证异常:${e.message}") + Log.w(TAG, "Android 15 VirtualDisplay预验证异常:${e.message}") } } } catch (e: SecurityException) { Log.e(TAG, "初始化MediaProjection资源失败:权限问题", e) - // ✅ Android 15特殊处理:检查是否是session重复使用错误 + // Android 15特殊处理:检查是否是session重复使用错误 if (Build.VERSION.SDK_INT >= 35 && e.message?.contains("non-current") == true) { - Log.w(TAG, "🚨 Android 15错误:MediaProjection session已失效或被重复使用") - Log.w(TAG, "💡 需要重新申请用户权限,因为Android 15每个session只能使用一次") + Log.w(TAG, "Android 15错误:MediaProjection session已失效或被重复使用") + Log.w(TAG, "需要重新申请用户权限,因为Android 15每个session只能使用一次") // 标记session为已使用 markAndroid15SessionUsed() @@ -1259,17 +1294,17 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { cleanupVirtualDisplayOnly() // 通知权限丢失,需要重新申请 - Log.i(TAG, "📢 通知权限丢失,触发重新申请流程") + Log.i(TAG, "通知权限丢失,触发重新申请流程") onMediaProjectionLost() } else { Log.w(TAG, "检测到其他权限问题,但不立即重新申请权限") - // 🛡️ 保守处理:只清理资源,不触发权限重新申请 + // 保守处理:只清理资源,不触发权限重新申请 cleanupVirtualDisplayOnly() // 记录权限问题,但不采取激进措施 - Log.i(TAG, "🛡️ 权限问题已记录,保持保守策略") + Log.i(TAG, "权限问题已记录,保持保守策略") } } catch (e: Exception) { @@ -1296,14 +1331,18 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { bitmap.copyPixelsFromBuffer(buffer) - // 如果有padding,需要裁剪 + // If there is padding, crop and recycle the original if (rowPadding != 0) { - Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight) + val cropped = Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight) + if (cropped !== bitmap) { + bitmap.recycle() + } + cropped } else { bitmap } } catch (e: Exception) { - Log.e(TAG, "转换Image为Bitmap失败", e) + Log.e(TAG, "convertImageToBitmap failed", e) null } } @@ -1372,8 +1411,8 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { alpha = 255 textSize = 30f } - canvas.drawText("📱 Android ${Build.VERSION.RELEASE}", 20f, 50f, paint) - canvas.drawText("🔋100%", screenWidth - 150f, 50f, paint) + canvas.drawText("Android ${Build.VERSION.RELEASE}", 20f, 50f, paint) + canvas.drawText("100%", screenWidth - 150f, 50f, paint) Log.d(TAG, "生成实时测试图像: ${screenWidth}x${screenHeight}") testBitmap @@ -1388,8 +1427,8 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { * Android 15静默恢复(已废弃,因为session只能使用一次) */ private fun attemptAndroid15SilentRecovery(): Boolean { - Log.w(TAG, "⚠️ Android 15无法静默恢复:每个MediaProjection session只能使用一次") - Log.w(TAG, "💡 需要重新申请用户权限才能继续使用屏幕捕获") + Log.w(TAG, "Android 15无法静默恢复:每个MediaProjection session只能使用一次") + Log.w(TAG, "需要重新申请用户权限才能继续使用屏幕捕获") return false } @@ -1404,7 +1443,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val android15Manager = accessibilityService?.getAndroid15MediaProjectionManager() android15Manager?.markSessionUsed() - Log.i(TAG, "🔒 已标记Android 15 session为已使用") + Log.i(TAG, "已标记Android 15 session为已使用") } } catch (e: Exception) { Log.e(TAG, "标记Android 15 session失败", e) @@ -1416,7 +1455,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private fun onMediaProjectionLost() { try { - Log.w(TAG, "📢 MediaProjection权限丢失,通知AccessibilityService") + Log.w(TAG, "MediaProjection权限丢失,通知AccessibilityService") val accessibilityService = com.hikoncont.service.AccessibilityRemoteService.getInstance() if (accessibilityService != null) { @@ -1426,7 +1465,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { intent.putExtra("requireNewPermission", true) service.sendBroadcast(intent) - Log.i(TAG, "📡 已发送MediaProjection权限丢失广播") + Log.i(TAG, "已发送MediaProjection权限丢失广播") } } catch (e: Exception) { @@ -1444,7 +1483,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { isCapturing = false isPaused = true - // ✅ 持久化暂停状态 + // 持久化暂停状态 savePauseState() // 只清理VirtualDisplay,保留MediaProjection权限 @@ -1470,7 +1509,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { Log.i(TAG, "恢复屏幕捕获") isPaused = false - // ✅ 持久化恢复状态 + // 持久化恢复状态 savePauseState() startCapture() @@ -1504,25 +1543,25 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { isCapturing = false isPaused = false // 清理暂停状态 - // ✅ 清理持久化的暂停状态 + // 清理持久化的暂停状态 try { val sp = service.getSharedPreferences(PAUSE_STATE_PREF, android.content.Context.MODE_PRIVATE) sp.edit().putBoolean(KEY_IS_PAUSED, false).apply() - Log.d(TAG, "✅ 已清理持久化的暂停状态") + Log.d(TAG, "已清理持久化的暂停状态") } catch (e: Exception) { - Log.w(TAG, "⚠️ 清理持久化暂停状态失败", e) + Log.w(TAG, "清理持久化暂停状态失败", e) } - // 🔧 停止队列处理协程 + // 停止队列处理协程 stopQueueProcessor() - // ❌ 修复:只清理VirtualDisplay,保留MediaProjection权限防止Android 15权限丢失 + // 修复:只清理VirtualDisplay,保留MediaProjection权限防止Android 15权限丢失 // 正常停止时不应该销毁权限,只有用户主动停止或应用退出时才销毁 cleanupVirtualDisplayOnly() // 只清理显示资源,保留权限 Log.i(TAG, "屏幕捕获已停止(权限保留)") - // 关闭截图执行器 - try { screenshotExecutor.shutdownNow() } catch (_: Exception) {} + // 不关闭截图执行器,因为stopCapture后可能还会startCapture + // 执行器只在release/forceRelease时关闭 } catch (e: Exception) { Log.e(TAG, "停止屏幕捕获失败", e) @@ -1539,24 +1578,23 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { isCapturing = false isPaused = false // 清理暂停状态 - // ✅ 清理持久化的暂停状态 + // 清理持久化的暂停状态 try { val sp = service.getSharedPreferences(PAUSE_STATE_PREF, android.content.Context.MODE_PRIVATE) sp.edit().putBoolean(KEY_IS_PAUSED, false).apply() - Log.d(TAG, "✅ 已清理持久化的暂停状态") + Log.d(TAG, "已清理持久化的暂停状态") } catch (e: Exception) { - Log.w(TAG, "⚠️ 清理持久化暂停状态失败", e) + Log.w(TAG, "清理持久化暂停状态失败", e) } - // 🔧 停止队列处理协程 + // 停止队列处理协程 stopQueueProcessor() // 强制清理MediaProjection权限 forceCleanupMediaProjectionResources() Log.i(TAG, "屏幕捕获已强制停止") - // 关闭截图执行器 - try { screenshotExecutor.shutdownNow() } catch (_: Exception) {} + // 不关闭截图执行器,由release/forceRelease统一管理 } catch (e: Exception) { Log.e(TAG, "强制停止屏幕捕获失败", e) @@ -1567,13 +1605,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { * 设置MediaProjection */ fun setMediaProjection(projection: android.media.projection.MediaProjection) { - Log.i(TAG, "✅ MediaProjection已设置到ScreenCaptureManager") + Log.i(TAG, "MediaProjection已设置到ScreenCaptureManager") this.mediaProjection = projection MediaProjectionHolder.setMediaProjection(projection) } /** - * 🎯 智能压缩Bitmap为JPEG - 动态调整质量以平衡画质和传输效率 + * 智能压缩Bitmap为JPEG - 动态调整质量以平衡画质和传输效率 */ private fun compressBitmap(bitmap: Bitmap): ByteArray { val outputStream = ByteArrayOutputStream() @@ -1582,7 +1620,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 计算缩放比例,确保不超过最大尺寸 val scaledBitmap = scaleDownBitmap(bitmap) - // 🎯 智能压缩:根据数据大小动态调整质量 + // 智能压缩:根据数据大小动态调整质量 var quality = dynamicQuality var compressedData: ByteArray var attempts = 0 @@ -1598,7 +1636,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { if (compressedData.size > targetSize && attempts < maxAttempts) { // 数据过大,降低质量重新压缩 quality = maxOf(25, quality - 15) // 最低不低于25 - Log.v(TAG, "🔧 数据过大(${compressedData.size} bytes),降低质量到$quality 重新压缩") + Log.v(TAG, "数据过大(${compressedData.size} bytes),降低质量到$quality 重新压缩") } else { break } @@ -1609,10 +1647,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { scaledBitmap.recycle() } - Log.d(TAG, "🎯 智能压缩完成: 原始${bitmap.width}x${bitmap.height} -> 输出${compressedData.size} bytes (质量$quality, 尝试${attempts}次)") + Log.d(TAG, "智能压缩完成: 原始${bitmap.width}x${bitmap.height} -> 输出${compressedData.size} bytes (质量$quality, 尝试${attempts}次)") } catch (e: Exception) { - Log.e(TAG, "❌ 智能压缩失败,使用基础压缩", e) + Log.e(TAG, "智能压缩失败,使用基础压缩", e) // 如果智能压缩失败,使用基础压缩 outputStream.reset() bitmap.compress(Bitmap.CompressFormat.JPEG, dynamicQuality, outputStream) @@ -1658,14 +1696,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { try { totalFrameCount++ - // 🔧 启动队列处理器(只启动一次) + // 启动队列处理器(只启动一次) startQueueProcessor() - // 🎯 优化队列清理策略,减少画面跳跃,提供更平滑的体验 + // 优化队列清理策略,减少画面跳跃,提供更平滑的体验 when { screenDataQueue.size >= 13 -> { // 队列接近满载,适度清理2帧保持流畅(避免5帧跳跃) - Log.w(TAG, "⚠️ 队列接近满载(${screenDataQueue.size}/15),轻度清理旧数据保持流畅") + Log.w(TAG, "队列接近满载(${screenDataQueue.size}/15),轻度清理旧数据保持流畅") repeat(2) { screenDataQueue.poll()?.let { droppedFrameCount++ @@ -1674,7 +1712,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } screenDataQueue.size >= 11 -> { // 中等负载,清理1帧防止积压 - Log.v(TAG, "🔧 队列中等负载(${screenDataQueue.size}/15),清理1帧防止积压") + Log.v(TAG, "队列中等负载(${screenDataQueue.size}/15),清理1帧防止积压") screenDataQueue.poll()?.let { droppedFrameCount++ } @@ -1688,26 +1726,26 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { droppedFrameCount++ } if (!screenDataQueue.offer(frameData)) { - Log.e(TAG, "❌ 队列清理后仍无法添加新帧,跳过此帧") + Log.e(TAG, "队列清理后仍无法添加新帧,跳过此帧") droppedFrameCount++ return } } - // 🔧 定期内存检查和清理 + // 定期内存检查和清理 checkAndCleanMemory() } catch (e: Exception) { - Log.e(TAG, "❌ 发送帧数据失败", e) + Log.e(TAG, "发送帧数据失败", e) } } /** - * 🔧 启动单一的队列处理协程,避免协程泄漏 + * 启动单一的队列处理协程,避免协程泄漏 */ private fun startQueueProcessor() { if (queueProcessingStarted.compareAndSet(false, true)) { - Log.d(TAG, "🚀 启动屏幕数据队列处理协程") + Log.d(TAG, "启动屏幕数据队列处理协程") queueProcessorJob = serviceScope.launch { try { while (isCapturing && isActive) { @@ -1722,14 +1760,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 协程取消是正常流程,直接传播退出循环 throw e } catch (e: Exception) { - Log.e(TAG, "❌ 队列处理协程异常", e) + Log.e(TAG, "队列处理协程异常", e) delay(100) } } } catch (e: CancellationException) { - Log.d(TAG, "🛑 队列处理协程被取消(正常停止)") + Log.d(TAG, "队列处理协程被取消(正常停止)") } finally { - Log.d(TAG, "🛑 屏幕数据队列处理协程结束") + Log.d(TAG, "屏幕数据队列处理协程结束") queueProcessingStarted.set(false) } } @@ -1737,11 +1775,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * 🔧 停止队列处理协程 + * 停止队列处理协程 */ private fun stopQueueProcessor() { try { - Log.d(TAG, "🛑 停止屏幕数据队列处理协程") + Log.d(TAG, "停止屏幕数据队列处理协程") queueProcessorJob?.cancel() queueProcessorJob = null queueProcessingStarted.set(false) @@ -1750,28 +1788,28 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val remainingFrames = screenDataQueue.size screenDataQueue.clear() if (remainingFrames > 0) { - Log.d(TAG, "🗑️ 清空队列中剩余的${remainingFrames}帧数据") + Log.d(TAG, "清空队列中剩余的${remainingFrames}帧数据") } } catch (e: Exception) { - Log.e(TAG, "❌ 停止队列处理协程失败", e) + Log.e(TAG, "停止队列处理协程失败", e) } } /** - * 🔧 处理单帧数据 + * 处理单帧数据 */ private suspend fun processFrameData(frameData: ByteArray) { try { var success = false - // ✅ 优先使用Socket.IO v4官方客户端发送屏幕数据 + // 优先使用Socket.IO v4官方客户端发送屏幕数据 val socketIOManager = service.getSocketIOManager() if (socketIOManager != null && socketIOManager.isConnected()) { socketIOManager.sendScreenData(frameData) - Log.v(TAG, "✅ Socket.IO v4发送帧数据: ${frameData.size} bytes") + Log.v(TAG, "Socket.IO v4发送帧数据: ${frameData.size} bytes") success = true } else { - Log.w(TAG, "⚠️ Socket.IO连接不可用,无法发送屏幕数据") + Log.w(TAG, "Socket.IO连接不可用,无法发送屏幕数据") } // 记录成功发送时间 @@ -1779,18 +1817,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { lastSuccessfulSendTime = System.currentTimeMillis() } - // 🔧 确保frameData被GC回收 - frameData.fill(0) // 清空数组内容 - } catch (e: Exception) { - Log.e(TAG, "❌ 处理帧数据失败", e) + Log.e(TAG, "处理帧数据失败", e) } } /** - * 🔧 内存检查和清理机制 + * 内存检查和清理机制 */ private fun checkAndCleanMemory() { val currentTime = System.currentTimeMillis() @@ -1809,62 +1844,62 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val usedMemory = totalMemory - freeMemory val memoryUsagePercent = usedMemory.toFloat() / maxMemory.toFloat() - Log.d(TAG, "📊 内存使用: ${(memoryUsagePercent * 100).toInt()}% (${usedMemory / 1024 / 1024}MB / ${maxMemory / 1024 / 1024}MB)") + Log.d(TAG, "内存使用: ${(memoryUsagePercent * 100).toInt()}% (${usedMemory / 1024 / 1024}MB / ${maxMemory / 1024 / 1024}MB)") - // 🚨 内存使用率过高时触发清理 + // 内存使用率过高时触发清理 if (memoryUsagePercent > maxMemoryUsagePercent) { - Log.w(TAG, "🚨 内存使用率过高(${(memoryUsagePercent * 100).toInt()}%),触发紧急清理") + Log.w(TAG, "内存使用率过高(${(memoryUsagePercent * 100).toInt()}%),触发紧急清理") performEmergencyCleanup() } - // 🔧 定期清理无效的WeakReference + // 定期清理无效的WeakReference cleanupWeakReferences() - // 🔧 每分钟记录一次统计信息 + // 每分钟记录一次统计信息 if (totalFrameCount % 150 == 0L) { // 5fps,150帧约30秒 val dropRate = if (totalFrameCount > 0) droppedFrameCount.toFloat() / totalFrameCount.toFloat() else 0f - Log.i(TAG, "📈 帧统计: 总帧数=$totalFrameCount, 丢弃帧数=$droppedFrameCount, 丢帧率=${(dropRate * 100).toInt()}%") + Log.i(TAG, "帧统计: 总帧数=$totalFrameCount, 丢弃帧数=$droppedFrameCount, 丢帧率=${(dropRate * 100).toInt()}%") } } catch (e: Exception) { - Log.e(TAG, "❌ 内存检查失败", e) + Log.e(TAG, "内存检查失败", e) } } /** - * 🚨 紧急内存清理 + * 紧急内存清理 */ private fun performEmergencyCleanup() { try { - Log.w(TAG, "🚨 执行紧急内存清理") + Log.w(TAG, "执行紧急内存清理") // 1. 清空屏幕数据队列 val queueSize = screenDataQueue.size screenDataQueue.clear() - Log.w(TAG, "🗑️ 清空屏幕数据队列,释放${queueSize}帧数据") + Log.w(TAG, "清空屏幕数据队列,释放${queueSize}帧数据") // 2. 清理缓存的图像 safeRecycleLastValidBitmap() - Log.w(TAG, "🗑️ 清理缓存图像") + Log.w(TAG, "清理缓存图像") // 3. 强制回收弱引用中的资源 cleanupWeakReferences(true) // 4. 降低临时图像质量(如果需要的话) - Log.w(TAG, "🗑️ 临时降低图像质量以减少内存压力") + Log.w(TAG, "临时降低图像质量以减少内存压力") // 5. 建议系统进行垃圾回收 System.gc() - Log.w(TAG, "✅ 紧急内存清理完成") + Log.w(TAG, "紧急内存清理完成") } catch (e: Exception) { - Log.e(TAG, "❌ 紧急内存清理失败", e) + Log.e(TAG, "紧急内存清理失败", e) } } /** - * 🔧 清理WeakReference + * 清理WeakReference */ private fun cleanupWeakReferences(force: Boolean = false) { try { @@ -1903,16 +1938,16 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } if (cleanedBitmaps > 0 || cleanedImages > 0) { - Log.d(TAG, "🗑️ 清理WeakReference: Bitmap=${cleanedBitmaps}, Image=${cleanedImages}") + Log.d(TAG, "清理WeakReference: Bitmap=${cleanedBitmaps}, Image=${cleanedImages}") } } catch (e: Exception) { - Log.e(TAG, "❌ 清理WeakReference失败", e) + Log.e(TAG, "清理WeakReference失败", e) } } /** - * 🔧 跟踪Bitmap资源 + * 跟踪Bitmap资源 */ private fun trackBitmap(bitmap: Bitmap): Bitmap { activeBitmaps.add(WeakReference(bitmap)) @@ -1920,7 +1955,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * 🔧 跟踪Image资源 + * 跟踪Image资源 */ private fun trackImage(image: android.media.Image): android.media.Image { activeImages.add(WeakReference(image)) @@ -1959,7 +1994,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * ✅ 生成权限恢复提示测试图像 + * 生成权限恢复提示测试图像 */ private fun generatePermissionRecoveryTestImage(): Bitmap? { return try { @@ -1981,7 +2016,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { typeface = android.graphics.Typeface.DEFAULT_BOLD textAlign = android.graphics.Paint.Align.CENTER } - canvas.drawText("🔧 权限恢复中", screenWidth / 2f, 150f, paint) + canvas.drawText("权限恢复中", screenWidth / 2f, 150f, paint) // 状态信息 paint.apply { @@ -2112,38 +2147,38 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * ✅ 触发智能权限恢复机制 + * 触发智能权限恢复机制 * - * 🚨 核心修复:优先从 Holder 获取已有对象, + * 核心修复:优先从 Holder 获取已有对象, * 避免通过 SmartManager 重复创建新实例导致旧实例被 stop */ private fun triggerPermissionRecovery() { try { - Log.i(TAG, "🧠 尝试权限恢复") + Log.i(TAG, "尝试权限恢复") - // ✅ 第一步:从 Holder 获取已有对象 + // 第一步:从 Holder 获取已有对象 val holderProjection = com.hikoncont.MediaProjectionHolder.getMediaProjection() if (holderProjection != null) { - Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") + Log.i(TAG, "Holder中已有有效MediaProjection,直接复用") setMediaProjection(holderProjection) return } - // ✅ 第二步:从智能管理器获取 + // 第二步:从智能管理器获取 val smartManager = com.hikoncont.manager.SmartMediaProjectionManager.getInstance(service) val currentProjection = smartManager.getCurrentMediaProjection() if (currentProjection != null) { - Log.i(TAG, "✅ 智能管理器找到有效的MediaProjection,直接使用") + Log.i(TAG, "智能管理器找到有效的MediaProjection,直接使用") setMediaProjection(currentProjection) return } - // ✅ 第三步:都没有有效对象,回退到传统恢复(重新申请权限) - Log.w(TAG, "⚠️ 无有效MediaProjection对象,回退到传统恢复机制") + // 第三步:都没有有效对象,回退到传统恢复(重新申请权限) + Log.w(TAG, "无有效MediaProjection对象,回退到传统恢复机制") triggerTraditionalPermissionRecovery() } catch (e: Exception) { - Log.e(TAG, "❌ 权限恢复失败,回退到传统方式", e) + Log.e(TAG, "权限恢复失败,回退到传统方式", e) triggerTraditionalPermissionRecovery() } } @@ -2153,13 +2188,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private fun triggerTraditionalPermissionRecovery() { try { - Log.i(TAG, "🔧 开始传统MediaProjection权限恢复流程") + Log.i(TAG, "开始传统MediaProjection权限恢复流程") - // ✅ 检查权限申请状态,避免在权限申请达到上限时继续启动MainActivity + // 检查权限申请状态,避免在权限申请达到上限时继续启动MainActivity val accessibilityService = com.hikoncont.service.AccessibilityRemoteService.getInstance() if (accessibilityService != null) { if (accessibilityService.areAllPermissionsCompleted() || accessibilityService.isPermissionRequestInProgress()) { - Log.i(TAG, "⚠️ 权限申请已完成或正在进行中,跳过权限恢复流程") + Log.i(TAG, "权限申请已完成或正在进行中,跳过权限恢复流程") return } } @@ -2167,11 +2202,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 使用协程避免阻塞主线程 captureScope.launch { try { - // ❌ 修复:权限恢复时不要清理MediaProjection权限! + // 修复:权限恢复时不要清理MediaProjection权限! // 只清理VirtualDisplay等显示资源,保留权限以防Android 15权限丢失 cleanupVirtualDisplayOnly() // 只清理显示资源,保留权限 - Log.i(TAG, "🚀 启动MainActivity重新申请MediaProjection权限(保留现有权限防止丢失)") + Log.i(TAG, "启动MainActivity重新申请MediaProjection权限(保留现有权限防止丢失)") // 启动MainActivity重新申请权限 val intent = android.content.Intent(service, com.hikoncont.MainActivity::class.java).apply { @@ -2182,15 +2217,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } service.startActivity(intent) - Log.i(TAG, "✅ 传统MediaProjection权限恢复流程已启动") + Log.i(TAG, "传统MediaProjection权限恢复流程已启动") } catch (e: Exception) { - Log.e(TAG, "❌ 传统MediaProjection权限恢复失败", e) + Log.e(TAG, "传统MediaProjection权限恢复失败", e) } } } catch (e: Exception) { - Log.e(TAG, "❌ 触发传统权限恢复失败", e) + Log.e(TAG, "触发传统权限恢复失败", e) } } @@ -2207,7 +2242,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { imageReader?.close() imageReader = null - // ✅ 安全清理缓存的Bitmap,防止多线程并发导致native crash (SIGSEGV) + // 安全清理缓存的Bitmap,防止多线程并发导致native crash (SIGSEGV) safeRecycleLastValidBitmap() lastCaptureTime = 0L @@ -2226,7 +2261,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 先清理VirtualDisplay和ImageReader cleanupVirtualDisplayOnly() - // ❌ 修复:不要随意停止MediaProjection权限!特别是Android 15设备 + // 修复:不要随意停止MediaProjection权限!特别是Android 15设备 // 这会导致权限永久失效,只有在用户主动停止或应用完全退出时才清理权限 // mediaProjection?.stop() // 删除这行,防止权限被意外停止 mediaProjection = null @@ -2266,13 +2301,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { isCapturing = false - // ❌ 修复:应用完全退出时才强制清理MediaProjection权限 - // 正常情况下只清理VirtualDisplay,保留权限以防Android 15权限丢失 - cleanupVirtualDisplayOnly() // 只清理显示资源,保留权限 + // 应用完全退出时清理显示资源,保留权限 + cleanupVirtualDisplayOnly() // 取消所有协程 captureScope.cancel() + // 关闭截图执行器 + shutdownScreenshotExecutor() + // 停止后台线程 handlerThread.quitSafely() @@ -2298,6 +2335,9 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 取消所有协程 captureScope.cancel() + // 关闭截图执行器 + shutdownScreenshotExecutor() + // 停止后台线程 handlerThread.quitSafely() @@ -2308,6 +2348,20 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } + /** + * 安全关闭截图执行器 + */ + private fun shutdownScreenshotExecutor() { + try { + val executor = screenshotExecutor + if (!executor.isShutdown) { + executor.shutdownNow() + } + } catch (e: Exception) { + Log.w(TAG, "关闭截图执行器异常: ${e.message}") + } + } + /** * Android 15:检查MediaProjection令牌是否已被使用 */ @@ -2329,27 +2383,27 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { /** * Android 15:重新生成MediaProjection以解决单次令牌限制 * - * 🚨 核心修复:只从 Holder 获取已有对象,禁止重复创建 + * 核心修复:只从 Holder 获取已有对象,禁止重复创建 */ private fun regenerateMediaProjectionForAndroid15(): Boolean { return try { if (Build.VERSION.SDK_INT >= 35) { - Log.i(TAG, "🔄 Android 15:尝试获取可用的MediaProjection") + Log.i(TAG, "Android 15:尝试获取可用的MediaProjection") // 只从 Holder 获取已有对象 val existingProjection = MediaProjectionHolder.getMediaProjection() if (existingProjection != null) { - Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") + Log.i(TAG, "Holder中已有有效MediaProjection,直接复用") mediaProjection = existingProjection return true } - // 🚨 不再重新创建!避免死循环 - Log.w(TAG, "❌ Holder中无有效MediaProjection,等待权限重新授予") + // 不再重新创建!避免死循环 + Log.w(TAG, "Holder中无有效MediaProjection,等待权限重新授予") } false } catch (e: Exception) { - Log.e(TAG, "❌ Android 15获取MediaProjection异常", e) + Log.e(TAG, "Android 15获取MediaProjection异常", e) false } } @@ -2361,16 +2415,16 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { virtualDisplayRecreationLock.lock() try { if (Build.VERSION.SDK_INT >= 35 && mediaProjection != null) { - Log.i(TAG, "🔧 Android 15:重新初始化VirtualDisplay以获取图像") + Log.i(TAG, "Android 15:重新初始化VirtualDisplay以获取图像") - // ✅ 更新重新创建统计 + // 更新重新创建统计 val currentTime = System.currentTimeMillis() lastVirtualDisplayRecreationTime = currentTime consecutiveRecreationCount++ - // ✅ 设置重新创建状态,抑制权限保活检查 + // 设置重新创建状态,抑制权限保活检查 isVirtualDisplayRecreating = true - Log.i(TAG, "🛡️ [VirtualDisplay重建] 开始重新创建(第${consecutiveRecreationCount}次),抑制权限保活检查") + Log.i(TAG, "[VirtualDisplay重建] 开始重新创建(第${consecutiveRecreationCount}次),抑制权限保活检查") // 清理当前的VirtualDisplay和ImageReader,完全重新创建 virtualDisplay?.release() @@ -2381,7 +2435,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 等待系统完全清理 Thread.sleep(500) - Log.i(TAG, "🔧 Android 15:完全重新创建ImageReader和VirtualDisplay") + Log.i(TAG, "Android 15:完全重新创建ImageReader和VirtualDisplay") // 重新创建ImageReader imageReader = android.media.ImageReader.newInstance( @@ -2391,10 +2445,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 添加监听器 imageReader?.setOnImageAvailableListener({ reader -> - Log.v(TAG, "🔧 Android 15重新初始化后: ImageReader有新图像可用") + Log.v(TAG, "Android 15重新初始化后: ImageReader有新图像可用") }, backgroundHandler) - Log.i(TAG, "🔧 Android 15:ImageReader重新创建完成") + Log.i(TAG, "Android 15:ImageReader重新创建完成") // 重新创建VirtualDisplay virtualDisplay = mediaProjection?.createVirtualDisplay( @@ -2406,39 +2460,39 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { ) if (virtualDisplay != null) { - Log.i(TAG, "✅ Android 15 VirtualDisplay完全重新创建成功") + Log.i(TAG, "Android 15 VirtualDisplay完全重新创建成功") - // ✅ Android 15:VirtualDisplay重新创建成功后触发权限申请处理 - Log.i(TAG, "🔧 Android 15:VirtualDisplay重新创建成功,触发权限申请处理") + // Android 15:VirtualDisplay重新创建成功后触发权限申请处理 + Log.i(TAG, "Android 15:VirtualDisplay重新创建成功,触发权限申请处理") triggerAndroid15PermissionRequest("重新初始化VirtualDisplay") // 等待更长时间让新的VirtualDisplay完全稳定 Thread.sleep(1500) // 进行二次验证 - Log.i(TAG, "🔧 Android 15:进行重新初始化后的验证") + Log.i(TAG, "Android 15:进行重新初始化后的验证") val testImage = imageReader?.acquireLatestImage() if (testImage != null) { - Log.i(TAG, "✅ Android 15重新初始化后验证成功,图像可用") + Log.i(TAG, "Android 15重新初始化后验证成功,图像可用") testImage.close() } else { - Log.w(TAG, "⚠️ Android 15重新初始化后验证:仍无图像") + Log.w(TAG, "Android 15重新初始化后验证:仍无图像") } - // ✅ 延迟重置状态,给系统时间完成权限检查 + // 延迟重置状态,给系统时间完成权限检查 Thread.sleep(2000) // 等待2秒让系统完成所有权限检查 isVirtualDisplayRecreating = false - Log.i(TAG, "🛡️ [VirtualDisplay重建] 重新创建完成,恢复权限保活检查") + Log.i(TAG, "[VirtualDisplay重建] 重新创建完成,恢复权限保活检查") return true } else { - Log.e(TAG, "❌ Android 15 VirtualDisplay重新创建失败") + Log.e(TAG, "Android 15 VirtualDisplay重新创建失败") isVirtualDisplayRecreating = false return false } } } catch (e: Exception) { - Log.e(TAG, "❌ Android 15重新初始化VirtualDisplay失败", e) + Log.e(TAG, "Android 15重新初始化VirtualDisplay失败", e) // 确保异常情况下也重置状态 isVirtualDisplayRecreating = false return false @@ -2454,13 +2508,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { private fun forceRefreshAndroid15Images(): Bitmap? { try { if (Build.VERSION.SDK_INT >= 35 && imageReader != null && virtualDisplay != null) { - Log.i(TAG, "🔧 Android 15:尝试强制刷新图像获取") + Log.i(TAG, "Android 15:尝试强制刷新图像获取") // 方法1:触发VirtualDisplay刷新 virtualDisplay?.surface?.let { surface -> try { // 强制触发surface渲染 - Log.v(TAG, "🔧 Android 15:触发Surface刷新") + Log.v(TAG, "Android 15:触发Surface刷新") } catch (e: Exception) { Log.w(TAG, "Surface刷新失败:${e.message}") } @@ -2477,7 +2531,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { if (image != null) { latestImage?.close() // 关闭之前的图像 latestImage = image - Log.v(TAG, "🔧 Android 15强制刷新:获得图像(尝试${attempts + 1})") + Log.v(TAG, "Android 15强制刷新:获得图像(尝试${attempts + 1})") break } Thread.sleep(200) @@ -2489,7 +2543,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { try { val bitmap = convertImageToBitmap(latestImage!!) if (bitmap != null) { - Log.i(TAG, "✅ Android 15强制刷新成功:${bitmap.width}x${bitmap.height}") + Log.i(TAG, "Android 15强制刷新成功:${bitmap.width}x${bitmap.height}") // 更新缓存 safeRecycleLastValidBitmap() @@ -2505,13 +2559,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } - Log.w(TAG, "⚠️ Android 15强制刷新未获得有效图像") + Log.w(TAG, "Android 15强制刷新未获得有效图像") return null } else { return null } } catch (e: Exception) { - Log.e(TAG, "❌ Android 15强制刷新失败", e) + Log.e(TAG, "Android 15强制刷新失败", e) return null } } @@ -2521,7 +2575,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ fun verifyAndroid15ImageTransmission() { if (Build.VERSION.SDK_INT >= 35 && !android15ImageTransmissionVerified) { - Log.i(TAG, "🔍 Android 15设备注册成功,开始图像传输验证...") + Log.i(TAG, "Android 15设备注册成功,开始图像传输验证...") serviceScope.launch { delay(android15ImageVerificationDelay) @@ -2531,15 +2585,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val hasValidImage = checkImageTransmissionStatus() if (!hasValidImage) { - Log.w(TAG, "⚠️ Android 15图像传输验证失败,尝试修复...") + Log.w(TAG, "Android 15图像传输验证失败,尝试修复...") repairAndroid15ImageTransmission() } else { - Log.i(TAG, "✅ Android 15图像传输验证成功") + Log.i(TAG, "Android 15图像传输验证成功") android15ImageTransmissionVerified = true } } catch (e: Exception) { - Log.e(TAG, "❌ Android 15图像传输验证异常", e) + Log.e(TAG, "Android 15图像传输验证异常", e) } } } @@ -2552,21 +2606,21 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { try { // 检查VirtualDisplay是否存在 if (!isVirtualDisplayCreated()) { - Log.w(TAG, "❌ VirtualDisplay未创建") + Log.w(TAG, "VirtualDisplay未创建") return false } // 尝试获取最新图像 val latestImage = getLatestImage() if (latestImage == null) { - Log.w(TAG, "❌ 无法获取最新图像") + Log.w(TAG, "无法获取最新图像") return false } // 检查图像是否为有效图像(非测试图像) val bitmap = convertImageToBitmap(latestImage) if (bitmap == null) { - Log.w(TAG, "❌ 图像转换为Bitmap失败") + Log.w(TAG, "图像转换为Bitmap失败") return false } @@ -2576,15 +2630,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val uniqueColors = pixels.distinct().size if (uniqueColors < 10) { - Log.w(TAG, "❌ 图像内容可能无效,颜色种类过少: $uniqueColors") + Log.w(TAG, "图像内容可能无效,颜色种类过少: $uniqueColors") return false } - Log.i(TAG, "✅ 图像传输状态检查通过") + Log.i(TAG, "图像传输状态检查通过") return true } catch (e: Exception) { - Log.e(TAG, "❌ 检查图像传输状态失败", e) + Log.e(TAG, "检查图像传输状态失败", e) return false } } @@ -2594,45 +2648,45 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private suspend fun repairAndroid15ImageTransmission() { try { - Log.i(TAG, "🔧 开始修复Android 15图像传输...") + Log.i(TAG, "开始修复Android 15图像传输...") // 步骤1:强制刷新Surface - Log.i(TAG, "📱 步骤1:强制刷新Surface") + Log.i(TAG, "步骤1:强制刷新Surface") refreshSurfaceForAndroid15() delay(2000) if (checkImageTransmissionStatus()) { - Log.i(TAG, "✅ Surface刷新修复成功") + Log.i(TAG, "Surface刷新修复成功") android15ImageTransmissionVerified = true return } // 步骤2:重新初始化ImageReader - Log.i(TAG, "📱 步骤2:重新初始化ImageReader") + Log.i(TAG, "步骤2:重新初始化ImageReader") reinitializeImageReaderForAndroid15() delay(3000) if (checkImageTransmissionStatus()) { - Log.i(TAG, "✅ ImageReader重新初始化修复成功") + Log.i(TAG, "ImageReader重新初始化修复成功") android15ImageTransmissionVerified = true return } // 步骤3:完整重建VirtualDisplay - Log.i(TAG, "📱 步骤3:完整重建VirtualDisplay") + Log.i(TAG, "步骤3:完整重建VirtualDisplay") recreateVirtualDisplayForAndroid15() delay(3000) if (checkImageTransmissionStatus()) { - Log.i(TAG, "✅ VirtualDisplay重建修复成功") + Log.i(TAG, "VirtualDisplay重建修复成功") android15ImageTransmissionVerified = true return } - Log.w(TAG, "⚠️ Android 15图像传输修复失败,将依赖后续自动恢复机制") + Log.w(TAG, "Android 15图像传输修复失败,将依赖后续自动恢复机制") } catch (e: Exception) { - Log.e(TAG, "❌ Android 15图像传输修复异常", e) + Log.e(TAG, "Android 15图像传输修复异常", e) } } @@ -2644,7 +2698,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { imageReader?.let { reader -> val surface = reader.surface if (surface.isValid) { - Log.d(TAG, "🔄 Android 15强制刷新Surface") + Log.d(TAG, "Android 15强制刷新Surface") // 通过重设Surface的方式强制刷新 surface.release() delay(500) @@ -2656,7 +2710,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } } } catch (e: Exception) { - Log.e(TAG, "❌ Android 15 Surface刷新失败", e) + Log.e(TAG, "Android 15 Surface刷新失败", e) } } @@ -2665,7 +2719,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private suspend fun reinitializeImageReaderForAndroid15() { try { - Log.d(TAG, "🔄 Android 15重新初始化ImageReader") + Log.d(TAG, "Android 15重新初始化ImageReader") // 释放现有ImageReader releaseImageReader() @@ -2679,12 +2733,12 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { virtualDisplay?.let { display -> imageReader?.let { reader -> display.surface = reader.surface - Log.d(TAG, "✅ Android 15已重新关联ImageReader和VirtualDisplay") + Log.d(TAG, "Android 15已重新关联ImageReader和VirtualDisplay") } } } catch (e: Exception) { - Log.e(TAG, "❌ Android 15 ImageReader重新初始化失败", e) + Log.e(TAG, "Android 15 ImageReader重新初始化失败", e) } } @@ -2693,7 +2747,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { */ private suspend fun recreateVirtualDisplayForAndroid15() { try { - Log.d(TAG, "🔄 Android 15完整重建VirtualDisplay") + Log.d(TAG, "Android 15完整重建VirtualDisplay") // 停止当前屏幕捕获 stopScreenCapture() @@ -2701,13 +2755,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { // 重新启动屏幕捕获 if (startScreenCapture()) { - Log.i(TAG, "✅ Android 15 VirtualDisplay重建成功") + Log.i(TAG, "Android 15 VirtualDisplay重建成功") } else { - Log.w(TAG, "⚠️ Android 15 VirtualDisplay重建失败") + Log.w(TAG, "Android 15 VirtualDisplay重建失败") } } catch (e: Exception) { - Log.e(TAG, "❌ Android 15 VirtualDisplay重建异常", e) + Log.e(TAG, "Android 15 VirtualDisplay重建异常", e) } } @@ -2744,10 +2798,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { if (Build.VERSION.SDK_INT >= 35) 4 else 2 // Android 15增加缓冲区 ) - Log.d(TAG, "✅ 创建ImageReader: ${width}x${height}") + Log.d(TAG, "创建ImageReader: ${width}x${height}") } catch (e: Exception) { - Log.e(TAG, "❌ 创建ImageReader失败", e) + Log.e(TAG, "创建ImageReader失败", e) } } @@ -2758,9 +2812,9 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { try { imageReader?.close() imageReader = null - Log.d(TAG, "✅ 释放ImageReader") + Log.d(TAG, "释放ImageReader") } catch (e: Exception) { - Log.e(TAG, "❌ 释放ImageReader失败", e) + Log.e(TAG, "释放ImageReader失败", e) } } @@ -2774,7 +2828,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { startCapture() true } catch (e: Exception) { - Log.e(TAG, "❌ 启动屏幕捕获失败", e) + Log.e(TAG, "启动屏幕捕获失败", e) false } } @@ -2786,7 +2840,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { try { stopCapture() } catch (e: Exception) { - Log.e(TAG, "❌ 停止屏幕捕获失败", e) + Log.e(TAG, "停止屏幕捕获失败", e) } } @@ -2808,72 +2862,72 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * ✅ Android 15:检查是否正在重新创建VirtualDisplay + * Android 15:检查是否正在重新创建VirtualDisplay */ fun isVirtualDisplayRecreating(): Boolean { return isVirtualDisplayRecreating } /** - * ✅ 智能判断是否应该重新创建VirtualDisplay + * 智能判断是否应该重新创建VirtualDisplay */ private fun shouldRecreateVirtualDisplay(): Boolean { val currentTime = System.currentTimeMillis() // 检查冷却期 if (virtualDisplayRecreationCooldown) { - Log.i(TAG, "🛡️ VirtualDisplay重新创建处于冷却期,跳过") + Log.i(TAG, "VirtualDisplay重新创建处于冷却期,跳过") return false } // 检查最小间隔 if (currentTime - lastVirtualDisplayRecreationTime < minRecreationInterval) { val remainingTime = minRecreationInterval - (currentTime - lastVirtualDisplayRecreationTime) - Log.i(TAG, "🛡️ VirtualDisplay重新创建间隔不足,还需等待${remainingTime}ms") + Log.i(TAG, "VirtualDisplay重新创建间隔不足,还需等待${remainingTime}ms") return false } // 检查连续重新创建次数 if (consecutiveRecreationCount >= maxConsecutiveRecreations) { - Log.w(TAG, "🛡️ VirtualDisplay连续重新创建次数过多(${consecutiveRecreationCount}/${maxConsecutiveRecreations}),进入冷却期") + Log.w(TAG, "VirtualDisplay连续重新创建次数过多(${consecutiveRecreationCount}/${maxConsecutiveRecreations}),进入冷却期") virtualDisplayRecreationCooldown = true // 5分钟后重置冷却期 serviceScope.launch { delay(300000) // 5分钟 virtualDisplayRecreationCooldown = false consecutiveRecreationCount = 0 - Log.i(TAG, "🔄 VirtualDisplay重新创建冷却期结束,重置计数") + Log.i(TAG, "VirtualDisplay重新创建冷却期结束,重置计数") } return false } // 检查失败次数是否达到阈值 if (consecutiveImageFailures < maxImageFailuresBeforeRecreation) { - Log.i(TAG, "🛡️ 图像失败次数未达到阈值(${consecutiveImageFailures}/${maxImageFailuresBeforeRecreation}),暂不重新创建") + Log.i(TAG, "图像失败次数未达到阈值(${consecutiveImageFailures}/${maxImageFailuresBeforeRecreation}),暂不重新创建") return false } // 检查VirtualDisplay和ImageReader是否存在 if (virtualDisplay == null || imageReader == null) { - Log.w(TAG, "⚠️ VirtualDisplay或ImageReader为null,需要重新创建") + Log.w(TAG, "VirtualDisplay或ImageReader为null,需要重新创建") return true } // 所有条件都满足,可以重新创建 - Log.i(TAG, "✅ 满足VirtualDisplay重新创建条件:失败${consecutiveImageFailures}次,上次重建${currentTime - lastVirtualDisplayRecreationTime}ms前") + Log.i(TAG, "满足VirtualDisplay重新创建条件:失败${consecutiveImageFailures}次,上次重建${currentTime - lastVirtualDisplayRecreationTime}ms前") return true } /** - * ✅ Android 15:触发权限申请流程 + * Android 15:触发权限申请流程 */ private fun triggerAndroid15PermissionRequest(reason: String) { try { - Log.i(TAG, "🎯 [权限申请] Android 15触发权限申请:$reason") + Log.i(TAG, "[权限申请] Android 15触发权限申请:$reason") - // 🚨 Android 11+特殊处理:统一使用无障碍截图API,跳过权限申请触发 + // Android 11+特殊处理:统一使用无障碍截图API,跳过权限申请触发 if (Build.VERSION.SDK_INT >= 30) { - Log.i(TAG, "📱 Android 11+设备:使用无障碍截图API,跳过权限申请触发") + Log.i(TAG, "Android 11+设备:使用无障碍截图API,跳过权限申请触发") return } @@ -2881,13 +2935,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val permissionGranter = try { (service as? AccessibilityRemoteService)?.getPermissionGranter() } catch (e: Exception) { - Log.e(TAG, "❌ 无法获取PermissionGranter实例", e) + Log.e(TAG, "无法获取PermissionGranter实例", e) null } if (permissionGranter != null) { - // ✅ 强制设置权限申请状态,绕过现有权限检查 - Log.i(TAG, "🎯 [权限申请] 强制设置MediaProjection申请状态为true,准备处理权限弹窗") + // 强制设置权限申请状态,绕过现有权限检查 + Log.i(TAG, "[权限申请] 强制设置MediaProjection申请状态为true,准备处理权限弹窗") // 使用反射或直接设置,绕过setMediaProjectionRequesting中的权限检查 try { @@ -2895,36 +2949,36 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { val field = permissionGranter.javaClass.getDeclaredField("isRequestingMediaProjection") field.isAccessible = true field.setBoolean(permissionGranter, true) - Log.i(TAG, "🎯 [权限申请] 已直接设置权限申请状态,绕过权限检查") + Log.i(TAG, "[权限申请] 已直接设置权限申请状态,绕过权限检查") } catch (e: Exception) { - Log.w(TAG, "⚠️ 无法直接设置权限状态,使用常规方法", e) + Log.w(TAG, "无法直接设置权限状态,使用常规方法", e) permissionGranter.setMediaProjectionRequesting(true) } - Log.i(TAG, "🎯 [权限申请] Android 15权限申请流程已触发,等待自动处理权限弹窗") + Log.i(TAG, "[权限申请] Android 15权限申请流程已触发,等待自动处理权限弹窗") // 启动异步监控,在权限处理完成后重置状态 serviceScope.launch { delay(15000) // 15秒后自动重置状态,给权限处理更多时间 try { permissionGranter.setMediaProjectionRequesting(false) - Log.i(TAG, "🎯 [权限申请] 15秒后自动重置权限申请状态") + Log.i(TAG, "[权限申请] 15秒后自动重置权限申请状态") } catch (e: Exception) { Log.w(TAG, "重置权限申请状态失败", e) } } } else { - Log.w(TAG, "⚠️ [权限申请] 无法获取PermissionGranter,跳过权限申请触发") + Log.w(TAG, "[权限申请] 无法获取PermissionGranter,跳过权限申请触发") } } catch (e: Exception) { - Log.e(TAG, "❌ [权限申请] 触发Android 15权限申请失败", e) + Log.e(TAG, "[权限申请] 触发Android 15权限申请失败", e) } } /** - * 🚨 Android 11+专用:生成测试图像 + * Android 11+专用:生成测试图像 */ private fun generateAndroid11TestImage(): Bitmap? { return try { @@ -2946,7 +3000,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { typeface = android.graphics.Typeface.DEFAULT_BOLD textAlign = android.graphics.Paint.Align.CENTER } - canvas.drawText("📱 Android 11+ 设备", screenWidth / 2f, 120f, paint) + canvas.drawText("Android 11+ 设备", screenWidth / 2f, 120f, paint) // 副标题 paint.apply {