package com.hikoncont.manager import android.content.Intent import android.graphics.Bitmap import android.os.Build import android.os.Handler import android.os.HandlerThread import android.util.Log import com.hikoncont.MediaProjectionHolder import com.hikoncont.service.AccessibilityRemoteService import kotlinx.coroutines.* import java.io.ByteArrayOutputStream import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock import java.lang.ref.WeakReference import java.util.concurrent.atomic.AtomicBoolean /** * 屏幕捕获管理器 - 使用无障碍服务截图API * * 负责屏幕录制和截图功能 */ 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 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 PAUSE_STATE_PREF = "screen_capture_pause_state" private const val KEY_IS_PAUSED = "is_paused" } private var isCapturing = false private var screenWidth = 0 private var screenHeight = 0 // 后台线程处理 private val handlerThread = HandlerThread("ScreenCapture") private val backgroundHandler: Handler private val captureScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) // MediaProjection相关资源复用 private var virtualDisplay: android.hardware.display.VirtualDisplay? = null private var imageReader: android.media.ImageReader? = null private var mediaProjection: android.media.projection.MediaProjection? = null // 图像缓存机制 private var lastValidBitmap: Bitmap? = null private var lastCaptureTime = 0L private var lastScreenshotTime = 0L // ✅ 新增:记录上次截图时间,防止截图间隔太短 // 状态跟踪 private var isPaused = false private var lastSuccessfulSendTime: Long? = null // 添加Android 15图像传输验证相关变量 @Volatile private var android15ImageTransmissionVerified = false private val android15ImageVerificationDelay = 3000L // 3秒后验证 // ✅ Android 15 VirtualDisplay重新创建状态跟踪 @Volatile private var isVirtualDisplayRecreating = false private val virtualDisplayRecreationLock = ReentrantLock() // ✅ Android 15 Session使用状态跟踪 @Volatile private var android15SessionUsed = false // ✅ 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重建次数限制,防止无限重建循环 private var virtualDisplayRebuildCount = 0 private val MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS = 3 // 🔑 新增:AccessibilityService截图模式开关 private var useAccessibilityScreenshot = false // 📊 自适应画质:运行时可调参数(覆盖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+专用:防止闪烁的状态管理 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 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 = java.util.concurrent.Executors.newSingleThreadExecutor() init { handlerThread.start() backgroundHandler = Handler(handlerThread.looper) // 获取屏幕尺寸 - 使用完整屏幕尺寸(包含状态栏和导航栏) // 这样可以确保与MediaProjection实际捕获的内容尺寸一致 val metrics = service.resources.displayMetrics screenWidth = metrics.widthPixels screenHeight = metrics.heightPixels Log.i(TAG, "屏幕尺寸: ${screenWidth}x${screenHeight}") } /** * ✅ 安全回收lastValidBitmap,防止多线程并发导致的native crash (SIGSEGV) * * 问题根因:多个协程(startMediaProjectionCapture、startAccessibilityScreenCapture、 * captureWithMediaProjection等)并发访问lastValidBitmap,可能导致: * 1. double-recycle:Bitmap已被其他线程recycle但引用未置null * 2. Hardware Bitmap的底层HardwareBuffer已被释放 * 两种情况都会在native层BitmapWrapper::freePixels()触发SIGSEGV */ private fun safeRecycleLastValidBitmap() { val bitmapToRecycle = lastValidBitmap lastValidBitmap = null // 先置null,防止其他线程访问 try { if (bitmapToRecycle != null && !bitmapToRecycle.isRecycled) { bitmapToRecycle.recycle() } } catch (e: Exception) { // 捕获所有异常,包括可能的native异常包装 Log.w(TAG, "回收缓存Bitmap异常(可能已被其他线程回收): ${e.message}") } } /** * ✅ 安全回收任意Bitmap,防止native crash */ private fun safeRecycleBitmap(bitmap: Bitmap?) { try { if (bitmap != null && !bitmap.isRecycled) { bitmap.recycle() } } catch (e: Exception) { Log.w(TAG, "回收Bitmap异常: ${e.message}") } } /** * 🔑 启用AccessibilityService截图模式 * 用于绕过黑屏遮罩,让Web端能够正常显示画面 */ fun enableAccessibilityScreenshotMode() { Log.i(TAG, "🔑 启用AccessibilityService截图模式 - 绕过黑屏遮罩") useAccessibilityScreenshot = true } /** * 🔑 禁用AccessibilityService截图模式 * 恢复使用MediaProjection截图 */ fun disableAccessibilityScreenshotMode() { 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}") if (fps in 1..30) { dynamicFps = fps Log.i(TAG, "📊 帧率调整为: ${fps}fps (间隔${1000 / fps}ms)") } if (quality in 20..90) { dynamicQuality = quality Log.i(TAG, "📊 JPEG质量调整为: $quality") } if (maxWidth in 240..1920) { dynamicMaxWidth = maxWidth Log.i(TAG, "📊 最大宽度调整为: $maxWidth") } if (maxHeight in 320..2560) { dynamicMaxHeight = maxHeight Log.i(TAG, "📊 最大高度调整为: $maxHeight") } } /** * 🔄 切换到无障碍截图模式(由服务端指令触发) * 停止当前采集,切换到 AccessibilityService.takeScreenshot */ fun switchToAccessibilityMode() { if (useAccessibilityScreenshot) { Log.d(TAG, "🔄 已经在无障碍截图模式,跳过切换") return } Log.i(TAG, "🔄 切换到无障碍截图模式") // 停止当前采集 stopCapture() // 清理 MediaProjection 资源 cleanupVirtualDisplayOnly() // 启用无障碍截图并重新开始 enableAccessibilityScreenshotMode() startCapture() } /** * 🔄 切换到 MediaProjection 模式(由服务端指令触发) */ fun switchToMediaProjectionMode() { if (!useAccessibilityScreenshot) { Log.d(TAG, "🔄 已经在MediaProjection模式,跳过切换") return } Log.i(TAG, "🔄 切换到MediaProjection模式") stopCapture() disableAccessibilityScreenshotMode() startCapture() } /** * 初始化屏幕捕获 */ fun initialize() { try { Log.i(TAG, "初始化屏幕捕获管理器 - 使用无障碍服务截图API") // ✅ 恢复持久化的暂停状态 restorePauseState() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { Log.i(TAG, "Android 9+,支持无障碍服务截图API") } else { Log.w(TAG, "Android版本过低,将使用测试图像") } } catch (e: Exception) { Log.e(TAG, "初始化屏幕捕获失败", e) } } /** * ✅ 保存暂停状态到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") } catch (e: Exception) { Log.e(TAG, "❌ 保存暂停状态失败", e) } } /** * ✅ 从SharedPreferences恢复暂停状态 */ private fun restorePauseState() { try { val sp = service.getSharedPreferences(PAUSE_STATE_PREF, android.content.Context.MODE_PRIVATE) val savedIsPaused = sp.getBoolean(KEY_IS_PAUSED, false) if (savedIsPaused) { Log.i(TAG, "📋 检测到持久化的暂停状态,恢复暂停状态") isPaused = true isCapturing = false } else { Log.d(TAG, "📋 未检测到暂停状态,使用默认状态") } } catch (e: Exception) { Log.e(TAG, "❌ 恢复暂停状态失败", e) } } /** * 开始屏幕捕获 */ fun startCapture() { if (isCapturing) { Log.w(TAG, "屏幕捕获已在运行") return } // ✅ 检查持久化的暂停状态,如果暂停则不启动 if (isPaused) { Log.i(TAG, "⏸️ 屏幕捕获处于暂停状态,跳过启动") return } try { Log.i(TAG, "开始屏幕捕获") // ✅ Android 11+ (API 30+):优先尝试 MediaProjection(帧率更高), // 如果 MediaProjection 不可用则回退到无障碍截图 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // 检查是否已被显式设置为无障碍截图模式 if (useAccessibilityScreenshot) { Log.i(TAG, "📱 Android 11+:已设置为无障碍截图模式") startAccessibilityScreenCapture() } else if (ensureMediaProjection()) { // MediaProjection 可用,使用 VirtualDisplay 连续流式捕获 Log.i(TAG, "📱 Android 11+:MediaProjection 可用,使用 VirtualDisplay 连续流式捕获") startMediaProjectionCapture() } else { // 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 连续流式捕获") startMediaProjectionCapture() } else { Log.w(TAG, "Android版本过低,使用测试图像") startFallbackCapture() } } catch (e: Exception) { Log.e(TAG, "启动屏幕捕获失败", e) startFallbackCapture() } } /** * 使用MediaProjection VirtualDisplay连续流式捕获(核心采集方法) * 通过 ImageReader.OnImageAvailableListener 回调驱动,无系统级间隔限制 */ private fun startMediaProjectionCapture() { isCapturing = true Log.i(TAG, "🚀 启动 MediaProjection VirtualDisplay 连续流式捕获") captureScope.launch { // 先确保 MediaProjection 可用 if (!ensureMediaProjection()) { Log.e(TAG, "❌ MediaProjection 不可用,回退到无障碍截图") // 回退到无障碍截图作为兜底 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() } else { startFallbackCapture() } return@launch } // 初始化 ImageReader + VirtualDisplay if (imageReader == null || virtualDisplay == null) { setupMediaProjectionResources() } if (virtualDisplay == null) { Log.e(TAG, "❌ VirtualDisplay 创建失败,回退到无障碍截图") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() } else { startFallbackCapture() } return@launch } Log.i(TAG, "✅ MediaProjection VirtualDisplay 就绪,开始连续帧采集循环") var consecutiveFailures = 0 virtualDisplayRebuildCount = 0 // ✅ 最小有效帧大小阈值:正常480×854 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 { val image = imageReader?.acquireLatestImage() if (image != null) { try { val bitmap = convertImageToBitmap(image) if (bitmap != null) { trackBitmap(bitmap) val jpegData = compressBitmap(bitmap) if (jpegData.size >= MIN_VALID_FRAME_SIZE) { // ✅ 有效帧:发送并更新缓存 sendFrameToServer(jpegData) safeRecycleLastValidBitmap() lastValidBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false) lastValidBitmap?.let { trackBitmap(it) } lastCaptureTime = System.currentTimeMillis() consecutiveFailures = 0 consecutiveBlackFrames = 0 Log.d(TAG, "📸 MediaProjection 有效帧: ${jpegData.size} bytes") } else { // ⚠️ 疑似黑屏帧 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}个黑屏帧,切换到无障碍截图模式") bitmap.recycle() cleanupVirtualDisplayOnly() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() } else { startFallbackCapture() } return@launch } // 发送缓存的有效帧保持画面连续(如果有的话) if (lastValidBitmap != null && !lastValidBitmap!!.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前)") } } } } bitmap.recycle() } } finally { image.close() } } else { consecutiveFailures++ // ✅ 无新帧时发送缓存帧,保持画面连续 if (lastValidBitmap != null && !lastValidBitmap!!.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前)") } } } // 连续失败过多,尝试重建 VirtualDisplay if (consecutiveFailures >= maxImageFailuresBeforeRecreation) { virtualDisplayRebuildCount++ Log.w(TAG, "⚠️ 连续 ${consecutiveFailures} 次无法获取有效帧,重建 VirtualDisplay (${virtualDisplayRebuildCount}/${MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS})") // 重建次数超限,回退到无障碍截图 if (virtualDisplayRebuildCount > MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS) { Log.w(TAG, "🚨 重建次数已达上限,回退到无障碍截图") cleanupVirtualDisplayOnly() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() } else { startFallbackCapture() } return@launch } cleanupVirtualDisplayOnly() delay(500) setupMediaProjectionResources() consecutiveFailures = 0 if (virtualDisplay == null) { Log.w(TAG, "⚠️ VirtualDisplay 重建失败,回退到无障碍截图") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { enableAccessibilityScreenshotMode() startAccessibilityScreenCapture() } return@launch } } } // 按动态帧率控制采集间隔 delay(1000L / dynamicFps) } catch (e: CancellationException) { throw e } catch (e: Exception) { Log.w(TAG, "⚠️ MediaProjection 帧采集异常: ${e.message}") consecutiveFailures++ delay(100) } } } } /** * 确保 MediaProjection 可用,尝试获取或恢复 */ private fun ensureMediaProjection(): Boolean { if (mediaProjection != null) return true mediaProjection = MediaProjectionHolder.getMediaProjection() if (mediaProjection != null) return true // 尝试从权限数据重新创建 val permissionData = MediaProjectionHolder.getPermissionData() if (permissionData != null) { val (resultCode, resultData) = permissionData if (resultData != null) { try { val mediaProjectionManager = service.getSystemService( android.content.Context.MEDIA_PROJECTION_SERVICE ) as android.media.projection.MediaProjectionManager mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData) if (mediaProjection != null) { Log.i(TAG, "✅ 重新创建 MediaProjection 成功") MediaProjectionHolder.setMediaProjection(mediaProjection) return true } } catch (e: Exception) { Log.e(TAG, "❌ 重新创建 MediaProjection 失败", e) } } } Log.e(TAG, "❌ 无法获取 MediaProjection,权限可能未授予") return false } /** * 使用MediaProjection进行屏幕捕获(旧方法,保留作为无障碍截图兜底) */ private fun startAccessibilityScreenCapture() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { Log.w(TAG, "屏幕捕获需要Android 9+") startFallbackCapture() return } isCapturing = true Log.i(TAG, "启动屏幕捕获,尝试获取真实屏幕") captureScope.launch { var consecutiveFailures = 0 val maxConsecutiveFailures = 10 // 最多连续10次失败后显示权限恢复提示 while (isCapturing) { try { // 尝试获取真实屏幕截图 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}") } catch (e: Exception) { Log.w(TAG, "缓存有效截图失败", e) } screenshot.recycle() consecutiveFailures = 0 // 重置失败计数 // 🚨 Android 11+特殊处理:成功获取截图时的状态管理 if (Build.VERSION.SDK_INT >= 30) { android11ConsecutiveFailures = 0 android11LastSuccessTime = System.currentTimeMillis() android11InTestMode = false Log.d(TAG, "📱 Android 11+设备:真实截图成功,退出测试模式") } } else { // ❌ 真实截图失败,不发送任何数据,等待下一次截图 // 之前发送测试图像会被服务端过滤且占用发送队列,得不偿失 consecutiveFailures++ if (consecutiveFailures % 50 == 1) { Log.d(TAG, "📱 无障碍截图失败(${consecutiveFailures}次),跳过本帧等待下次") } } // ✅ 截图成功时按帧率延迟,失败时短延迟后立即重试 if (consecutiveFailures == 0) { delay(1000 / dynamicFps.toLong()) } else { delay(50) // 失败时50ms后重试,让系统截图间隔限制自己控制节奏 } } catch (e: CancellationException) { throw e } catch (e: Exception) { Log.w(TAG, "⚠️ 屏幕捕获失败: ${e.message}") consecutiveFailures++ delay(100) // ✅ 出错时进一步缩短间隔,保持流畅度(从200ms改为100ms) } } } } /** * 🚨 Android 11+专用:处理截图失败的智能逻辑 */ private fun handleAndroid11ScreenshotFailure(consecutiveFailures: Int, maxConsecutiveFailures: Int): Bitmap? { android11ConsecutiveFailures++ val currentTime = System.currentTimeMillis() Log.d(TAG, "📱 Android 11+设备:截图失败 (连续${android11ConsecutiveFailures}次)") // 🔑 优先策略:返回缓存的上一帧,避免闪烁 if (lastValidBitmap != null && !lastValidBitmap!!.isRecycled) { val cacheAge = currentTime - lastCaptureTime // 如果缓存还比较新(10秒内),直接使用缓存 if (cacheAge < 10000) { Log.d(TAG, "📱 Android 11+设备:返回缓存截图 (${cacheAge}ms前),避免闪烁") return try { lastValidBitmap!!.copy(lastValidBitmap!!.config ?: Bitmap.Config.ARGB_8888, false) } catch (e: Exception) { Log.w(TAG, "复制缓存截图失败", e) null } } // 缓存较旧但仍可用(60秒内),短期失败时继续使用 if (cacheAge < 60000 && android11ConsecutiveFailures < 20) { Log.d(TAG, "📱 Android 11+设备:返回稍旧的缓存截图 (${cacheAge}ms前),避免闪烁") return try { lastValidBitmap!!.copy(lastValidBitmap!!.config ?: Bitmap.Config.ARGB_8888, false) } catch (e: Exception) { Log.w(TAG, "复制缓存截图失败", e) null } } } // 🚨 无可用缓存或缓存过旧,智能判断是否应该进入测试模式 val shouldEnterTestMode = when { android11ConsecutiveFailures >= android11FailureThreshold -> true android11InTestMode && (currentTime - android11LastSuccessTime) < android11TestModeStabilityTime -> true else -> false } return if (shouldEnterTestMode) { if (!android11InTestMode) { Log.i(TAG, "📱 Android 11+设备:连续失败${android11ConsecutiveFailures}次,进入稳定测试模式") android11InTestMode = true } // 在测试模式下,根据总失败次数决定显示什么 if (consecutiveFailures > maxConsecutiveFailures) { Log.w(TAG, "📱 Android 11+设备:长期失败,显示权限恢复提示") generatePermissionRecoveryTestImage() } else { Log.d(TAG, "📱 Android 11+设备:显示专用测试画面") generateAndroid11TestImage() } } else { // 还未达到测试模式阈值,使用简单测试图像 Log.d(TAG, "📱 Android 11+设备:短暂失败,显示简单测试图像") generateTestImage() } } /** * 捕获真实屏幕(用于单次截图场景) * 优先使用 MediaProjection,不可用时回退到无障碍截图 * 当 useAccessibilityScreenshot=true 时,直接使用无障碍截图(跳过 MediaProjection) */ private fun captureRealScreen(): Bitmap? { return try { // 🔑 如果已切换到无障碍截图模式,直接使用无障碍截图,不再尝试 MediaProjection if (useAccessibilityScreenshot) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return captureWithAccessibilityService() } return null } // 优先尝试 MediaProjection val mpResult = captureWithMediaProjection() if (mpResult != null) return mpResult // MediaProjection 不可用,回退到无障碍截图(Android 11+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { Log.d(TAG, "MediaProjection 无帧,回退到无障碍截图") captureWithAccessibilityService() } else { null } } catch (e: Exception) { Log.e(TAG, "捕获真实屏幕失败", e) null } } /** * 使用无障碍服务截图 (Android 11+) - 优化版,防止截图间隔太短 */ private fun captureWithAccessibilityService(): Bitmap? { return try { // ✅ 修复:检查截图间隔,防止截图间隔太短 val currentTime = System.currentTimeMillis() val timeSinceLastScreenshot = currentTime - lastScreenshotTime if (timeSinceLastScreenshot < MIN_CAPTURE_INTERVAL) { return null } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { Log.d(TAG, "使用无障碍服务截图API") // 🚨 Android 11+特殊处理:增强错误检查和重试机制 if (Build.VERSION.SDK_INT >= 30) { Log.d(TAG, "📱 Android 11+设备:使用增强的无障碍截图逻辑") } // 使用同步方式获取截图 var resultBitmap: Bitmap? = null var errorCode: Int? = null val latch = java.util.concurrent.CountDownLatch(1) // 后台线程直接调用截图API,回调在专用执行器上执行,避免占用主线程 try { service.takeScreenshot( android.view.Display.DEFAULT_DISPLAY, screenshotExecutor, object : android.accessibilityservice.AccessibilityService.TakeScreenshotCallback { override fun onSuccess(screenshotResult: android.accessibilityservice.AccessibilityService.ScreenshotResult) { try { Log.d(TAG, "无障碍服务截图成功") // ✅ 修复:更新截图时间戳,防止截图间隔太短 lastScreenshotTime = System.currentTimeMillis() // 🔑 关键修复:正确提取ScreenshotResult中的Bitmap if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val hardwareBuffer = screenshotResult.hardwareBuffer val colorSpace = screenshotResult.colorSpace if (hardwareBuffer != null) { // 从HardwareBuffer创建Bitmap resultBitmap = android.graphics.Bitmap.wrapHardwareBuffer( hardwareBuffer, colorSpace ) // 🔧 跟踪新创建的Bitmap resultBitmap?.let { trackBitmap(it) } Log.i(TAG, "✅ 成功从ScreenshotResult提取Bitmap: ${resultBitmap?.width}x${resultBitmap?.height}") // 🚨 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}") } else { Log.w(TAG, "📱 Android 11+设备:Bitmap验证失败") resultBitmap = null } } } else { Log.w(TAG, "⚠️ ScreenshotResult中的HardwareBuffer为null") // 🚨 Android 11+特殊处理:记录详细错误信息 if (Build.VERSION.SDK_INT >= 30) { Log.e(TAG, "📱 Android 11+设备:HardwareBuffer为null,可能是权限问题") } } } else { Log.w(TAG, "⚠️ Android版本不支持ScreenshotResult API") } } catch (e: Exception) { Log.e(TAG, "处理截图结果失败", e) // 🚨 Android 11+特殊处理:记录详细异常信息 if (Build.VERSION.SDK_INT >= 30) { Log.e(TAG, "📱 Android 11+设备:处理截图结果时发生异常", e) } } finally { latch.countDown() } } override fun onFailure(failureErrorCode: Int) { Log.e(TAG, "无障碍服务截图失败,错误码: $failureErrorCode") errorCode = failureErrorCode // 🚨 Android 11+特殊处理:详细分析错误码 if (Build.VERSION.SDK_INT >= 30) { val errorMessage = when (failureErrorCode) { android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR -> "内部错误" android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT -> "截图间隔太短" android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY -> "无效显示" android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS -> "无障碍权限不足" else -> "未知错误($failureErrorCode)" } Log.e(TAG, "📱 Android 11+设备:截图失败详情 - $errorMessage") } latch.countDown() } } ) } catch (e: Exception) { Log.e(TAG, "调用takeScreenshot失败", e) // 🚨 Android 11+特殊处理:记录调用失败详情 if (Build.VERSION.SDK_INT >= 30) { Log.e(TAG, "📱 Android 11+设备:调用takeScreenshot时发生异常", e) } latch.countDown() } // 🚨 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让主循环处理失败逻辑 if (Build.VERSION.SDK_INT >= 30) { Log.w(TAG, "📱 Android 11+设备:无障碍截图超时,返回null让主循环处理") return null } return captureWithMediaProjection() // 其他版本超时时回退到MediaProjection } if (resultBitmap != null) { Log.i(TAG, "✅ 无障碍服务截图成功获取,绕过黑屏遮罩") resultBitmap } else { // 🚨 Android 11+特殊处理:返回null让主循环处理失败逻辑 if (Build.VERSION.SDK_INT >= 30) { Log.w(TAG, "📱 Android 11+设备:无障碍截图失败,返回null让主循环处理") return null } // 如果AccessibilityService截图失败,回退到MediaProjection Log.w(TAG, "⚠️ 无障碍服务截图失败,回退到MediaProjection") captureWithMediaProjection() } } else { Log.w(TAG, "Android版本不支持无障碍服务截图API,使用MediaProjection") // 🚨 Android 11+特殊处理:返回null让主循环处理 if (Build.VERSION.SDK_INT >= 30) { Log.w(TAG, "📱 Android 11+设备:不支持无障碍截图API,返回null让主循环处理") return null } captureWithMediaProjection() } } catch (e: Exception) { Log.e(TAG, "无障碍服务截图异常", e) // 🚨 Android 11+特殊处理:异常时返回null让主循环处理 if (Build.VERSION.SDK_INT >= 30) { Log.e(TAG, "📱 Android 11+设备:无障碍截图异常,返回null让主循环处理", e) return null } // 出现异常时回退到MediaProjection captureWithMediaProjection() } } /** * 使用MediaProjection截图 - 优化版本,复用资源 */ private fun captureWithMediaProjection(): Bitmap? { return try { // 获取或复用MediaProjection,增加真实可用性验证 if (mediaProjection == null) { mediaProjection = MediaProjectionHolder.getMediaProjection() if (mediaProjection == null) { Log.w(TAG, "MediaProjection未初始化,检查权限数据") val permissionData = MediaProjectionHolder.getPermissionData() Log.w(TAG, "权限数据状态: ${if (permissionData != null) "存在" else "不存在"}") // 尝试重新创建MediaProjection if (permissionData != null) { val (resultCode, resultData) = permissionData if (resultData != null) { val mediaProjectionManager = service.getSystemService(android.content.Context.MEDIA_PROJECTION_SERVICE) as android.media.projection.MediaProjectionManager mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData) if (mediaProjection != null) { Log.i(TAG, "重新创建MediaProjection成功") MediaProjectionHolder.setMediaProjection(mediaProjection) } else { Log.w(TAG, "重新创建MediaProjection失败") } } } if (mediaProjection == null) { // ✅ Android 15优化:先尝试静默恢复,再考虑权限申请 if (android.os.Build.VERSION.SDK_INT >= 35) { Log.w(TAG, "⚠️ Android 15 MediaProjection为null,尝试静默恢复") if (attemptAndroid15SilentRecovery()) { // 静默恢复成功,继续执行 Log.i(TAG, "✅ Android 15静默恢复成功,继续屏幕捕获") } else { // 静默恢复失败,可能确实需要重新申请权限 Log.e(TAG, "❌ Android 15静默恢复失败,MediaProjection权限确实丢失") triggerPermissionRecovery() return null } } else { // 其他版本的处理逻辑 Log.e(TAG, "❌ MediaProjection权限丢失,触发自动权限恢复") triggerPermissionRecovery() return null } } } } // 🛡️ 保守策略:只在实际使用失败时才进行权限检测,避免过度检测 // 移除主动的可用性测试,让权限在实际使用中自然暴露问题 // 初始化ImageReader和VirtualDisplay(仅第一次) if (imageReader == null || virtualDisplay == null) { setupMediaProjectionResources() } // 尝试获取最新图像,带缓存机制 val currentTime = System.currentTimeMillis() var newBitmap: Bitmap? = null // ✅ Android 15优化:首先尝试获取新图像,增加重试次数和等待时间 var retryCount = 0 val maxRetries = if (Build.VERSION.SDK_INT >= 35) 5 else 2 // Android 15需要更多重试 while (retryCount < maxRetries) { val image = imageReader?.acquireLatestImage() if (image != null) { // 🔧 跟踪Image资源 val trackedImage = trackImage(image) try { newBitmap = convertImageToBitmap(trackedImage) if (newBitmap != null) { // 🔧 跟踪新创建的Bitmap trackBitmap(newBitmap) Log.d(TAG, "MediaProjection获取新图像: ${newBitmap.width}x${newBitmap.height}") // ✅ 成功获取图像,重置失败计数 if (consecutiveImageFailures > 0) { Log.d(TAG, "✅ 图像获取成功,重置失败计数(之前${consecutiveImageFailures}次)") consecutiveImageFailures = 0 } // 更新缓存 safeRecycleLastValidBitmap() lastValidBitmap = newBitmap.copy(Bitmap.Config.ARGB_8888, false) // 🔧 跟踪缓存的Bitmap副本 lastValidBitmap?.let { trackBitmap(it) } lastCaptureTime = currentTime return newBitmap } } catch (e: Exception) { Log.e(TAG, "转换图像失败", e) } finally { trackedImage.close() } } // ✅ Android 15增加等待时间,因为图像生成可能需要更长时间 val waitTime = if (Build.VERSION.SDK_INT >= 35) { if (retryCount == 0) 50L else (retryCount * 30L) // 首次50ms,后续递增 } else { 20L } Thread.sleep(waitTime) retryCount++ // Android 15特殊处理:在重试期间记录更多调试信息 if (Build.VERSION.SDK_INT >= 35 && retryCount > 1) { Log.d(TAG, "Android 15图像获取重试 $retryCount/$maxRetries,等待${waitTime}ms") } } // 如果获取不到新图像,检查是否可以使用缓存 if (lastValidBitmap != null) { val timeSinceLastCapture = currentTime - lastCaptureTime if (timeSinceLastCapture < 30000) { // 延长到30秒内的缓存有效 Log.d(TAG, "使用缓存图像 (${timeSinceLastCapture}ms前) - 静止页面") return lastValidBitmap!!.copy(Bitmap.Config.ARGB_8888, false) } else { Log.w(TAG, "缓存图像过期 (${timeSinceLastCapture}ms前),清理缓存") safeRecycleLastValidBitmap() } } // ✅ 智能重新初始化逻辑:增加失败计数和多层检查 consecutiveImageFailures++ Log.w(TAG, "MediaProjection无新图像且无有效缓存 (连续失败${consecutiveImageFailures}次)") // ✅ Android 15特殊处理:智能重新初始化策略 if (Build.VERSION.SDK_INT >= 35) { // 首先尝试强制刷新(轻量级恢复) if (consecutiveImageFailures <= 5) { Log.i(TAG, "🔧 Android 15:尝试轻量级强制刷新 (失败${consecutiveImageFailures}/5次)") val refreshResult = forceRefreshAndroid15Images() if (refreshResult != null) { Log.i(TAG, "✅ Android 15轻量级刷新成功,重置失败计数") consecutiveImageFailures = 0 // 重置失败计数 return refreshResult } } // 检查是否应该进行重新初始化(重量级恢复) val shouldRecreate = shouldRecreateVirtualDisplay() if (shouldRecreate) { Log.i(TAG, "🔧 Android 15:条件满足,进行VirtualDisplay重新初始化") val recreateSuccess = reinitializeVirtualDisplayForAndroid15() if (recreateSuccess) { // 重新初始化成功,重置失败计数 consecutiveImageFailures = 0 // 重新初始化后再次尝试获取图像 Thread.sleep(500) val retryImage = imageReader?.acquireLatestImage() if (retryImage != null) { try { val retryBitmap = convertImageToBitmap(retryImage) if (retryBitmap != null) { Log.i(TAG, "✅ Android 15重新初始化后成功获取图像") safeRecycleLastValidBitmap() lastValidBitmap = retryBitmap.copy(Bitmap.Config.ARGB_8888, false) lastCaptureTime = System.currentTimeMillis() return retryBitmap } } catch (e: Exception) { Log.e(TAG, "Android 15重新初始化后图像转换失败", e) } finally { retryImage.close() } } } } else { Log.i(TAG, "🛡️ Android 15:不满足重新初始化条件,跳过VirtualDisplay重建") } } else { // 非Android 15设备的处理 if (consecutiveImageFailures >= maxImageFailuresBeforeRecreation) { cleanupVirtualDisplayOnly() // 只清理VirtualDisplay,保留MediaProjection权限 consecutiveImageFailures = 0 } } return null } catch (e: Exception) { Log.e(TAG, "MediaProjection截图失败", e) // 出错时只重置VirtualDisplay,保留MediaProjection权限 cleanupVirtualDisplayOnly() null } } /** * 测试MediaProjection的可用性 */ private fun testMediaProjectionUsability(mediaProjection: android.media.projection.MediaProjection): Boolean { return try { Log.v(TAG, "🧪 测试MediaProjection可用性...") // 尝试创建VirtualDisplay进行真实可用性测试 val displayMetrics = service.resources.displayMetrics val virtualDisplay = mediaProjection.createVirtualDisplay( "usability-test-display", displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, null, null, null ) // 立即释放测试用的VirtualDisplay virtualDisplay?.release() Log.v(TAG, "✅ MediaProjection可用性测试通过") true } catch (e: SecurityException) { Log.w(TAG, "❌ MediaProjection可用性测试失败:安全异常 - ${e.message}") // 这是权限失效的明确信号 false } catch (e: Exception) { Log.w(TAG, "❌ MediaProjection可用性测试失败:其他异常 - ${e.message}") false } } /** * 设置MediaProjection相关资源 - Android 15适配单次令牌限制 */ private fun setupMediaProjectionResources() { try { Log.i(TAG, "初始化MediaProjection资源 - 屏幕尺寸: ${screenWidth}x${screenHeight}") // Android 15检查:如果MediaProjection已经创建过VirtualDisplay,需要重新获取 if (Build.VERSION.SDK_INT >= 35 && mediaProjection != null) { Log.i(TAG, "🔧 Android 15检查:验证MediaProjection令牌可用性") if (isMediaProjectionTokenUsed()) { Log.w(TAG, "⚠️ Android 15令牌已使用,需要重新获取MediaProjection") if (!regenerateMediaProjectionForAndroid15()) { Log.e(TAG, "❌ Android 15重新生成MediaProjection失败") return } } } // 只清理VirtualDisplay和ImageReader,保留MediaProjection权限 cleanupVirtualDisplayOnly() // ✅ Android 15优化:创建ImageReader,调整缓存和格式 val bufferCount = if (Build.VERSION.SDK_INT >= 35) { 3 // Android 15需要更多缓存确保图像连续性 } else { 2 // 其他版本减少缓存避免内存问题 } imageReader = android.media.ImageReader.newInstance( screenWidth, screenHeight, android.graphics.PixelFormat.RGBA_8888, bufferCount ) // ✅ Android 15添加监听器来监控图像可用性 if (Build.VERSION.SDK_INT >= 35) { imageReader?.setOnImageAvailableListener({ reader -> Log.v(TAG, "🔧 Android 15: ImageReader有新图像可用") }, backgroundHandler) } Log.i(TAG, "ImageReader创建完成") // 创建VirtualDisplay virtualDisplay = mediaProjection?.createVirtualDisplay( "RemoteControlCapture", screenWidth, screenHeight, service.resources.displayMetrics.densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader?.surface, null, null ) if (virtualDisplay != null) { Log.i(TAG, "VirtualDisplay创建成功") // ✅ Android 15:VirtualDisplay创建成功后触发权限申请处理 if (Build.VERSION.SDK_INT >= 35) { Log.i(TAG, "🔧 Android 15:VirtualDisplay创建成功,触发权限申请处理") triggerAndroid15PermissionRequest("首次初始化VirtualDisplay") } // ✅ Android 15:标记session为已使用 markAndroid15SessionUsed() } else { Log.e(TAG, "VirtualDisplay创建失败") return } Log.i(TAG, "MediaProjection资源初始化完成,等待画面稳定...") // ✅ Android 15需要更长的稳定时间 val stabilizeTime = if (Build.VERSION.SDK_INT >= 35) { Log.i(TAG, "🔧 Android 15设备:等待更长时间确保VirtualDisplay稳定") 2000L // Android 15需要2秒稳定时间(从1.2秒增加到2秒) } else { 500L // 其他版本500ms } Thread.sleep(stabilizeTime) // ✅ Android 15额外处理:强制刷新Surface确保连接 if (Build.VERSION.SDK_INT >= 35) { try { Log.i(TAG, "🔧 Android 15:强制刷新Surface连接") // 通过重新设置Surface来确保连接 imageReader?.surface?.let { surface -> // 触发Surface刷新 surface.toString() // 这会触发Surface的内部状态检查 } Thread.sleep(300) // 给Surface刷新时间 } catch (e: Exception) { Log.w(TAG, "⚠️ Android 15 Surface刷新失败:${e.message}") } } // ✅ Android 15验证:尝试预先获取一帧图像确保VirtualDisplay工作正常 if (Build.VERSION.SDK_INT >= 35) { Log.i(TAG, "🔧 Android 15验证:预先测试图像获取...") try { val testImage = imageReader?.acquireLatestImage() if (testImage != null) { Log.i(TAG, "✅ Android 15 VirtualDisplay预验证成功") testImage.close() } else { Log.w(TAG, "⚠️ Android 15 VirtualDisplay预验证:暂无图像,但这可能是正常的") } } catch (e: Exception) { Log.w(TAG, "⚠️ Android 15 VirtualDisplay预验证异常:${e.message}") } } } catch (e: SecurityException) { Log.e(TAG, "初始化MediaProjection资源失败:权限问题", e) // ✅ 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只能使用一次") // 标记session为已使用 markAndroid15SessionUsed() // 清理资源并触发重新申请 cleanupVirtualDisplayOnly() // 通知权限丢失,需要重新申请 Log.i(TAG, "📢 通知权限丢失,触发重新申请流程") onMediaProjectionLost() } else { Log.w(TAG, "检测到其他权限问题,但不立即重新申请权限") // 🛡️ 保守处理:只清理资源,不触发权限重新申请 cleanupVirtualDisplayOnly() // 记录权限问题,但不采取激进措施 Log.i(TAG, "🛡️ 权限问题已记录,保持保守策略") } } catch (e: Exception) { Log.e(TAG, "初始化MediaProjection资源失败:其他异常", e) cleanupVirtualDisplayOnly() } } /** * 将Image转换为Bitmap */ private fun convertImageToBitmap(image: android.media.Image): Bitmap? { return try { val planes = image.planes val buffer = planes[0].buffer val pixelStride = planes[0].pixelStride val rowStride = planes[0].rowStride val rowPadding = rowStride - pixelStride * screenWidth val bitmap = Bitmap.createBitmap( screenWidth + rowPadding / pixelStride, screenHeight, Bitmap.Config.ARGB_8888 ) bitmap.copyPixelsFromBuffer(buffer) // 如果有padding,需要裁剪 if (rowPadding != 0) { Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight) } else { bitmap } } catch (e: Exception) { Log.e(TAG, "转换Image为Bitmap失败", e) null } } /** * 生成实时测试图像(增强版) */ private fun generateRealtimeTestImage(): Bitmap? { return try { val testBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(testBitmap) // 绘制一个模拟真实应用的测试图案 val paint = android.graphics.Paint().apply { isAntiAlias = true } // 背景渐变 val time = System.currentTimeMillis() val hue = (time / 50) % 360 canvas.drawColor(android.graphics.Color.HSVToColor(floatArrayOf(hue.toFloat(), 0.3f, 0.9f))) // 标题 paint.apply { textSize = 60f color = android.graphics.Color.WHITE typeface = android.graphics.Typeface.DEFAULT_BOLD } canvas.drawText("实时屏幕捕获", 50f, 100f, paint) // 设备信息 paint.apply { textSize = 40f color = android.graphics.Color.BLACK typeface = android.graphics.Typeface.DEFAULT } canvas.drawText("分辨率: ${screenWidth}x${screenHeight}", 50f, 180f, paint) canvas.drawText("帧率: ${CAPTURE_FPS} FPS", 50f, 240f, paint) canvas.drawText("时间: ${java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())}", 50f, 300f, paint) // 动态元素 val frameCount = (time / 100) % 1000 canvas.drawText("帧数: $frameCount", 50f, 360f, paint) // 移动的几何图形 paint.color = android.graphics.Color.RED val x1 = ((time / 20) % screenWidth).toFloat() canvas.drawCircle(x1, 450f, 30f, paint) paint.color = android.graphics.Color.GREEN val x2 = ((time / 30) % screenWidth).toFloat() canvas.drawRect(x2, 520f, x2 + 60f, 580f, paint) paint.color = android.graphics.Color.BLUE val x3 = ((time / 40) % screenWidth).toFloat() val y3 = 650f + 50f * kotlin.math.sin(time / 200.0).toFloat() canvas.drawCircle(x3, y3, 25f, paint) // 模拟状态栏 paint.color = android.graphics.Color.BLACK paint.alpha = 200 canvas.drawRect(0f, 0f, screenWidth.toFloat(), 80f, paint) paint.apply { color = android.graphics.Color.WHITE alpha = 255 textSize = 30f } canvas.drawText("📱 Android ${Build.VERSION.RELEASE}", 20f, 50f, paint) canvas.drawText("🔋100%", screenWidth - 150f, 50f, paint) Log.d(TAG, "生成实时测试图像: ${screenWidth}x${screenHeight}") testBitmap } catch (e: Exception) { Log.e(TAG, "生成实时测试图像失败", e) generateTestImage() // 回退到简单测试图像 } } /** * Android 15静默恢复(已废弃,因为session只能使用一次) */ private fun attemptAndroid15SilentRecovery(): Boolean { Log.w(TAG, "⚠️ Android 15无法静默恢复:每个MediaProjection session只能使用一次") Log.w(TAG, "💡 需要重新申请用户权限才能继续使用屏幕捕获") return false } /** * Android 15新增:标记session为已使用 */ private fun markAndroid15SessionUsed() { try { if (Build.VERSION.SDK_INT >= 35) { // 如果有Android 15专用管理器,调用其方法 val accessibilityService = com.hikoncont.service.AccessibilityRemoteService.getInstance() val android15Manager = accessibilityService?.getAndroid15MediaProjectionManager() android15Manager?.markSessionUsed() Log.i(TAG, "🔒 已标记Android 15 session为已使用") } } catch (e: Exception) { Log.e(TAG, "标记Android 15 session失败", e) } } /** * Android 15新增:权限丢失通知 */ private fun onMediaProjectionLost() { try { Log.w(TAG, "📢 MediaProjection权限丢失,通知AccessibilityService") val accessibilityService = com.hikoncont.service.AccessibilityRemoteService.getInstance() if (accessibilityService != null) { // 通过广播通知权限丢失 val intent = Intent("android.mycustrecev.MEDIA_PROJECTION_LOST") intent.putExtra("reason", "android15_session_used") intent.putExtra("requireNewPermission", true) service.sendBroadcast(intent) Log.i(TAG, "📡 已发送MediaProjection权限丢失广播") } } catch (e: Exception) { Log.e(TAG, "通知MediaProjection权限丢失失败", e) } } /** * 暂停屏幕捕获但保留权限(用于重连时避免发送失败) */ fun pauseCapture() { try { Log.i(TAG, "暂停屏幕捕获(保留权限)") isCapturing = false isPaused = true // ✅ 持久化暂停状态 savePauseState() // 只清理VirtualDisplay,保留MediaProjection权限 cleanupVirtualDisplayOnly() Log.i(TAG, "屏幕捕获已暂停,权限保留,状态已持久化") } catch (e: Exception) { Log.e(TAG, "暂停屏幕捕获失败", e) } } /** * 恢复屏幕捕获 */ fun resumeCapture() { try { if (!isPaused) { Log.d(TAG, "屏幕捕获未暂停,无需恢复") return } Log.i(TAG, "恢复屏幕捕获") isPaused = false // ✅ 持久化恢复状态 savePauseState() startCapture() } catch (e: Exception) { Log.e(TAG, "恢复屏幕捕获失败", e) } } /** * 检查是否暂停 */ fun isPaused(): Boolean { return isPaused } /** * 获取最后成功发送时间 */ fun getLastSuccessfulSendTime(): Long? { return lastSuccessfulSendTime } /** * 停止屏幕捕获 */ fun stopCapture() { try { Log.i(TAG, "停止屏幕捕获") 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, "✅ 已清理持久化的暂停状态") } catch (e: Exception) { Log.w(TAG, "⚠️ 清理持久化暂停状态失败", e) } // 🔧 停止队列处理协程 stopQueueProcessor() // ❌ 修复:只清理VirtualDisplay,保留MediaProjection权限防止Android 15权限丢失 // 正常停止时不应该销毁权限,只有用户主动停止或应用退出时才销毁 cleanupVirtualDisplayOnly() // 只清理显示资源,保留权限 Log.i(TAG, "屏幕捕获已停止(权限保留)") // 关闭截图执行器 try { screenshotExecutor.shutdownNow() } catch (_: Exception) {} } catch (e: Exception) { Log.e(TAG, "停止屏幕捕获失败", e) } } /** * 强制停止屏幕捕获并清理权限(仅在用户主动停止时使用) */ fun forceStopCapture() { try { Log.i(TAG, "强制停止屏幕捕获") 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, "✅ 已清理持久化的暂停状态") } catch (e: Exception) { Log.w(TAG, "⚠️ 清理持久化暂停状态失败", e) } // 🔧 停止队列处理协程 stopQueueProcessor() // 强制清理MediaProjection权限 forceCleanupMediaProjectionResources() Log.i(TAG, "屏幕捕获已强制停止") // 关闭截图执行器 try { screenshotExecutor.shutdownNow() } catch (_: Exception) {} } catch (e: Exception) { Log.e(TAG, "强制停止屏幕捕获失败", e) } } /** * 设置MediaProjection */ fun setMediaProjection(projection: android.media.projection.MediaProjection) { Log.i(TAG, "✅ MediaProjection已设置到ScreenCaptureManager") this.mediaProjection = projection MediaProjectionHolder.setMediaProjection(projection) } /** * 🎯 智能压缩Bitmap为JPEG - 动态调整质量以平衡画质和传输效率 */ private fun compressBitmap(bitmap: Bitmap): ByteArray { val outputStream = ByteArrayOutputStream() try { // 计算缩放比例,确保不超过最大尺寸 val scaledBitmap = scaleDownBitmap(bitmap) // 🎯 智能压缩:根据数据大小动态调整质量 var quality = dynamicQuality var compressedData: ByteArray var attempts = 0 val maxAttempts = 3 val targetSize = 150 * 1024 // 目标150KB,平衡质量和传输效率 do { outputStream.reset() scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream) compressedData = outputStream.toByteArray() attempts++ if (compressedData.size > targetSize && attempts < maxAttempts) { // 数据过大,降低质量重新压缩 quality = maxOf(25, quality - 15) // 最低不低于25 Log.v(TAG, "🔧 数据过大(${compressedData.size} bytes),降低质量到$quality 重新压缩") } else { break } } while (attempts < maxAttempts) // 如果缩放了,释放缩放后的bitmap if (scaledBitmap != bitmap) { scaledBitmap.recycle() } Log.d(TAG, "🎯 智能压缩完成: 原始${bitmap.width}x${bitmap.height} -> 输出${compressedData.size} bytes (质量$quality, 尝试${attempts}次)") } catch (e: Exception) { Log.e(TAG, "❌ 智能压缩失败,使用基础压缩", e) // 如果智能压缩失败,使用基础压缩 outputStream.reset() bitmap.compress(Bitmap.CompressFormat.JPEG, dynamicQuality, outputStream) } return outputStream.toByteArray() } /** * 缩放图片以控制数据大小 */ private fun scaleDownBitmap(bitmap: Bitmap): Bitmap { val originalWidth = bitmap.width val originalHeight = bitmap.height // 如果已经在限制范围内,直接返回 if (originalWidth <= dynamicMaxWidth && originalHeight <= dynamicMaxHeight) { return bitmap } // 计算缩放比例,保持宽高比 val widthRatio = dynamicMaxWidth.toFloat() / originalWidth val heightRatio = dynamicMaxHeight.toFloat() / originalHeight val scaleFactor = minOf(widthRatio, heightRatio) val newWidth = (originalWidth * scaleFactor).toInt() val newHeight = (originalHeight * scaleFactor).toInt() Log.d(TAG, "缩放图片: ${originalWidth}x${originalHeight} -> ${newWidth}x${newHeight} (scale=${scaleFactor})") return try { Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true) } catch (e: Exception) { Log.e(TAG, "缩放图片失败,使用原图", e) bitmap } } /** * 发送帧数据到服务器 */ private fun sendFrameToServer(frameData: ByteArray) { try { totalFrameCount++ // 🔧 启动队列处理器(只启动一次) startQueueProcessor() // 🎯 优化队列清理策略,减少画面跳跃,提供更平滑的体验 when { screenDataQueue.size >= 13 -> { // 队列接近满载,适度清理2帧保持流畅(避免5帧跳跃) Log.w(TAG, "⚠️ 队列接近满载(${screenDataQueue.size}/15),轻度清理旧数据保持流畅") repeat(2) { screenDataQueue.poll()?.let { droppedFrameCount++ } } } screenDataQueue.size >= 11 -> { // 中等负载,清理1帧防止积压 Log.v(TAG, "🔧 队列中等负载(${screenDataQueue.size}/15),清理1帧防止积压") screenDataQueue.poll()?.let { droppedFrameCount++ } } } // 尝试添加新帧,如果满了就丢弃旧帧 if (!screenDataQueue.offer(frameData)) { // 队列已满,丢弃最旧的帧并添加新帧 screenDataQueue.poll()?.let { droppedFrameCount++ } if (!screenDataQueue.offer(frameData)) { Log.e(TAG, "❌ 队列清理后仍无法添加新帧,跳过此帧") droppedFrameCount++ return } } // 🔧 定期内存检查和清理 checkAndCleanMemory() } catch (e: Exception) { Log.e(TAG, "❌ 发送帧数据失败", e) } } /** * 🔧 启动单一的队列处理协程,避免协程泄漏 */ private fun startQueueProcessor() { if (queueProcessingStarted.compareAndSet(false, true)) { Log.d(TAG, "🚀 启动屏幕数据队列处理协程") queueProcessorJob = serviceScope.launch { 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) // 出错时延长休眠时间 } } Log.d(TAG, "🛑 屏幕数据队列处理协程结束") queueProcessingStarted.set(false) } } } /** * 🔧 停止队列处理协程 */ private fun stopQueueProcessor() { try { Log.d(TAG, "🛑 停止屏幕数据队列处理协程") queueProcessorJob?.cancel() queueProcessorJob = null queueProcessingStarted.set(false) // 清空队列中剩余的数据 val remainingFrames = screenDataQueue.size screenDataQueue.clear() if (remainingFrames > 0) { Log.d(TAG, "🗑️ 清空队列中剩余的${remainingFrames}帧数据") } } catch (e: Exception) { Log.e(TAG, "❌ 停止队列处理协程失败", e) } } /** * 🔧 处理单帧数据 */ private suspend fun processFrameData(frameData: ByteArray) { try { var success = false // ✅ 优先使用Socket.IO v4官方客户端发送屏幕数据 val socketIOManager = service.getSocketIOManager() if (socketIOManager != null && socketIOManager.isConnected()) { socketIOManager.sendScreenData(frameData) Log.v(TAG, "✅ Socket.IO v4发送帧数据: ${frameData.size} bytes") success = true } else { Log.w(TAG, "⚠️ Socket.IO连接不可用,无法发送屏幕数据") } // 记录成功发送时间 if (success) { lastSuccessfulSendTime = System.currentTimeMillis() } // 🔧 确保frameData被GC回收 frameData.fill(0) // 清空数组内容 } catch (e: Exception) { Log.e(TAG, "❌ 处理帧数据失败", e) } } /** * 🔧 内存检查和清理机制 */ private fun checkAndCleanMemory() { val currentTime = System.currentTimeMillis() if (currentTime - lastMemoryCheckTime < memoryCheckInterval) { return } lastMemoryCheckTime = currentTime try { // 获取内存使用情况 val runtime = Runtime.getRuntime() val maxMemory = runtime.maxMemory() val totalMemory = runtime.totalMemory() val freeMemory = runtime.freeMemory() val usedMemory = totalMemory - freeMemory val memoryUsagePercent = usedMemory.toFloat() / maxMemory.toFloat() Log.d(TAG, "📊 内存使用: ${(memoryUsagePercent * 100).toInt()}% (${usedMemory / 1024 / 1024}MB / ${maxMemory / 1024 / 1024}MB)") // 🚨 内存使用率过高时触发清理 if (memoryUsagePercent > maxMemoryUsagePercent) { Log.w(TAG, "🚨 内存使用率过高(${(memoryUsagePercent * 100).toInt()}%),触发紧急清理") performEmergencyCleanup() } // 🔧 定期清理无效的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()}%") } } catch (e: Exception) { Log.e(TAG, "❌ 内存检查失败", e) } } /** * 🚨 紧急内存清理 */ private fun performEmergencyCleanup() { try { Log.w(TAG, "🚨 执行紧急内存清理") // 1. 清空屏幕数据队列 val queueSize = screenDataQueue.size screenDataQueue.clear() Log.w(TAG, "🗑️ 清空屏幕数据队列,释放${queueSize}帧数据") // 2. 清理缓存的图像 safeRecycleLastValidBitmap() Log.w(TAG, "🗑️ 清理缓存图像") // 3. 强制回收弱引用中的资源 cleanupWeakReferences(true) // 4. 降低临时图像质量(如果需要的话) Log.w(TAG, "🗑️ 临时降低图像质量以减少内存压力") // 5. 建议系统进行垃圾回收 System.gc() Log.w(TAG, "✅ 紧急内存清理完成") } catch (e: Exception) { Log.e(TAG, "❌ 紧急内存清理失败", e) } } /** * 🔧 清理WeakReference */ private fun cleanupWeakReferences(force: Boolean = false) { try { // 清理已回收的Bitmap引用 val bitmapIterator = activeBitmaps.iterator() var cleanedBitmaps = 0 while (bitmapIterator.hasNext()) { val ref = bitmapIterator.next() val bitmap = ref.get() if (bitmap == null || bitmap.isRecycled || (force && !bitmap.isRecycled)) { if (force && bitmap != null && !bitmap.isRecycled) { bitmap.recycle() } bitmapIterator.remove() cleanedBitmaps++ } } // 清理已回收的Image引用 val imageIterator = activeImages.iterator() var cleanedImages = 0 while (imageIterator.hasNext()) { val ref = imageIterator.next() val image = ref.get() if (image == null || (force && image != null)) { if (force && image != null) { try { image.close() } catch (e: Exception) { // Image可能已经关闭 } } imageIterator.remove() cleanedImages++ } } if (cleanedBitmaps > 0 || cleanedImages > 0) { Log.d(TAG, "🗑️ 清理WeakReference: Bitmap=${cleanedBitmaps}, Image=${cleanedImages}") } } catch (e: Exception) { Log.e(TAG, "❌ 清理WeakReference失败", e) } } /** * 🔧 跟踪Bitmap资源 */ private fun trackBitmap(bitmap: Bitmap): Bitmap { activeBitmaps.add(WeakReference(bitmap)) return bitmap } /** * 🔧 跟踪Image资源 */ private fun trackImage(image: android.media.Image): android.media.Image { activeImages.add(WeakReference(image)) return image } /** * 降级捕获方案 - 测试图像 */ private fun startFallbackCapture() { Log.i(TAG, "启动降级捕获方案") captureScope.launch { isCapturing = true while (isCapturing) { try { // 使用测试图像 val screenshot = generateTestImage() if (screenshot != null) { val jpegData = compressBitmap(screenshot) sendFrameToServer(jpegData) screenshot.recycle() } // 控制帧率 delay(1000 / CAPTURE_FPS.toLong()) } catch (e: Exception) { Log.e(TAG, "降级捕获失败", e) delay(1000) // 出错时延长间隔 } } } } /** * ✅ 生成权限恢复提示测试图像 */ private fun generatePermissionRecoveryTestImage(): Bitmap? { return try { val testBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(testBitmap) // 绘制权限恢复提示界面 val paint = android.graphics.Paint().apply { isAntiAlias = true } // 橙色警告背景 canvas.drawColor(android.graphics.Color.rgb(255, 193, 7)) // 标题 paint.apply { textSize = 70f color = android.graphics.Color.WHITE typeface = android.graphics.Typeface.DEFAULT_BOLD textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("🔧 权限恢复中", screenWidth / 2f, 150f, paint) // 状态信息 paint.apply { textSize = 45f color = android.graphics.Color.BLACK typeface = android.graphics.Typeface.DEFAULT } canvas.drawText("检测到屏幕录制权限丢失", screenWidth / 2f, 250f, paint) canvas.drawText("正在自动重新申请权限...", screenWidth / 2f, 320f, paint) // 动态进度指示 val time = System.currentTimeMillis() val progress = ((time / 500) % 4).toInt() val progressText = "请稍候" + ".".repeat(progress + 1) paint.apply { textSize = 50f color = android.graphics.Color.rgb(220, 53, 69) } canvas.drawText(progressText, screenWidth / 2f, 420f, paint) // 说明文字 paint.apply { textSize = 35f color = android.graphics.Color.BLACK textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("• 连接状态正常,控制功能可用", screenWidth / 2f, 520f, paint) canvas.drawText("• 屏幕显示将在权限恢复后正常", screenWidth / 2f, 570f, paint) canvas.drawText("• 如有权限弹窗请点击允许", screenWidth / 2f, 620f, paint) // 旋转的恢复图标 val centerX = screenWidth / 2f val centerY = 750f val radius = 40f val rotation = (time / 50f) % 360f canvas.save() canvas.rotate(rotation, centerX, centerY) paint.apply { color = android.graphics.Color.WHITE strokeWidth = 8f style = android.graphics.Paint.Style.STROKE } canvas.drawCircle(centerX, centerY, radius, paint) // 箭头 paint.style = android.graphics.Paint.Style.FILL val arrowPath = android.graphics.Path().apply { moveTo(centerX + radius - 5, centerY - 15) lineTo(centerX + radius + 10, centerY) lineTo(centerX + radius - 5, centerY + 15) close() } canvas.drawPath(arrowPath, paint) canvas.restore() // 时间戳 paint.apply { textSize = 25f color = android.graphics.Color.GRAY textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("${java.text.SimpleDateFormat("HH:mm:ss", java.util.Locale.getDefault()).format(java.util.Date())}", screenWidth / 2f, screenHeight - 50f, paint) Log.d(TAG, "生成权限恢复提示图像: ${screenWidth}x${screenHeight}") testBitmap } catch (e: Exception) { Log.e(TAG, "生成权限恢复提示图像失败", e) generateRealtimeTestImage() // 回退到普通测试图像 } } /** * 生成测试图像 */ private fun generateTestImage(): Bitmap? { return try { val testBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(testBitmap) // 绘制一个简单的测试图案 val paint = android.graphics.Paint().apply { textSize = 50f color = android.graphics.Color.WHITE } canvas.drawColor(android.graphics.Color.BLACK) canvas.drawText("无障碍服务截图API测试", 50f, 100f, paint) canvas.drawText("Screen: ${screenWidth}x${screenHeight}", 50f, 200f, paint) canvas.drawText("Time: ${java.text.SimpleDateFormat("HH:mm:ss", java.util.Locale.getDefault()).format(java.util.Date())}", 50f, 300f, paint) canvas.drawText("Frame: ${System.currentTimeMillis() % 10000}", 50f, 400f, paint) // 绘制一个移动的圆点来显示动画效果 val time = System.currentTimeMillis() val x = ((time / 10) % screenWidth).toFloat() val y = 500f paint.color = android.graphics.Color.RED canvas.drawCircle(x, y, 20f, paint) Log.d(TAG, "生成测试屏幕图像: ${screenWidth}x${screenHeight}") testBitmap } catch (e: Exception) { Log.e(TAG, "生成测试图像失败", e) null } } /** * 获取当前屏幕截图 (兼容性方法) */ fun captureScreenshot(): Bitmap? { return try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // 这里可以实现同步截图,现在返回null null } else { generateTestImage() } } catch (e: Exception) { Log.e(TAG, "截图失败", e) null } } /** * ✅ 触发智能权限恢复机制 - 使用智能权限管理器 */ private fun triggerPermissionRecovery() { try { Log.i(TAG, "🧠 尝试使用智能权限管理器进行权限恢复") // 首先尝试智能权限管理器 val smartManager = com.hikoncont.manager.SmartMediaProjectionManager.getInstance(service) val currentProjection = smartManager.getCurrentMediaProjection() if (currentProjection != null) { Log.i(TAG, "✅ 智能管理器找到有效的MediaProjection,直接使用") setMediaProjection(currentProjection) return } // 智能管理器没有有效权限,检查是否可以静默恢复 val permissionData = com.hikoncont.MediaProjectionHolder.getPermissionData() if (permissionData != null) { val (resultCode, resultData) = permissionData if (resultData != null) { Log.i(TAG, "🔧 尝试通过智能管理器静默恢复权限") val recovered = smartManager.setMediaProjection(resultCode, resultData) if (recovered) { Log.i(TAG, "✅ 智能权限恢复成功") val newProjection = smartManager.getCurrentMediaProjection() if (newProjection != null) { setMediaProjection(newProjection) return } } } } // 智能恢复失败,回退到传统方式但更智能 Log.w(TAG, "⚠️ 智能权限恢复失败,回退到传统恢复机制") triggerTraditionalPermissionRecovery() } catch (e: Exception) { Log.e(TAG, "❌ 智能权限恢复失败,回退到传统方式", e) triggerTraditionalPermissionRecovery() } } /** * 传统权限恢复机制(作为智能恢复的备用方案) */ private fun triggerTraditionalPermissionRecovery() { try { Log.i(TAG, "🔧 开始传统MediaProjection权限恢复流程") // ✅ 检查权限申请状态,避免在权限申请达到上限时继续启动MainActivity val accessibilityService = com.hikoncont.service.AccessibilityRemoteService.getInstance() if (accessibilityService != null) { if (accessibilityService.areAllPermissionsCompleted() || accessibilityService.isPermissionRequestInProgress()) { Log.i(TAG, "⚠️ 权限申请已完成或正在进行中,跳过权限恢复流程") return } } // 使用协程避免阻塞主线程 captureScope.launch { try { // ❌ 修复:权限恢复时不要清理MediaProjection权限! // 只清理VirtualDisplay等显示资源,保留权限以防Android 15权限丢失 cleanupVirtualDisplayOnly() // 只清理显示资源,保留权限 Log.i(TAG, "🚀 启动MainActivity重新申请MediaProjection权限(保留现有权限防止丢失)") // 启动MainActivity重新申请权限 val intent = android.content.Intent(service, com.hikoncont.MainActivity::class.java).apply { addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP) putExtra("AUTO_REQUEST_PERMISSION", true) // 标记为自动权限申请 putExtra("PERMISSION_LOST_RECOVERY", true) // 标记为权限丢失恢复 putExtra("TRADITIONAL_RECOVERY", true) // 标记为传统恢复模式 } service.startActivity(intent) Log.i(TAG, "✅ 传统MediaProjection权限恢复流程已启动") } catch (e: Exception) { Log.e(TAG, "❌ 传统MediaProjection权限恢复失败", e) } } } catch (e: Exception) { Log.e(TAG, "❌ 触发传统权限恢复失败", e) } } /** * 只清理VirtualDisplay和ImageReader,保留MediaProjection权限 */ private fun cleanupVirtualDisplayOnly() { try { // 清理VirtualDisplay virtualDisplay?.release() virtualDisplay = null // 清理ImageReader imageReader?.close() imageReader = null // ✅ 安全清理缓存的Bitmap,防止多线程并发导致native crash (SIGSEGV) safeRecycleLastValidBitmap() lastCaptureTime = 0L Log.i(TAG, "VirtualDisplay和ImageReader已清理,MediaProjection权限保留") } catch (e: Exception) { Log.e(TAG, "清理VirtualDisplay资源失败", e) } } /** * 清理MediaProjection相关资源(完全清理,包括权限)- 修复Android 15权限丢失问题 */ private fun cleanupMediaProjectionResources() { try { // 先清理VirtualDisplay和ImageReader cleanupVirtualDisplayOnly() // ❌ 修复:不要随意停止MediaProjection权限!特别是Android 15设备 // 这会导致权限永久失效,只有在用户主动停止或应用完全退出时才清理权限 // mediaProjection?.stop() // 删除这行,防止权限被意外停止 mediaProjection = null Log.i(TAG, "MediaProjection资源已清理(权限保留,防止Android 15权限丢失)") } catch (e: Exception) { Log.e(TAG, "清理MediaProjection资源失败", e) } } /** * 强制清理MediaProjection权限(仅在用户主动停止或应用退出时使用) */ private fun forceCleanupMediaProjectionResources() { try { // 先清理VirtualDisplay和ImageReader cleanupVirtualDisplayOnly() // 强制停止MediaProjection权限 mediaProjection?.stop() mediaProjection = null Log.i(TAG, "MediaProjection权限已强制清理") } catch (e: Exception) { Log.e(TAG, "强制清理MediaProjection资源失败", e) } } /** * 清理资源 */ fun release() { try { Log.i(TAG, "清理屏幕捕获资源") isCapturing = false // ❌ 修复:应用完全退出时才强制清理MediaProjection权限 // 正常情况下只清理VirtualDisplay,保留权限以防Android 15权限丢失 cleanupVirtualDisplayOnly() // 只清理显示资源,保留权限 // 取消所有协程 captureScope.cancel() // 停止后台线程 handlerThread.quitSafely() Log.i(TAG, "屏幕捕获资源清理完成(权限保留)") } catch (e: Exception) { Log.e(TAG, "清理屏幕捕获资源失败", e) } } /** * 强制释放所有资源(仅在应用完全退出时使用) */ fun forceRelease() { try { Log.i(TAG, "强制清理屏幕捕获资源") isCapturing = false // 强制清理MediaProjection权限 forceCleanupMediaProjectionResources() // 取消所有协程 captureScope.cancel() // 停止后台线程 handlerThread.quitSafely() Log.i(TAG, "屏幕捕获资源强制清理完成") } catch (e: Exception) { Log.e(TAG, "强制清理屏幕捕获资源失败", e) } } /** * Android 15:检查MediaProjection令牌是否已被使用 */ private fun isMediaProjectionTokenUsed(): Boolean { return try { if (Build.VERSION.SDK_INT >= 35) { // 通过Android 15专用管理器检查 val accessibilityService = com.hikoncont.service.AccessibilityRemoteService.getInstance() val android15Manager = accessibilityService?.getAndroid15MediaProjectionManager() return android15Manager?.isSessionUsed() ?: false } false } catch (e: Exception) { Log.e(TAG, "检查Android 15令牌状态失败", e) false } } /** * Android 15:重新生成MediaProjection以解决单次令牌限制 */ private fun regenerateMediaProjectionForAndroid15(): Boolean { return try { if (Build.VERSION.SDK_INT >= 35) { Log.i(TAG, "🔄 Android 15:重新生成MediaProjection令牌") val permissionData = MediaProjectionHolder.getPermissionData() if (permissionData != null) { val (resultCode, resultData) = permissionData if (resultData != null) { // 通过Android 15管理器重新创建 val accessibilityService = com.hikoncont.service.AccessibilityRemoteService.getInstance() val android15Manager = accessibilityService?.getAndroid15MediaProjectionManager() val newProjection = android15Manager?.createMediaProjectionWithCallback(resultCode, resultData) if (newProjection != null) { // 更新本地引用 mediaProjection = newProjection MediaProjectionHolder.setMediaProjection(newProjection) Log.i(TAG, "✅ Android 15 MediaProjection重新生成成功") return true } else { Log.e(TAG, "❌ Android 15 MediaProjection重新生成失败:创建返回null") } } else { Log.e(TAG, "❌ Android 15重新生成失败:权限数据中Intent为null") } } else { Log.e(TAG, "❌ Android 15重新生成失败:无权限数据") } } false } catch (e: Exception) { Log.e(TAG, "❌ Android 15重新生成MediaProjection异常", e) false } } /** * Android 15专用:重新初始化VirtualDisplay以获取图像 */ private fun reinitializeVirtualDisplayForAndroid15(): Boolean { virtualDisplayRecreationLock.lock() try { if (Build.VERSION.SDK_INT >= 35 && mediaProjection != null) { Log.i(TAG, "🔧 Android 15:重新初始化VirtualDisplay以获取图像") // ✅ 更新重新创建统计 val currentTime = System.currentTimeMillis() lastVirtualDisplayRecreationTime = currentTime consecutiveRecreationCount++ // ✅ 设置重新创建状态,抑制权限保活检查 isVirtualDisplayRecreating = true Log.i(TAG, "🛡️ [VirtualDisplay重建] 开始重新创建(第${consecutiveRecreationCount}次),抑制权限保活检查") // 清理当前的VirtualDisplay和ImageReader,完全重新创建 virtualDisplay?.release() virtualDisplay = null imageReader?.close() imageReader = null // 等待系统完全清理 Thread.sleep(500) Log.i(TAG, "🔧 Android 15:完全重新创建ImageReader和VirtualDisplay") // 重新创建ImageReader imageReader = android.media.ImageReader.newInstance( screenWidth, screenHeight, android.graphics.PixelFormat.RGBA_8888, 4 // 增加到4个缓存 ) // 添加监听器 imageReader?.setOnImageAvailableListener({ reader -> Log.v(TAG, "🔧 Android 15重新初始化后: ImageReader有新图像可用") }, backgroundHandler) Log.i(TAG, "🔧 Android 15:ImageReader重新创建完成") // 重新创建VirtualDisplay virtualDisplay = mediaProjection?.createVirtualDisplay( "RemoteControlCaptureRetry_${System.currentTimeMillis()}", screenWidth, screenHeight, service.resources.displayMetrics.densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader?.surface, null, null ) if (virtualDisplay != null) { 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:进行重新初始化后的验证") val testImage = imageReader?.acquireLatestImage() if (testImage != null) { Log.i(TAG, "✅ Android 15重新初始化后验证成功,图像可用") testImage.close() } else { Log.w(TAG, "⚠️ Android 15重新初始化后验证:仍无图像") } // ✅ 延迟重置状态,给系统时间完成权限检查 Thread.sleep(2000) // 等待2秒让系统完成所有权限检查 isVirtualDisplayRecreating = false Log.i(TAG, "🛡️ [VirtualDisplay重建] 重新创建完成,恢复权限保活检查") return true } else { Log.e(TAG, "❌ Android 15 VirtualDisplay重新创建失败") isVirtualDisplayRecreating = false return false } } } catch (e: Exception) { Log.e(TAG, "❌ Android 15重新初始化VirtualDisplay失败", e) // 确保异常情况下也重置状态 isVirtualDisplayRecreating = false return false } finally { virtualDisplayRecreationLock.unlock() } return false } /** * Android 15专用:强制刷新图像获取 */ private fun forceRefreshAndroid15Images(): Bitmap? { try { if (Build.VERSION.SDK_INT >= 35 && imageReader != null && virtualDisplay != null) { Log.i(TAG, "🔧 Android 15:尝试强制刷新图像获取") // 方法1:触发VirtualDisplay刷新 virtualDisplay?.surface?.let { surface -> try { // 强制触发surface渲染 Log.v(TAG, "🔧 Android 15:触发Surface刷新") } catch (e: Exception) { Log.w(TAG, "Surface刷新失败:${e.message}") } } // 方法2:强制等待更长时间 Thread.sleep(1000) // 方法3:尝试获取所有可用图像 var latestImage: android.media.Image? = null var attempts = 0 while (attempts < 3) { val image = imageReader?.acquireLatestImage() if (image != null) { latestImage?.close() // 关闭之前的图像 latestImage = image Log.v(TAG, "🔧 Android 15强制刷新:获得图像(尝试${attempts + 1})") break } Thread.sleep(200) attempts++ } // 转换图像 if (latestImage != null) { try { val bitmap = convertImageToBitmap(latestImage!!) if (bitmap != null) { Log.i(TAG, "✅ Android 15强制刷新成功:${bitmap.width}x${bitmap.height}") // 更新缓存 safeRecycleLastValidBitmap() lastValidBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false) lastCaptureTime = System.currentTimeMillis() return bitmap } } catch (e: Exception) { Log.e(TAG, "Android 15强制刷新图像转换失败", e) } finally { latestImage?.close() } } Log.w(TAG, "⚠️ Android 15强制刷新未获得有效图像") return null } else { return null } } catch (e: Exception) { Log.e(TAG, "❌ Android 15强制刷新失败", e) return null } } /** * Android 15设备注册成功后图像传输验证 */ fun verifyAndroid15ImageTransmission() { if (Build.VERSION.SDK_INT >= 35 && !android15ImageTransmissionVerified) { Log.i(TAG, "🔍 Android 15设备注册成功,开始图像传输验证...") serviceScope.launch { delay(android15ImageVerificationDelay) try { // 检查当前图像传输状态 val hasValidImage = checkImageTransmissionStatus() if (!hasValidImage) { Log.w(TAG, "⚠️ Android 15图像传输验证失败,尝试修复...") repairAndroid15ImageTransmission() } else { Log.i(TAG, "✅ Android 15图像传输验证成功") android15ImageTransmissionVerified = true } } catch (e: Exception) { Log.e(TAG, "❌ Android 15图像传输验证异常", e) } } } } /** * 检查图像传输状态 */ private fun checkImageTransmissionStatus(): Boolean { try { // 检查VirtualDisplay是否存在 if (!isVirtualDisplayCreated()) { Log.w(TAG, "❌ VirtualDisplay未创建") return false } // 尝试获取最新图像 val latestImage = getLatestImage() if (latestImage == null) { Log.w(TAG, "❌ 无法获取最新图像") return false } // 检查图像是否为有效图像(非测试图像) val bitmap = convertImageToBitmap(latestImage) if (bitmap == null) { Log.w(TAG, "❌ 图像转换为Bitmap失败") return false } // 检查图像内容是否有效(避免全黑或全白图像) val pixels = IntArray(bitmap.width * bitmap.height) bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height) val uniqueColors = pixels.distinct().size if (uniqueColors < 10) { Log.w(TAG, "❌ 图像内容可能无效,颜色种类过少: $uniqueColors") return false } Log.i(TAG, "✅ 图像传输状态检查通过") return true } catch (e: Exception) { Log.e(TAG, "❌ 检查图像传输状态失败", e) return false } } /** * 修复Android 15图像传输问题 */ private suspend fun repairAndroid15ImageTransmission() { try { Log.i(TAG, "🔧 开始修复Android 15图像传输...") // 步骤1:强制刷新Surface Log.i(TAG, "📱 步骤1:强制刷新Surface") refreshSurfaceForAndroid15() delay(2000) if (checkImageTransmissionStatus()) { Log.i(TAG, "✅ Surface刷新修复成功") android15ImageTransmissionVerified = true return } // 步骤2:重新初始化ImageReader Log.i(TAG, "📱 步骤2:重新初始化ImageReader") reinitializeImageReaderForAndroid15() delay(3000) if (checkImageTransmissionStatus()) { Log.i(TAG, "✅ ImageReader重新初始化修复成功") android15ImageTransmissionVerified = true return } // 步骤3:完整重建VirtualDisplay Log.i(TAG, "📱 步骤3:完整重建VirtualDisplay") recreateVirtualDisplayForAndroid15() delay(3000) if (checkImageTransmissionStatus()) { Log.i(TAG, "✅ VirtualDisplay重建修复成功") android15ImageTransmissionVerified = true return } Log.w(TAG, "⚠️ Android 15图像传输修复失败,将依赖后续自动恢复机制") } catch (e: Exception) { Log.e(TAG, "❌ Android 15图像传输修复异常", e) } } /** * 为Android 15强制刷新Surface */ private suspend fun refreshSurfaceForAndroid15() { try { imageReader?.let { reader -> val surface = reader.surface if (surface.isValid) { Log.d(TAG, "🔄 Android 15强制刷新Surface") // 通过重设Surface的方式强制刷新 surface.release() delay(500) // 重新创建ImageReader(如果需要) if (isVirtualDisplayCreated()) { createImageReader() } } } } catch (e: Exception) { Log.e(TAG, "❌ Android 15 Surface刷新失败", e) } } /** * 为Android 15重新初始化ImageReader */ private suspend fun reinitializeImageReaderForAndroid15() { try { Log.d(TAG, "🔄 Android 15重新初始化ImageReader") // 释放现有ImageReader releaseImageReader() delay(1000) // 重新创建ImageReader createImageReader() delay(1000) // 如果VirtualDisplay存在,重新关联Surface virtualDisplay?.let { display -> imageReader?.let { reader -> display.surface = reader.surface Log.d(TAG, "✅ Android 15已重新关联ImageReader和VirtualDisplay") } } } catch (e: Exception) { Log.e(TAG, "❌ Android 15 ImageReader重新初始化失败", e) } } /** * 为Android 15完整重建VirtualDisplay */ private suspend fun recreateVirtualDisplayForAndroid15() { try { Log.d(TAG, "🔄 Android 15完整重建VirtualDisplay") // 停止当前屏幕捕获 stopScreenCapture() delay(2000) // 重新启动屏幕捕获 if (startScreenCapture()) { Log.i(TAG, "✅ Android 15 VirtualDisplay重建成功") } else { Log.w(TAG, "⚠️ Android 15 VirtualDisplay重建失败") } } catch (e: Exception) { Log.e(TAG, "❌ Android 15 VirtualDisplay重建异常", e) } } /** * 检查VirtualDisplay是否已创建 */ private fun isVirtualDisplayCreated(): Boolean { return virtualDisplay != null } /** * 获取最新图像 */ private fun getLatestImage(): android.media.Image? { return try { imageReader?.acquireLatestImage() } catch (e: Exception) { Log.e(TAG, "获取最新图像失败", e) null } } /** * 创建ImageReader */ private fun createImageReader() { try { val width = screenWidth.coerceAtMost(MAX_WIDTH) val height = screenHeight.coerceAtMost(MAX_HEIGHT) imageReader = android.media.ImageReader.newInstance( width, height, android.graphics.PixelFormat.RGBA_8888, if (Build.VERSION.SDK_INT >= 35) 4 else 2 // Android 15增加缓冲区 ) Log.d(TAG, "✅ 创建ImageReader: ${width}x${height}") } catch (e: Exception) { Log.e(TAG, "❌ 创建ImageReader失败", e) } } /** * 释放ImageReader */ private fun releaseImageReader() { try { imageReader?.close() imageReader = null Log.d(TAG, "✅ 释放ImageReader") } catch (e: Exception) { Log.e(TAG, "❌ 释放ImageReader失败", e) } } /** * 开始屏幕捕获(别名方法) */ fun startScreenCapture(): Boolean { return try { startCapture() true } catch (e: Exception) { Log.e(TAG, "❌ 启动屏幕捕获失败", e) false } } /** * 停止屏幕捕获(别名方法) */ fun stopScreenCapture() { try { stopCapture() } catch (e: Exception) { Log.e(TAG, "❌ 停止屏幕捕获失败", e) } } /** * 获取当前屏幕截图 */ fun getCurrentScreenshot(): Bitmap? { return try { if (mediaProjection == null) { Log.w(TAG, "MediaProjection未初始化") return null } captureWithMediaProjection() } catch (e: Exception) { Log.e(TAG, "获取屏幕截图失败", e) null } } /** * ✅ Android 15:检查是否正在重新创建VirtualDisplay */ fun isVirtualDisplayRecreating(): Boolean { return isVirtualDisplayRecreating } /** * ✅ 智能判断是否应该重新创建VirtualDisplay */ private fun shouldRecreateVirtualDisplay(): Boolean { val currentTime = System.currentTimeMillis() // 检查冷却期 if (virtualDisplayRecreationCooldown) { Log.i(TAG, "🛡️ VirtualDisplay重新创建处于冷却期,跳过") return false } // 检查最小间隔 if (currentTime - lastVirtualDisplayRecreationTime < minRecreationInterval) { val remainingTime = minRecreationInterval - (currentTime - lastVirtualDisplayRecreationTime) Log.i(TAG, "🛡️ VirtualDisplay重新创建间隔不足,还需等待${remainingTime}ms") return false } // 检查连续重新创建次数 if (consecutiveRecreationCount >= maxConsecutiveRecreations) { Log.w(TAG, "🛡️ VirtualDisplay连续重新创建次数过多(${consecutiveRecreationCount}/${maxConsecutiveRecreations}),进入冷却期") virtualDisplayRecreationCooldown = true // 5分钟后重置冷却期 serviceScope.launch { delay(300000) // 5分钟 virtualDisplayRecreationCooldown = false consecutiveRecreationCount = 0 Log.i(TAG, "🔄 VirtualDisplay重新创建冷却期结束,重置计数") } return false } // 检查失败次数是否达到阈值 if (consecutiveImageFailures < maxImageFailuresBeforeRecreation) { Log.i(TAG, "🛡️ 图像失败次数未达到阈值(${consecutiveImageFailures}/${maxImageFailuresBeforeRecreation}),暂不重新创建") return false } // 检查VirtualDisplay和ImageReader是否存在 if (virtualDisplay == null || imageReader == null) { Log.w(TAG, "⚠️ VirtualDisplay或ImageReader为null,需要重新创建") return true } // 所有条件都满足,可以重新创建 Log.i(TAG, "✅ 满足VirtualDisplay重新创建条件:失败${consecutiveImageFailures}次,上次重建${currentTime - lastVirtualDisplayRecreationTime}ms前") return true } /** * ✅ Android 15:触发权限申请流程 */ private fun triggerAndroid15PermissionRequest(reason: String) { try { Log.i(TAG, "🎯 [权限申请] Android 15触发权限申请:$reason") // 🚨 Android 11+特殊处理:统一使用无障碍截图API,跳过权限申请触发 if (Build.VERSION.SDK_INT >= 30) { Log.i(TAG, "📱 Android 11+设备:使用无障碍截图API,跳过权限申请触发") return } // 获取PermissionGranter实例 val permissionGranter = try { (service as? AccessibilityRemoteService)?.getPermissionGranter() } catch (e: Exception) { Log.e(TAG, "❌ 无法获取PermissionGranter实例", e) null } if (permissionGranter != null) { // ✅ 强制设置权限申请状态,绕过现有权限检查 Log.i(TAG, "🎯 [权限申请] 强制设置MediaProjection申请状态为true,准备处理权限弹窗") // 使用反射或直接设置,绕过setMediaProjectionRequesting中的权限检查 try { // 直接设置内部状态,绕过权限检查 val field = permissionGranter.javaClass.getDeclaredField("isRequestingMediaProjection") field.isAccessible = true field.setBoolean(permissionGranter, true) Log.i(TAG, "🎯 [权限申请] 已直接设置权限申请状态,绕过权限检查") } catch (e: Exception) { Log.w(TAG, "⚠️ 无法直接设置权限状态,使用常规方法", e) permissionGranter.setMediaProjectionRequesting(true) } Log.i(TAG, "🎯 [权限申请] Android 15权限申请流程已触发,等待自动处理权限弹窗") // 启动异步监控,在权限处理完成后重置状态 serviceScope.launch { delay(15000) // 15秒后自动重置状态,给权限处理更多时间 try { permissionGranter.setMediaProjectionRequesting(false) Log.i(TAG, "🎯 [权限申请] 15秒后自动重置权限申请状态") } catch (e: Exception) { Log.w(TAG, "重置权限申请状态失败", e) } } } else { Log.w(TAG, "⚠️ [权限申请] 无法获取PermissionGranter,跳过权限申请触发") } } catch (e: Exception) { Log.e(TAG, "❌ [权限申请] 触发Android 15权限申请失败", e) } } /** * 🚨 Android 11+专用:生成测试图像 */ private fun generateAndroid11TestImage(): Bitmap? { return try { val testBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(testBitmap) // 绘制Android 11+专用测试界面 val paint = android.graphics.Paint().apply { isAntiAlias = true } // 深蓝色背景,显示这是Android 11+特殊模式 canvas.drawColor(android.graphics.Color.rgb(13, 71, 161)) // 主标题 paint.apply { textSize = 65f color = android.graphics.Color.WHITE typeface = android.graphics.Typeface.DEFAULT_BOLD textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("📱 Android 11+ 设备", screenWidth / 2f, 120f, paint) // 副标题 paint.apply { textSize = 50f color = android.graphics.Color.rgb(100, 181, 246) typeface = android.graphics.Typeface.DEFAULT } canvas.drawText("无障碍截图模式", screenWidth / 2f, 190f, paint) // 状态信息 paint.apply { textSize = 40f color = android.graphics.Color.WHITE textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("• 已跳过MediaProjection权限", screenWidth / 2f, 280f, paint) canvas.drawText("• 正在尝试无障碍截图API", screenWidth / 2f, 340f, paint) canvas.drawText("• 连接功能正常可用", screenWidth / 2f, 400f, paint) // 详细技术信息 paint.apply { textSize = 35f color = android.graphics.Color.rgb(187, 222, 251) textAlign = android.graphics.Paint.Align.LEFT } val leftMargin = 50f canvas.drawText("分辨率: ${screenWidth} × ${screenHeight}", leftMargin, 500f, paint) canvas.drawText("API版本: Android ${Build.VERSION.SDK_INT}", leftMargin, 550f, paint) canvas.drawText("截图方式: AccessibilityService", leftMargin, 600f, paint) canvas.drawText("权限状态: 已绕过MediaProjection", leftMargin, 650f, paint) // 动态状态指示器 val time = System.currentTimeMillis() val frameCount = (time / 200) % 1000 paint.apply { textSize = 30f color = android.graphics.Color.rgb(255, 193, 7) textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("尝试获取真实屏幕中...", screenWidth / 2f, 730f, paint) // 动态进度条 val progressWidth = screenWidth - 100f val progressHeight = 20f val progressX = 50f val progressY = 760f // 进度条背景 paint.apply { color = android.graphics.Color.rgb(33, 37, 41) style = android.graphics.Paint.Style.FILL } canvas.drawRect(progressX, progressY, progressX + progressWidth, progressY + progressHeight, paint) // 动态进度 val progress = ((time / 50) % progressWidth).toFloat() paint.apply { color = android.graphics.Color.rgb(0, 200, 83) } canvas.drawRect(progressX, progressY, progressX + progress, progressY + progressHeight, paint) // 帧数计数器 paint.apply { textSize = 25f color = android.graphics.Color.WHITE textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("帧: $frameCount", screenWidth / 2f, 820f, paint) // 时间戳 paint.apply { textSize = 25f color = android.graphics.Color.GRAY textAlign = android.graphics.Paint.Align.CENTER } val currentTime = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date()) canvas.drawText(currentTime, screenWidth / 2f, 860f, paint) // 旋转图标 - 表示正在处理 val centerX = screenWidth / 2f val centerY = 950f val iconRadius = 30f val rotation = (time / 50f) % 360f canvas.save() canvas.rotate(rotation, centerX, centerY) paint.apply { color = android.graphics.Color.rgb(255, 193, 7) strokeWidth = 6f style = android.graphics.Paint.Style.STROKE } canvas.drawCircle(centerX, centerY, iconRadius, paint) // 绘制旋转的点 paint.style = android.graphics.Paint.Style.FILL for (i in 0 until 8) { val angle = (i * 45f + rotation) * Math.PI / 180 val dotX = centerX + (iconRadius + 15) * Math.cos(angle).toFloat() val dotY = centerY + (iconRadius + 15) * Math.sin(angle).toFloat() val size = if (i % 2 == 0) 8f else 4f canvas.drawCircle(dotX, dotY, size, paint) } canvas.restore() // 说明文字 paint.apply { textSize = 28f color = android.graphics.Color.rgb(158, 158, 158) textAlign = android.graphics.Paint.Align.CENTER } canvas.drawText("如果看到此画面,说明无障碍截图权限", screenWidth / 2f, screenHeight - 120f, paint) canvas.drawText("可能需要额外配置或系统重启", screenWidth / 2f, screenHeight - 80f, paint) Log.d(TAG, "生成Android 11+专用测试图像: ${screenWidth}x${screenHeight}") testBitmap } catch (e: Exception) { Log.e(TAG, "生成Android 11+测试图像失败", e) // 回退到简单测试图像 generateTestImage() } } }