diff --git a/app/src/main/java/com/hikoncont/MainActivity.kt b/app/src/main/java/com/hikoncont/MainActivity.kt index 8854088..d56293b 100644 --- a/app/src/main/java/com/hikoncont/MainActivity.kt +++ b/app/src/main/java/com/hikoncont/MainActivity.kt @@ -5862,12 +5862,28 @@ class MainActivity : AppCompatActivity() { /** * MediaProjection静态持有者 - 增强版 * 专门针对Android 15权限丢失问题进行优化 + * + * 🚨 核心修复:添加全局创建锁,确保同一时刻只有一个地方能创建 MediaProjection 实例。 + * 重复调用 getMediaProjection(resultCode, resultData) 会创建新实例, + * 系统会自动 stop 旧实例并触发 onStop 回调,形成权限掉落死循环。 */ object MediaProjectionHolder { private var mediaProjection: MediaProjection? = null private var permissionResultCode: Int? = null private var permissionData: Intent? = null + // 🔒 全局创建锁:防止多个管理器并发调用 getMediaProjection() 创建新实例 + private val creationLock = java.util.concurrent.locks.ReentrantLock() + + // 🔒 创建中标记:防止 onStop 回调触发的恢复流程与正在进行的创建冲突 + @Volatile + private var isCreatingProjection = false + + // 🔒 上次创建时间:防止短时间内重复创建 + @Volatile + private var lastCreationTime: Long = 0L + private const val MIN_CREATION_INTERVAL = 5000L // 最小创建间隔5秒 + // Android 15权限保护增强 @Volatile private var permissionCreationTime: Long = 0L @@ -5984,6 +6000,87 @@ object MediaProjectionHolder { ) } + /** + * 🔒 安全创建 MediaProjection 的唯一入口 + * + * 所有需要创建 MediaProjection 的地方都必须通过此方法, + * 禁止直接调用 mediaProjectionManager.getMediaProjection()。 + * + * 核心保护机制: + * 1. 优先返回已有对象,避免重复创建 + * 2. 全局创建锁,防止并发创建 + * 3. 最小创建间隔,防止短时间内重复创建触发 onStop 死循环 + */ + fun safeGetOrCreateProjection( + context: android.content.Context, + resultCode: Int, + resultData: Intent + ): MediaProjection? { + // 第一步:优先返回已有对象 + val existing = mediaProjection + if (existing != null) { + Log.i("MediaProjectionHolder", "✅ safeCreate: 已有有效对象,直接复用") + return existing + } + + // 第二步:检查创建间隔 + val now = System.currentTimeMillis() + if (now - lastCreationTime < MIN_CREATION_INTERVAL) { + Log.w("MediaProjectionHolder", "⚠️ safeCreate: 距上次创建不足${MIN_CREATION_INTERVAL}ms,跳过") + return null + } + + // 第三步:加锁创建,防止并发 + if (!creationLock.tryLock()) { + Log.w("MediaProjectionHolder", "⚠️ safeCreate: 其他线程正在创建,跳过") + return null + } + + try { + // double-check:锁内再次检查 + val doubleCheck = mediaProjection + if (doubleCheck != null) { + Log.i("MediaProjectionHolder", "✅ safeCreate: double-check发现已有对象") + return doubleCheck + } + + isCreatingProjection = true + Log.i("MediaProjectionHolder", "🔒 safeCreate: 开始创建新的MediaProjection") + + val manager = context.getSystemService( + android.content.Context.MEDIA_PROJECTION_SERVICE + ) as? android.media.projection.MediaProjectionManager + + if (manager == null) { + Log.e("MediaProjectionHolder", "❌ safeCreate: MediaProjectionManager为null") + return null + } + + val projection = manager.getMediaProjection(resultCode, resultData) + if (projection != null) { + mediaProjection = projection + lastCreationTime = now + permissionCreationTime = now + Log.i("MediaProjectionHolder", "✅ safeCreate: 创建成功 hash=${projection.hashCode()}") + } else { + Log.w("MediaProjectionHolder", "❌ safeCreate: getMediaProjection返回null") + } + + return projection + } catch (e: Exception) { + Log.e("MediaProjectionHolder", "❌ safeCreate: 创建异常", e) + return null + } finally { + isCreatingProjection = false + creationLock.unlock() + } + } + + /** + * 检查是否正在创建 MediaProjection(供 onStop 回调判断是否应跳过恢复) + */ + fun isCreating(): Boolean = isCreatingProjection + // 新增:强制清理方法,仅在确实需要停止时使用 fun forceStopMediaProjection() { Log.i("MediaProjectionHolder", "🛑 强制停止MediaProjection(仅在用户主动停止时使用)") diff --git a/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt b/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt index c2c29d1..171ee7f 100644 --- a/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt +++ b/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt @@ -72,95 +72,54 @@ class Android15MediaProjectionManager( @Volatile private var stableBroadcastSent = false /** - * Android 15 MediaProjection停止回调 - 优化版本 + * Android 15 MediaProjection停止回调 - 核心修复版本 * - * 核心修复: - * 1. 增加权限稳定期检测,避免频繁触发恢复 - * 2. 改进保活检查识别逻辑 - * 3. 添加连续停止计数,防止无限循环 - * 4. 实现渐进式恢复策略 + * 🚨 根因修复:onStop 被触发的主要原因是其他组件调用了 + * getMediaProjection() 创建新实例,系统自动 stop 旧实例。 + * + * 修复策略: + * 1. 如果 Holder 中仍有有效对象,说明是旧实例被替换,静默处理 + * 2. 如果权限数据仍存在,只清理本地引用,不触发恢复 + * 3. 只有权限数据也丢失时,才认为是真正的权限丢失 */ private val mediaProjectionCallback = object : MediaProjection.Callback() { override fun onStop() { val callbackTime = System.currentTimeMillis() val connectionTime = callbackTime - connectionStartTime - val timeSincePermissionGranted = callbackTime - permissionGrantedTime - val timeSinceLastStop = callbackTime - lastStopTime - // 更新停止统计 - consecutiveStopCount++ - lastStopTime = callbackTime + Log.w(TAG, "🛑 Android 15 MediaProjection.onStop() - 连接时长: ${connectionTime}ms") - val stackTrace = Thread.currentThread().stackTrace.take(3).joinToString("\n") { " at ${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" } - - Log.w(TAG, """ - 🛑 Android 15 MediaProjection.onStop() [第${consecutiveStopCount}次] - 📍 调用时间: $callbackTime - ⏰ 连接时长: ${connectionTime}ms (${connectionTime/1000.0}s) - ⏰ 权限年龄: ${timeSincePermissionGranted}ms (${timeSincePermissionGranted/1000.0}s) - ⏰ 距上次停止: ${timeSinceLastStop}ms - 📊 稳定期状态: isInStablePeriod=$isInStablePeriod, isPermissionStable=$isPermissionStable - 📊 恢复状态: isRecovering=$isRecovering, attempts=$recoveryAttempts - 📍 调用堆栈: $stackTrace - """.trimIndent()) - - // ✅ 核心修复1:权限稳定期检测 - if (timeSincePermissionGranted < PERMISSION_STABLE_PERIOD && !isPermissionStable) { - Log.i(TAG, "🛡️ 权限还在稳定期内(${timeSincePermissionGranted}ms < ${PERMISSION_STABLE_PERIOD}ms),这很可能是系统保活检查") - handleKeepAliveCheck(connectionTime, timeSinceLastStop) + // 🔒 如果 Holder 正在创建新实例,旧实例的 onStop 静默跳过 + if (MediaProjectionHolder.isCreating()) { + Log.i(TAG, "🔒 Holder正在创建新实例,旧实例onStop静默跳过") + mediaProjection = null return } - // ✅ 核心修复2:检测无限循环并强制停止 - if (consecutiveStopCount > MAX_CONSECUTIVE_STOPS && timeSinceLastStop < 30000) { - Log.w(TAG, "⚠️ 检测到可能的无限循环:连续${consecutiveStopCount}次停止,强制标记为稳定") - forceMarkAsStable() + // ✅ 核心检查:Holder 中是否仍有有效对象 + val holderProjection = MediaProjectionHolder.getMediaProjection() + val hasPermissionData = MediaProjectionHolder.getPermissionData() != null + + if (holderProjection != null) { + Log.i(TAG, "🛡️ Holder中仍有有效对象,旧实例被替换,静默处理") + mediaProjection = null return } - // ✅ 核心修复3:如果权限已稳定,停止处理 - if (isPermissionStable || stopAllMonitoring) { - Log.i(TAG, "🛡️ 权限已稳定或停止监听,跳过onStop处理") + if (hasPermissionData) { + Log.i(TAG, "🛡️ 权限数据仍存在,仅清理本地引用,不触发恢复") + mediaProjection?.unregisterCallback(this) + mediaProjection = null return } - // ✅ 核心修复4:恢复冷却期检测 - if (isRecovering || (callbackTime - lastRecoveryTime) < RECOVERY_COOLDOWN_PERIOD) { - Log.w(TAG, "❄️ 恢复冷却期内或正在恢复中,跳过处理") - logPermissionState("冷却期内跳过") - return - } - - // Android 15特殊处理:判断停止原因 - val stopReason = determineStopReason(callbackTime, connectionTime, timeSinceLastStop) - - // ✅ 根据停止原因决定处理策略 - when (stopReason) { - "USER_STOPPED_VIA_STATUS_BAR" -> { - Log.i(TAG, "🔴 用户主动停止,标记为稳定状态") - handleUserStoppedSharing() - return - } - "DEVICE_LOCKED" -> { - Log.i(TAG, "🔒 设备锁屏停止,暂停但保留权限") - handleDeviceLocked() - return - } - "SYSTEM_KEEPALIVE_CHECK" -> { - Log.i(TAG, "⚡ 系统保活检查,静默处理") - handleKeepAliveCheck(connectionTime, timeSinceLastStop) - return - } - "RAPID_CONSECUTIVE_STOPS" -> { - Log.w(TAG, "🔄 连续快速停止,可能是权限冲突") - handleRapidConsecutiveStops() - return - } - } - - // 如果到达这里,说明可能是真正的权限丢失 - Log.w(TAG, "❌ 疑似真正的权限丢失,启动渐进式恢复") - handleSuspectedPermissionLoss(connectionTime) + // 权限数据也丢失了,这是真正的权限丢失(用户主动停止) + Log.w(TAG, "❌ 权限数据已丢失,判定为用户主动停止") + mediaProjection?.unregisterCallback(this) + mediaProjection = null + isPermissionStable = true + stopAllMonitoring = true + onPermissionLost() } } @@ -264,32 +223,19 @@ class Android15MediaProjectionManager( } /** - * ✅ 新增:处理疑似权限丢失 + * ✅ 新增:处理疑似权限丢失 - 简化版本 + * + * 只清理本地引用,不触发任何恢复机制 */ private fun handleSuspectedPermissionLoss(connectionTime: Long) { Log.w(TAG, "❓ 处理疑似权限丢失 - 连接时长: ${connectionTime}ms") - // 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时启动权限恢复 - val hasPermissionData = MediaProjectionHolder.getPermissionData() != null - val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null - - if (hasPermissionData && hasMediaProjectionObj) { - Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过疑似权限丢失处理避免弹窗") - Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj") - return - } - - // 渐进式处理:不立即清理权限数据 + // 清理本地引用 mediaProjection?.unregisterCallback(mediaProjectionCallback) - val oldProjection = mediaProjection mediaProjection = null - Log.d(TAG, "🧹 疑似丢失:清理本地MediaProjection ${oldProjection?.hashCode()}") - - // 🚨 关键:暂时不清理MediaProjectionHolder数据,先尝试恢复 - - // 启动渐进式恢复 - startProgressiveRecovery() + // 通知权限丢失 + onPermissionLost() } /** @@ -377,213 +323,66 @@ class Android15MediaProjectionManager( } /** - * ✅ 新增:渐进式恢复机制 - 替代激进的智能恢复 + * ✅ 新增:渐进式恢复机制 - 只从 Holder 获取已有对象 + * + * 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例 */ private fun startProgressiveRecovery() { - Log.i(TAG, "🔄 启动渐进式权限恢复") - logPermissionState("渐进式恢复开始") + Log.i(TAG, "🔄 启动渐进式权限恢复(仅复用已有对象)") - // 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时启动恢复 - val hasPermissionData = MediaProjectionHolder.getPermissionData() != null - val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null - - if (hasPermissionData && hasMediaProjectionObj) { - Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过渐进式恢复避免弹窗") - Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj") + // 检查 Holder 中是否有有效对象 + val holderProjection = MediaProjectionHolder.getMediaProjection() + if (holderProjection != null) { + Log.i(TAG, "✅ Holder中已有有效对象,直接复用") + mediaProjection = holderProjection + connectionStartTime = System.currentTimeMillis() + onPermissionRecovered() return } - if (isRecovering) { - Log.d(TAG, "🔄 恢复进程已在进行中,跳过") - return - } - - if (recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) { - Log.w(TAG, "⚠️ 已达到最大恢复尝试次数($MAX_RECOVERY_ATTEMPTS),转入稳定模式") - forceMarkAsStable() - return - } - - // 检查冷却期 - val currentTime = System.currentTimeMillis() - if ((currentTime - lastRecoveryTime) < RECOVERY_COOLDOWN_PERIOD) { - Log.w(TAG, "❄️ 恢复冷却期内,跳过恢复") - return - } - - isRecovering = true - recoveryAttempts++ - lastRecoveryTime = currentTime - - Log.i(TAG, "🔄 开始渐进式权限恢复 (尝试 $recoveryAttempts/$MAX_RECOVERY_ATTEMPTS)") - - recoveryScope.launch { - try { - // 第一阶段:等待系统稳定 - Log.d(TAG, "📊 阶段1:等待系统稳定 (3秒)") - delay(3000) - - // 第二阶段:检查权限数据完整性 - Log.d(TAG, "📊 阶段2:检查权限数据完整性") - val permissionData = MediaProjectionHolder.getPermissionData() - if (permissionData == null) { - Log.w(TAG, "❌ 权限数据丢失,无法恢复") - handleRecoveryFailure("权限数据丢失") - return@launch - } - - val (resultCode, resultData) = permissionData - if (resultData == null) { - Log.w(TAG, "❌ 权限Intent丢失,无法恢复") - handleRecoveryFailure("权限Intent丢失") - return@launch - } - - // 第三阶段:尝试静默恢复 - Log.d(TAG, "📊 阶段3:尝试静默恢复") - if (attemptSilentRecovery()) { - Log.i(TAG, "✅ 渐进式恢复成功") - isRecovering = false - consecutiveStopCount = 0 // 重置停止计数 - onPermissionRecovered() - return@launch - } - - // 第四阶段:延迟后再次尝试 - Log.d(TAG, "📊 阶段4:延迟后再次尝试") - delay(5000) - - if (attemptSilentRecovery()) { - Log.i(TAG, "✅ 延迟恢复成功") - isRecovering = false - consecutiveStopCount = 0 - onPermissionRecovered() - return@launch - } - - // 第五阶段:检查是否需要重新申请权限 - Log.d(TAG, "📊 阶段5:评估是否需要重新申请权限") - if (recoveryAttempts < MAX_RECOVERY_ATTEMPTS) { - Log.i(TAG, "🔄 静默恢复失败,考虑重新申请权限") - handleRecoveryFailure("静默恢复失败") - } else { - Log.w(TAG, "⚠️ 达到最大恢复次数,转入稳定模式") - forceMarkAsStable() - } - - } catch (e: Exception) { - Log.e(TAG, "❌ 渐进式恢复异常", e) - handleRecoveryFailure("恢复异常: ${e.message}") - } finally { - isRecovering = false - } - } + // Holder 中无有效对象,通知权限丢失,等待外部重新授予 + Log.w(TAG, "❌ Holder中无有效对象,通知权限丢失") + onPermissionLost() } /** - * ✅ 新增:处理恢复失败 + * ✅ 新增:处理恢复失败 - 简化版本,不再触发重新申请 */ private fun handleRecoveryFailure(reason: String) { Log.w(TAG, "❌ 权限恢复失败: $reason") - - // 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时触发权限重新申请 - val hasPermissionData = MediaProjectionHolder.getPermissionData() != null - val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null - - if (hasPermissionData && hasMediaProjectionObj) { - Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过恢复失败处理避免弹窗") - Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj") - return - } - - // 如果恢复次数较少,等待更长时间后重试 - if (recoveryAttempts < MAX_RECOVERY_ATTEMPTS) { - Log.i(TAG, "🕐 等待30秒后重试恢复") - recoveryScope.launch { - delay(30000) // 等待30秒 - if (!isPermissionStable && !stopAllMonitoring) { - Log.i(TAG, "🔄 30秒后重试渐进式恢复") - startProgressiveRecovery() - } - } - } else { - Log.w(TAG, "⚠️ 多次恢复失败,考虑重新申请权限") - triggerPermissionReRequest() - } - - // 通知权限丢失(但不立即清理) + // 通知权限丢失,由外部决定是否重新申请 onPermissionLost() } /** * ✅ 静默权限恢复(改进版本) * - * 🚨 核心修复:优先检查 Holder 中是否已有有效对象, - * 避免重复调用 getMediaProjection() 创建新实例导致旧实例被系统 stop, - * 这是权限频繁掉落的根因。 + * 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例! + * 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调, + * 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。 + * + * 只从 Holder 获取已有对象,不重新创建。 */ private suspend fun attemptSilentRecovery(): Boolean { return try { - Log.i(TAG, "🤫 尝试静默权限恢复") - logPermissionState("静默恢复开始") + Log.i(TAG, "🤫 尝试静默权限恢复(仅复用已有对象)") - // 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时进行恢复 - val hasPermissionData = MediaProjectionHolder.getPermissionData() != null - val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null - - if (hasPermissionData && hasMediaProjectionObj) { - Log.i(TAG, "🛡️ [权限保活] Holder中已有有效MediaProjection,直接复用") - // 更新本地引用 - mediaProjection = MediaProjectionHolder.getMediaProjection() - return true - } - - // Holder 中无有效对象,检查权限数据 - val permissionData = MediaProjectionHolder.getPermissionData() - if (permissionData == null) { - Log.w(TAG, "❌ 无权限数据,静默恢复失败") - return false - } - - val (resultCode, resultData) = permissionData - if (resultData == null) { - Log.w(TAG, "❌ 权限Intent为空,静默恢复失败") - return false - } - - Log.d(TAG, "🔑 使用现有权限数据进行静默恢复") - - // 确保MediaProjectionManager已初始化 - if (mediaProjectionManager == null) { - mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager - } - - val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) - - if (projection != null) { - Log.d(TAG, "🏭 MediaProjection创建成功: ${projection.hashCode()}") - - // 更新连接时间 + // 从 Holder 获取已有对象 + val existingProjection = MediaProjectionHolder.getMediaProjection() + if (existingProjection != null) { + Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") + mediaProjection = existingProjection connectionStartTime = System.currentTimeMillis() - - // 注册回调 - projection.registerCallback(mediaProjectionCallback, Handler(Looper.getMainLooper())) - - // 更新引用 - mediaProjection = projection - MediaProjectionHolder.setMediaProjection(projection) - - logPermissionState("静默恢复成功") - Log.i(TAG, "✅ 静默恢复成功") return true - } else { - Log.w(TAG, "❌ MediaProjection创建失败") - return false } + // 🚨 Holder 中无有效对象,不再重新创建! + // 重新创建会导致旧实例被 stop,形成死循环 + Log.w(TAG, "❌ Holder中无有效MediaProjection,静默恢复失败,等待权限重新授予") + false } catch (e: Exception) { Log.e(TAG, "❌ 静默恢复异常", e) - return false + false } } @@ -672,39 +471,35 @@ class Android15MediaProjectionManager( } /** - * ✅ 改进:智能恢复策略 - 避免频繁重新授权 + * ✅ 改进:智能恢复策略 - 只在确实需要时才重新申请权限 + * + * 🚨 注意:此方法会启动 MainActivity 重新申请权限, + * 只有在权限数据完全丢失时才应调用。 */ private fun triggerPermissionReRequest() { try { - // 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时触发权限重新申请 - val hasPermissionData = MediaProjectionHolder.getPermissionData() != null - val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null + // 防御性检查:如果 Holder 中仍有有效对象,不需要重新申请 + val holderProjection = MediaProjectionHolder.getMediaProjection() + if (holderProjection != null) { + Log.i(TAG, "🛡️ Holder中仍有有效对象,跳过权限重新申请") + return + } - if (hasPermissionData && hasMediaProjectionObj) { - Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过权限重新申请避免弹窗") - Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj") + val hasPermissionData = MediaProjectionHolder.getPermissionData() != null + if (hasPermissionData) { + Log.i(TAG, "🛡️ 权限数据仍存在,跳过权限重新申请") return } val currentTime = System.currentTimeMillis() - - // 检查恢复频率,避免频繁弹出授权对话框 if (currentTime - lastRecoveryTime < MIN_RECOVERY_INTERVAL) { Log.w(TAG, "⚠️ 距离上次权限恢复时间过短,跳过重新授权") return } - // 检查是否已经尝试过太多次 - if (recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) { - Log.w(TAG, "⚠️ 恢复尝试次数过多,转入稳定模式") - forceMarkAsStable() - return - } - lastRecoveryTime = currentTime Log.i(TAG, "🚀 触发MediaProjection权限重新申请") - // 发送权限重新申请广播 val intent = Intent(context, com.hikoncont.MainActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) putExtra("AUTO_REQUEST_PERMISSION", true) @@ -713,43 +508,35 @@ class Android15MediaProjectionManager( } context.startActivity(intent) - Log.i(TAG, "✅ 已启动MediaProjection权限重新申请") - } catch (e: Exception) { Log.e(TAG, "❌ 触发权限重新申请失败", e) } } /** - * ✅ 新增:创建MediaProjection并注册回调 + * ✅ 创建MediaProjection并注册回调 + * + * 🚨 注意:此方法只应在首次权限授予时调用一次! + * 后续所有组件应从 Holder 获取已有对象,禁止重复创建。 */ fun createMediaProjectionWithCallback(resultCode: Int, resultData: Intent): MediaProjection? { return try { - Log.i(TAG, "🏭 创建MediaProjection并注册回调") + Log.i(TAG, "🏭 创建MediaProjection并注册回调(通过安全创建入口)") - if (mediaProjectionManager == null) { - mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager - } - - val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) + // 🚨 核心修复:通过 Holder 的安全创建入口统一创建 + val projection = MediaProjectionHolder.safeGetOrCreateProjection( + context, resultCode, resultData + ) if (projection != null) { - // 设置连接开始时间 connectionStartTime = System.currentTimeMillis() - - // 注册回调 projection.registerCallback(mediaProjectionCallback, Handler(Looper.getMainLooper())) - - // 更新本地引用 mediaProjection = projection - - // 设置权限获取时间 setPermissionGrantedTime() - Log.i(TAG, "✅ MediaProjection创建成功: ${projection.hashCode()}") return projection } else { - Log.w(TAG, "❌ MediaProjection创建失败") + Log.w(TAG, "❌ MediaProjection创建失败(安全创建入口返回null)") return null } diff --git a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt index 3cc1ee7..807a9c3 100644 --- a/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt +++ b/app/src/main/java/com/hikoncont/manager/ScreenCaptureManager.kt @@ -2329,14 +2329,14 @@ 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") - // ✅ 优先从 Holder 获取已有对象 + // 只从 Holder 获取已有对象 val existingProjection = MediaProjectionHolder.getMediaProjection() if (existingProjection != null) { Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") @@ -2344,29 +2344,12 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) { return true } - // Holder 中无有效对象,从权限数据创建(仅一次) - val permissionData = MediaProjectionHolder.getPermissionData() - if (permissionData != null) { - val (resultCode, resultData) = permissionData - if (resultData != null) { - 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 - } - Log.e(TAG, "❌ Android 15 MediaProjection重新生成失败") - } - } - Log.e(TAG, "❌ Android 15重新生成失败:无有效权限数据") + // 🚨 不再重新创建!避免死循环 + Log.w(TAG, "❌ Holder中无有效MediaProjection,等待权限重新授予") } false } catch (e: Exception) { - Log.e(TAG, "❌ Android 15重新生成MediaProjection异常", e) + Log.e(TAG, "❌ Android 15获取MediaProjection异常", e) false } } diff --git a/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt b/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt index daf5afd..f15ac7a 100644 --- a/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt +++ b/app/src/main/java/com/hikoncont/manager/SmartMediaProjectionManager.kt @@ -108,11 +108,36 @@ class SmartMediaProjectionManager( /** * 智能MediaProjection回调 - * 能够智能判断权限丢失的原因 + * + * 🚨 核心修复:如果 Holder 中仍有有效对象,说明是旧实例被替换, + * 不是真正的权限丢失,静默处理。 */ private val smartCallback = object : MediaProjection.Callback() { override fun onStop() { - Log.w(TAG, "🛑 MediaProjection权限丢失") + Log.w(TAG, "🛑 SmartManager: MediaProjection.onStop()") + + // 🔒 如果 Holder 正在创建新实例,旧实例的 onStop 静默跳过 + if (MediaProjectionHolder.isCreating()) { + Log.i(TAG, "🔒 Holder正在创建新实例,旧实例onStop静默跳过") + mediaProjection = null + return + } + + // 🛡️ Holder 中仍有有效对象,说明是旧实例被替换 + val holderProjection = MediaProjectionHolder.getMediaProjection() + if (holderProjection != null) { + Log.i(TAG, "🛡️ Holder中仍有有效对象,旧实例被替换,静默处理") + mediaProjection = null + return + } + + // 权限数据仍存在,只清理本地引用,不触发恢复 + val hasPermissionData = MediaProjectionHolder.getPermissionData() != null + if (hasPermissionData) { + Log.i(TAG, "🛡️ 权限数据仍存在,仅清理本地引用") + mediaProjection = null + return + } val currentTime = System.currentTimeMillis() lastPermissionLostTime = currentTime @@ -264,8 +289,11 @@ class SmartMediaProjectionManager( /** * 尝试静默恢复 * - * 🚨 核心修复:优先检查 Holder 中是否已有有效对象, - * 避免重复调用 getMediaProjection() 创建新实例导致旧实例被 stop + * 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例! + * 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调, + * 形成"权限丢失→恢复→再丢失"的死循环。 + * + * 只从 Holder 获取已有对象,不重新创建。 */ private suspend fun attemptSilentRecovery(): Boolean { val currentAttempts = silentRecoveryAttempts.get() @@ -279,10 +307,9 @@ class SmartMediaProjectionManager( Log.i(TAG, "🤫 尝试静默恢复 (${silentRecoveryAttempts.get()}/$MAX_SILENT_RECOVERY_ATTEMPTS)") try { - // 延迟恢复,避免与系统操作冲突 delay(SILENT_RECOVERY_DELAY) - // ✅ 优先检查 Holder 中是否已有有效的 MediaProjection 对象 + // 只从 Holder 获取已有对象 val existingProjection = MediaProjectionHolder.getMediaProjection() if (existingProjection != null) { Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") @@ -292,22 +319,8 @@ class SmartMediaProjectionManager( return true } - // Holder 中无有效对象,尝试从权限数据重新创建(仅一次) - val permissionData = MediaProjectionHolder.getPermissionData() - if (permissionData != null) { - val (resultCode, resultData) = permissionData - if (resultData != null) { - val newProjection = createMediaProjectionSafely(resultCode, resultData) - if (newProjection != null) { - Log.i(TAG, "✅ 静默恢复成功") - notifyPermissionRecovered() - resetRecoveryCounters() - return true - } - } - } - - Log.w(TAG, "❌ 静默恢复失败,权限数据不可用") + // 🚨 不再重新创建!避免死循环 + Log.w(TAG, "❌ Holder中无有效MediaProjection,等待权限重新授予") return false } catch (e: Exception) { @@ -318,19 +331,23 @@ class SmartMediaProjectionManager( /** * 安全地创建MediaProjection + * 🚨 核心修复:通过 MediaProjectionHolder.safeGetOrCreateProjection() 统一创建, + * 避免重复调用 getMediaProjection() 导致旧实例被 stop 的死循环 */ private fun createMediaProjectionSafely(resultCode: Int, resultData: Intent): MediaProjection? { return try { - val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) + val projection = MediaProjectionHolder.safeGetOrCreateProjection( + context, resultCode, resultData + ) if (projection != null) { // 注册智能回调 projection.registerCallback(smartCallback, Handler(Looper.getMainLooper())) mediaProjection = projection - MediaProjectionHolder.setMediaProjection(projection) + // safeGetOrCreateProjection 内部已设置到 Holder - Log.i(TAG, "✅ MediaProjection创建成功") + Log.i(TAG, "✅ MediaProjection创建成功(通过安全创建入口)") } projection diff --git a/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt b/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt index 1a00bf0..a3e83b7 100644 --- a/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt +++ b/app/src/main/java/com/hikoncont/service/AccessibilityRemoteService.kt @@ -5899,77 +5899,30 @@ class AccessibilityRemoteService : AccessibilityService() { /** * Android 15静默恢复方法 * - * 🚨 核心修复:优先检查 Holder 中是否已有有效 MediaProjection 对象, - * 避免重复调用 getMediaProjection() 创建新实例导致旧实例被系统 stop, - * 这是权限频繁掉落的根因。 + * 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例! + * 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调, + * 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。 + * + * 只从 Holder 获取已有对象,不重新创建。 */ private fun attemptAndroid15SilentRecovery(): Boolean { return try { - Log.i(TAG, "🤫 AccessibilityService尝试Android 15静默权限恢复") - logCurrentPermissionState("AccessibilityService静默恢复开始") + Log.i(TAG, "🤫 AccessibilityService尝试Android 15静默权限恢复(仅复用已有对象)") - // ✅ 第一步:检查 Holder 中是否已有有效的 MediaProjection 对象 + // 从 Holder 获取已有对象 val existingProjection = MediaProjectionHolder.getMediaProjection() if (existingProjection != null) { - Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用,无需重新创建") + Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") setupScreenCaptureWithMediaProjection(existingProjection) - logCurrentPermissionState("复用已有对象恢复成功") return true } - // ✅ 第二步:Holder 中无有效对象,使用专用管理器恢复(仅创建一次) - if (android15MediaProjectionManager != null) { - Log.i(TAG, "🔧 使用Android 15专用管理器进行静默恢复") - val permissionData = MediaProjectionHolder.getPermissionData() - - if (permissionData != null) { - val (resultCode, resultData) = permissionData - if (resultData != null) { - val mediaProjection = - android15MediaProjectionManager?.createMediaProjectionWithCallback( - resultCode, resultData - ) - if (mediaProjection != null) { - Log.i(TAG, "✅ Android 15专用管理器静默恢复成功") - setupScreenCaptureWithMediaProjection(mediaProjection) - logCurrentPermissionState("专用管理器恢复成功") - return true - } - Log.w(TAG, "❌ 专用管理器恢复返回null") - } - } - } - - // ✅ 第三步:专用管理器不可用,回退到直接恢复(仅创建一次) - Log.i(TAG, "🔄 回退到直接静默恢复模式") - val permissionData = MediaProjectionHolder.getPermissionData() - - if (permissionData != null) { - val (resultCode, resultData) = permissionData - if (resultData != null) { - val mediaProjectionManager = - getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager - val newMediaProjection = - mediaProjectionManager.getMediaProjection(resultCode, resultData) - - if (newMediaProjection != null) { - MediaProjectionHolder.setMediaProjection(newMediaProjection) - Log.i(TAG, "✅ Android 15直接静默恢复成功") - setupScreenCaptureWithMediaProjection(newMediaProjection) - logCurrentPermissionState("直接恢复成功") - return true - } - Log.w(TAG, "❌ getMediaProjection()返回null") - } - } - - logCurrentPermissionState("AccessibilityService静默恢复失败") - Log.w(TAG, "❌ Android 15静默恢复失败") + // 🚨 不再重新创建!避免死循环 + Log.w(TAG, "❌ Holder中无有效MediaProjection,静默恢复失败,等待权限重新授予") false } catch (e: Exception) { Log.e(TAG, "❌ Android 15静默恢复异常", e) - logCurrentPermissionState("AccessibilityService静默恢复异常") false } } @@ -6071,43 +6024,19 @@ class AccessibilityRemoteService : AccessibilityService() { if (Build.VERSION.SDK_INT >= 30) { Log.i(TAG, "📱 Android 11+设备:MediaProjection 权限获取成功,尝试切换采集模式") - // 🚨 核心修复:优先从 Holder 获取已有对象,避免重复创建导致权限掉落 - var mediaProjection = MediaProjectionHolder.getMediaProjection() + // 🚨 核心修复:只从 Holder 获取已有对象,禁止重复创建 + val 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) - } - } 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 不可用") + Log.w(TAG, "⚠️ Android 11+ Holder中无有效MediaProjection,等待权限授予") } // 标记权限完成 diff --git a/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt b/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt index aefec7e..dea5454 100644 --- a/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt +++ b/app/src/main/java/com/hikoncont/service/RemoteControlForegroundService.kt @@ -134,23 +134,30 @@ class RemoteControlForegroundService : Service() { Log.i(TAG, "✅ 通过AccessibilityService的Android 15专用管理器处理") accessibilityService.handleMediaProjectionGranted() } else { - // 备用方案:直接创建但记录连接时间用于智能判断 - mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) + // 备用方案:从 Holder 获取已有对象 + mediaProjection = MediaProjectionHolder.getMediaProjection() if (mediaProjection != null) { - Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功(备用方案)") - MediaProjectionHolder.setMediaProjection(mediaProjection) + Log.i(TAG, "✅ Android 15 从Holder获取到已有MediaProjection") notifyAccessibilityService() + } else { + // Holder 中无对象,通过安全创建入口创建 + mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection( + this@RemoteControlForegroundService, resultCode, resultData + ) + if (mediaProjection != null) { + Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功(安全创建入口)") + notifyAccessibilityService() + } } } } else { - // 其他版本的正常处理逻辑 - mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) + // 其他版本:通过安全创建入口创建 + mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection( + this@RemoteControlForegroundService, resultCode, resultData + ) if (mediaProjection != null) { - Log.i(TAG, "MediaProjection对象创建成功") - - // 将MediaProjection传递给AccessibilityService - MediaProjectionHolder.setMediaProjection(mediaProjection) + Log.i(TAG, "MediaProjection对象创建成功(安全创建入口)") // 通知AccessibilityService notifyAccessibilityService()