diff --git a/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt b/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt index b0edfa9..c2c29d1 100644 --- a/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt +++ b/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt @@ -294,11 +294,22 @@ class Android15MediaProjectionManager( /** * ✅ 改进:判断MediaProjection停止原因 - 更精确的判断逻辑 + * + * 🚨 核心修复:增加对"重复创建导致旧实例被stop"场景的识别, + * 避免误判为"用户主动停止"而放弃恢复。 */ private fun determineStopReason(stopTime: Long, connectionDuration: Long, timeSinceLastStop: Long): String { val hasPermissionData = MediaProjectionHolder.getPermissionData() != null + val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null return when { + // ✅ 新增:Holder中仍有有效对象,说明是旧实例被新实例替换导致的stop + // 这不是真正的权限丢失,应该静默处理 + hasMediaProjectionObj && hasPermissionData -> { + Log.i(TAG, "🔄 Holder中仍有有效对象,可能是旧实例被替换") + "SYSTEM_KEEPALIVE_CHECK" + } + // 连续快速停止,可能是权限冲突 consecutiveStopCount >= 3 && timeSinceLastStop < 5000 -> { Log.w(TAG, "🔄 检测到连续快速停止(${consecutiveStopCount}次),间隔${timeSinceLastStop}ms") @@ -311,9 +322,9 @@ class Android15MediaProjectionManager( "SYSTEM_KEEPALIVE_CHECK" } - // 用户主动停止(状态栏点击) - connectionDuration > 30000 && hasPermissionData -> { - Log.i(TAG, "🔴 用户主动停止:连接时长${connectionDuration}ms > 30s") + // 用户主动停止(状态栏点击)- 仅在权限数据也丢失时才判定 + connectionDuration > 30000 && !hasPermissionData -> { + Log.i(TAG, "🔴 用户主动停止:连接时长${connectionDuration}ms > 30s,权限数据已清除") "USER_STOPPED_VIA_STATUS_BAR" } @@ -506,6 +517,10 @@ class Android15MediaProjectionManager( /** * ✅ 静默权限恢复(改进版本) + * + * 🚨 核心修复:优先检查 Holder 中是否已有有效对象, + * 避免重复调用 getMediaProjection() 创建新实例导致旧实例被系统 stop, + * 这是权限频繁掉落的根因。 */ private suspend fun attemptSilentRecovery(): Boolean { return try { @@ -517,11 +532,13 @@ class Android15MediaProjectionManager( val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null if (hasPermissionData && hasMediaProjectionObj) { - Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过恢复避免弹窗") - Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj") - return true // 返回true表示状态正常,无需恢复 + Log.i(TAG, "🛡️ [权限保活] Holder中已有有效MediaProjection,直接复用") + // 更新本地引用 + mediaProjection = MediaProjectionHolder.getMediaProjection() + return true } + // Holder 中无有效对象,检查权限数据 val permissionData = MediaProjectionHolder.getPermissionData() if (permissionData == null) { Log.w(TAG, "❌ 无权限数据,静默恢复失败") diff --git a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt index 9309ea2..3cc1ee7 100644 --- a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt +++ b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt @@ -562,36 +562,28 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * 确保 MediaProjection 可用,尝试获取或恢复 + * 确保 MediaProjection 可用 + * + * 🚨 核心修复:禁止重复调用 getMediaProjection(resultCode, resultData) + * 每次调用都会创建新实例,系统会自动 stop 旧实例,触发 onStop 回调, + * 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。 + * + * 只从 MediaProjectionHolder 获取已有对象,不重新创建。 */ private fun ensureMediaProjection(): Boolean { if (mediaProjection != null) return true + // 从全局 Holder 获取已有的 MediaProjection 对象 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) - } - } + if (mediaProjection != null) { + Log.i(TAG, "✅ 从 MediaProjectionHolder 获取到已有 MediaProjection") + return true } - Log.e(TAG, "❌ 无法获取 MediaProjection,权限可能未授予") + // 🚨 不再重复调用 getMediaProjection() 创建新实例 + // 如果 Holder 中没有有效对象,说明权限确实未授予或已过期 + // 应由 MainActivity 的权限申请流程统一创建 + Log.w(TAG, "⚠️ MediaProjectionHolder 中无有效 MediaProjection,等待权限授予") return false } @@ -940,44 +932,21 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { if (mediaProjection == null) { mediaProjection = MediaProjectionHolder.getMediaProjection() if (mediaProjection == null) { - Log.w(TAG, "MediaProjection未初始化,检查权限数据") - val permissionData = MediaProjectionHolder.getPermissionData() - Log.w(TAG, "权限数据状态: ${if (permissionData != null) "存在" else "不存在"}") + Log.w(TAG, "MediaProjection未初始化,Holder中无有效对象") - // 尝试重新创建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失败") - } - } - } + // 🚨 核心修复:不再重复调用 getMediaProjection() 创建新实例 + // 重复创建会导致系统 stop 旧实例,触发 onStop 回调死循环 + // 权限应由 MainActivity 统一申请 - 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 - } + // ✅ Android 15优化:直接报告权限不可用 + if (android.os.Build.VERSION.SDK_INT >= 35) { + Log.w(TAG, "⚠️ Android 15 MediaProjection为null,等待权限重新授予") + triggerPermissionRecovery() + return null + } else { + Log.e(TAG, "❌ MediaProjection权限丢失,触发自动权限恢复") + triggerPermissionRecovery() + return null } } } @@ -2143,46 +2112,38 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { } /** - * ✅ 触发智能权限恢复机制 - 使用智能权限管理器 + * ✅ 触发智能权限恢复机制 + * + * 🚨 核心修复:优先从 Holder 获取已有对象, + * 避免通过 SmartManager 重复创建新实例导致旧实例被 stop */ private fun triggerPermissionRecovery() { try { - Log.i(TAG, "🧠 尝试使用智能权限管理器进行权限恢复") + Log.i(TAG, "🧠 尝试权限恢复") - // 首先尝试智能权限管理器 + // ✅ 第一步:从 Holder 获取已有对象 + val holderProjection = com.hikoncont.MediaProjectionHolder.getMediaProjection() + if (holderProjection != null) { + 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,直接使用") 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, "⚠️ 智能权限恢复失败,回退到传统恢复机制") + // ✅ 第三步:都没有有效对象,回退到传统恢复(重新申请权限) + Log.w(TAG, "⚠️ 无有效MediaProjection对象,回退到传统恢复机制") triggerTraditionalPermissionRecovery() } catch (e: Exception) { - Log.e(TAG, "❌ 智能权限恢复失败,回退到传统方式", e) + Log.e(TAG, "❌ 权限恢复失败,回退到传统方式", e) triggerTraditionalPermissionRecovery() } } @@ -2367,36 +2328,41 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { /** * Android 15:重新生成MediaProjection以解决单次令牌限制 + * + * 🚨 核心修复:优先从 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,直接复用") + mediaProjection = existingProjection + return true + } + + // Holder 中无有效对象,从权限数据创建(仅一次) 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") + Log.e(TAG, "❌ Android 15 MediaProjection重新生成失败") } - } else { - Log.e(TAG, "❌ Android 15重新生成失败:无权限数据") } + Log.e(TAG, "❌ Android 15重新生成失败:无有效权限数据") } false } catch (e: Exception) { diff --git a/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt b/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt index 7a37de9..daf5afd 100644 --- a/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt +++ b/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt @@ -263,6 +263,9 @@ class SmartMediaProjectionManager( /** * 尝试静默恢复 + * + * 🚨 核心修复:优先检查 Holder 中是否已有有效对象, + * 避免重复调用 getMediaProjection() 创建新实例导致旧实例被 stop */ private suspend fun attemptSilentRecovery(): Boolean { val currentAttempts = silentRecoveryAttempts.get() @@ -279,7 +282,17 @@ class SmartMediaProjectionManager( // 延迟恢复,避免与系统操作冲突 delay(SILENT_RECOVERY_DELAY) - // 检查权限数据是否仍然有效 + // ✅ 优先检查 Holder 中是否已有有效的 MediaProjection 对象 + val existingProjection = MediaProjectionHolder.getMediaProjection() + if (existingProjection != null) { + Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") + mediaProjection = existingProjection + notifyPermissionRecovered() + resetRecoveryCounters() + return true + } + + // Holder 中无有效对象,尝试从权限数据重新创建(仅一次) val permissionData = MediaProjectionHolder.getPermissionData() if (permissionData != null) { val (resultCode, resultData) = permissionData diff --git a/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt b/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt index 3756fe0..1a00bf0 100644 --- a/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt +++ b/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt @@ -5898,107 +5898,77 @@ class AccessibilityRemoteService : AccessibilityService() { /** * Android 15静默恢复方法 + * + * 🚨 核心修复:优先检查 Holder 中是否已有有效 MediaProjection 对象, + * 避免重复调用 getMediaProjection() 创建新实例导致旧实例被系统 stop, + * 这是权限频繁掉落的根因。 */ private fun attemptAndroid15SilentRecovery(): Boolean { return try { - Log.i(TAG, "🤫🤫🤫 AccessibilityService尝试Android 15静默权限恢复 🤫🤫🤫") + Log.i(TAG, "🤫 AccessibilityService尝试Android 15静默权限恢复") logCurrentPermissionState("AccessibilityService静默恢复开始") - // 优先使用专用管理器恢复 + // ✅ 第一步:检查 Holder 中是否已有有效的 MediaProjection 对象 + val existingProjection = MediaProjectionHolder.getMediaProjection() + if (existingProjection != null) { + Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用,无需重新创建") + setupScreenCaptureWithMediaProjection(existingProjection) + logCurrentPermissionState("复用已有对象恢复成功") + return true + } + + // ✅ 第二步:Holder 中无有效对象,使用专用管理器恢复(仅创建一次) if (android15MediaProjectionManager != null) { Log.i(TAG, "🔧 使用Android 15专用管理器进行静默恢复") - val permissionData = MediaProjectionHolder.getPermissionData() - Log.d(TAG, "🔍 专用管理器模式 - 权限数据存在: ${permissionData != null}") if (permissionData != null) { val (resultCode, resultData) = permissionData - Log.d( - TAG, - "🔑 专用管理器权限数据: resultCode=$resultCode, Intent存在=${resultData != null}" - ) - if (resultData != null) { - Log.d(TAG, "🏭 调用专用管理器的createMediaProjectionWithCallback") val mediaProjection = android15MediaProjectionManager?.createMediaProjectionWithCallback( - resultCode, - resultData + resultCode, resultData ) - Log.d(TAG, "🏭 专用管理器恢复结果: ${mediaProjection?.hashCode()}") - if (mediaProjection != null) { - Log.i(TAG, "✅✅✅ Android 15专用管理器静默恢复成功 ✅✅✅") + Log.i(TAG, "✅ Android 15专用管理器静默恢复成功") setupScreenCaptureWithMediaProjection(mediaProjection) logCurrentPermissionState("专用管理器恢复成功") return true - } else { - Log.w(TAG, "❌ 专用管理器createMediaProjectionWithCallback返回null") } - } else { - Log.w(TAG, "❌ 专用管理器模式:权限数据中的Intent为null") + Log.w(TAG, "❌ 专用管理器恢复返回null") } - } else { - Log.w(TAG, "❌ 专用管理器模式:没有权限数据") } - } else { - Log.w(TAG, "⚠️ Android 15专用管理器未初始化,回退到直接恢复") } - // 回退到直接恢复 + // ✅ 第三步:专用管理器不可用,回退到直接恢复(仅创建一次) Log.i(TAG, "🔄 回退到直接静默恢复模式") val permissionData = MediaProjectionHolder.getPermissionData() - Log.d(TAG, "🔍 直接恢复模式 - 权限数据存在: ${permissionData != null}") if (permissionData != null) { val (resultCode, resultData) = permissionData - Log.d( - TAG, - "🔑 直接恢复权限数据: resultCode=$resultCode, Intent存在=${resultData != null}" - ) - if (resultData != null) { - Log.i(TAG, "🔑 使用现有权限数据进行直接静默恢复") - val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager - Log.d( - TAG, - "🏭 获取系统MediaProjectionManager: ${mediaProjectionManager.javaClass.name}" - ) - val newMediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData) - Log.d(TAG, "🏭 直接恢复结果: ${newMediaProjection?.hashCode()}") if (newMediaProjection != null) { - Log.d(TAG, "🔄 更新MediaProjectionHolder") MediaProjectionHolder.setMediaProjection(newMediaProjection) - - Log.i(TAG, "✅✅✅ Android 15直接静默恢复成功 ✅✅✅") - - // 设置到屏幕捕获管理器 - Log.d(TAG, "🎬 设置到屏幕捕获管理器") + Log.i(TAG, "✅ Android 15直接静默恢复成功") setupScreenCaptureWithMediaProjection(newMediaProjection) - logCurrentPermissionState("直接恢复成功") return true - } else { - Log.w(TAG, "❌ 系统MediaProjectionManager.getMediaProjection()返回null") } - } else { - Log.w(TAG, "❌ 直接恢复模式:权限数据中的Intent为null") + Log.w(TAG, "❌ getMediaProjection()返回null") } - } else { - Log.w(TAG, "❌ 直接恢复模式:没有权限数据") } logCurrentPermissionState("AccessibilityService静默恢复失败") - Log.w(TAG, "❌❌❌ Android 15静默恢复失败 ❌❌❌") + Log.w(TAG, "❌ Android 15静默恢复失败") false } catch (e: Exception) { - Log.e(TAG, "❌❌❌ Android 15静默恢复异常 ❌❌❌", e) + Log.e(TAG, "❌ Android 15静默恢复异常", e) logCurrentPermissionState("AccessibilityService静默恢复异常") false } @@ -6101,34 +6071,45 @@ class AccessibilityRemoteService : AccessibilityService() { if (Build.VERSION.SDK_INT >= 30) { Log.i(TAG, "📱 Android 11+设备:MediaProjection 权限获取成功,尝试切换采集模式") - val permissionData = MediaProjectionHolder.getPermissionData() - if (permissionData != null) { - val (resultCode, resultData) = permissionData - if (resultData != null) { - try { - val mediaProjectionManager = getSystemService( - android.content.Context.MEDIA_PROJECTION_SERVICE - ) as android.media.projection.MediaProjectionManager - val mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData) - if (mediaProjection != null) { - Log.i(TAG, "✅ Android 11+ MediaProjection 创建成功") - MediaProjectionHolder.setMediaProjection(mediaProjection) - setupScreenCaptureWithMediaProjection(mediaProjection) - - // 切换到 MediaProjection 模式以获得更高帧率 - if (::screenCaptureManager.isInitialized) { - screenCaptureManager.switchToMediaProjectionMode() - Log.i(TAG, "📱 Android 11+设备:已切换到 MediaProjection 模式") + // 🚨 核心修复:优先从 Holder 获取已有对象,避免重复创建导致权限掉落 + var mediaProjection = MediaProjectionHolder.getMediaProjection() + + if (mediaProjection != null) { + Log.i(TAG, "✅ Android 11+ 从Holder获取到已有MediaProjection,直接复用") + } else { + // Holder 中无对象,从权限数据创建(仅一次) + val permissionData = MediaProjectionHolder.getPermissionData() + if (permissionData != null) { + val (resultCode, resultData) = permissionData + if (resultData != null) { + try { + val mediaProjectionManager = getSystemService( + android.content.Context.MEDIA_PROJECTION_SERVICE + ) as android.media.projection.MediaProjectionManager + mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData) + if (mediaProjection != null) { + Log.i(TAG, "✅ Android 11+ MediaProjection 创建成功") + MediaProjectionHolder.setMediaProjection(mediaProjection) } - } else { - Log.w(TAG, "⚠️ Android 11+ MediaProjection 创建返回 null") + } catch (e: Exception) { + Log.e(TAG, "❌ Android 11+ 创建 MediaProjection 失败", e) } - } catch (e: Exception) { - Log.e(TAG, "❌ Android 11+ 创建 MediaProjection 失败", e) } } } + if (mediaProjection != null) { + setupScreenCaptureWithMediaProjection(mediaProjection) + + // 切换到 MediaProjection 模式以获得更高帧率 + if (::screenCaptureManager.isInitialized) { + screenCaptureManager.switchToMediaProjectionMode() + Log.i(TAG, "📱 Android 11+设备:已切换到 MediaProjection 模式") + } + } else { + Log.w(TAG, "⚠️ Android 11+ MediaProjection 不可用") + } + // 标记权限完成 permissionRequestInProgress = false allPermissionsCompleted = true diff --git a/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt b/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt index e0ffcd7..aefec7e 100644 --- a/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt +++ b/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt @@ -107,6 +107,15 @@ class RemoteControlForegroundService : Service() { // 启动前台服务 startForegroundService() + // ✅ 优先检查 Holder 中是否已有有效的 MediaProjection 对象 + val existingProjection = MediaProjectionHolder.getMediaProjection() + if (existingProjection != null) { + Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用,避免重复创建") + mediaProjection = existingProjection + notifyAccessibilityService() + return + } + // 从MediaProjectionHolder获取权限数据 val permissionData = MediaProjectionHolder.getPermissionData()