fix: 修复屏幕录制权限频繁掉落的根因问题

- MediaProjectionHolder添加全局创建锁safeGetOrCreateProjection()
- 所有创建点统一通过安全入口,禁止直接调用getMediaProjection()
- 重复创建新实例会导致系统stop旧实例触发onStop死循环
- Android15MediaProjectionManager/SmartMediaProjectionManager的onStop回调添加isCreating检查
- RemoteControlForegroundService备用方案改用安全创建入口
- AccessibilityRemoteService静默恢复改用安全创建入口
- 添加最小创建间隔5秒防止短时间内重复创建
This commit is contained in:
wdvipa
2026-02-15 00:34:27 +08:00
parent 39bc5b47a0
commit d163c6fd50
6 changed files with 266 additions and 446 deletions

View File

@@ -5862,12 +5862,28 @@ class MainActivity : AppCompatActivity() {
/** /**
* MediaProjection静态持有者 - 增强版 * MediaProjection静态持有者 - 增强版
* 专门针对Android 15权限丢失问题进行优化 * 专门针对Android 15权限丢失问题进行优化
*
* 🚨 核心修复:添加全局创建锁,确保同一时刻只有一个地方能创建 MediaProjection 实例。
* 重复调用 getMediaProjection(resultCode, resultData) 会创建新实例,
* 系统会自动 stop 旧实例并触发 onStop 回调,形成权限掉落死循环。
*/ */
object MediaProjectionHolder { object MediaProjectionHolder {
private var mediaProjection: MediaProjection? = null private var mediaProjection: MediaProjection? = null
private var permissionResultCode: Int? = null private var permissionResultCode: Int? = null
private var permissionData: Intent? = 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权限保护增强 // Android 15权限保护增强
@Volatile @Volatile
private var permissionCreationTime: Long = 0L 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() { fun forceStopMediaProjection() {
Log.i("MediaProjectionHolder", "🛑 强制停止MediaProjection仅在用户主动停止时使用") Log.i("MediaProjectionHolder", "🛑 强制停止MediaProjection仅在用户主动停止时使用")

View File

@@ -72,95 +72,54 @@ class Android15MediaProjectionManager(
@Volatile private var stableBroadcastSent = false @Volatile private var stableBroadcastSent = false
/** /**
* Android 15 MediaProjection停止回调 - 优化版本 * Android 15 MediaProjection停止回调 - 核心修复版本
* *
* 核心修复: * 🚨 根因修复onStop 被触发的主要原因是其他组件调用了
* 1. 增加权限稳定期检测,避免频繁触发恢复 * getMediaProjection() 创建新实例,系统自动 stop 旧实例。
* 2. 改进保活检查识别逻辑 *
* 3. 添加连续停止计数,防止无限循环 * 修复策略:
* 4. 实现渐进式恢复策略 * 1. 如果 Holder 中仍有有效对象,说明是旧实例被替换,静默处理
* 2. 如果权限数据仍存在,只清理本地引用,不触发恢复
* 3. 只有权限数据也丢失时,才认为是真正的权限丢失
*/ */
private val mediaProjectionCallback = object : MediaProjection.Callback() { private val mediaProjectionCallback = object : MediaProjection.Callback() {
override fun onStop() { override fun onStop() {
val callbackTime = System.currentTimeMillis() val callbackTime = System.currentTimeMillis()
val connectionTime = callbackTime - connectionStartTime val connectionTime = callbackTime - connectionStartTime
val timeSincePermissionGranted = callbackTime - permissionGrantedTime
val timeSinceLastStop = callbackTime - lastStopTime
// 更新停止统计 Log.w(TAG, "🛑 Android 15 MediaProjection.onStop() - 连接时长: ${connectionTime}ms")
consecutiveStopCount++
lastStopTime = callbackTime
val stackTrace = Thread.currentThread().stackTrace.take(3).joinToString("\n") { " at ${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" } // 🔒 如果 Holder 正在创建新实例,旧实例的 onStop 静默跳过
if (MediaProjectionHolder.isCreating()) {
Log.w(TAG, """ Log.i(TAG, "🔒 Holder正在创建新实例旧实例onStop静默跳过")
🛑 Android 15 MediaProjection.onStop() [第${consecutiveStopCount}次] mediaProjection = null
📍 调用时间: $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)
return return
} }
// ✅ 核心修复2检测无限循环并强制停止 // ✅ 核心检查Holder 中是否仍有有效对象
if (consecutiveStopCount > MAX_CONSECUTIVE_STOPS && timeSinceLastStop < 30000) { val holderProjection = MediaProjectionHolder.getMediaProjection()
Log.w(TAG, "⚠️ 检测到可能的无限循环:连续${consecutiveStopCount}次停止,强制标记为稳定") val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
forceMarkAsStable()
if (holderProjection != null) {
Log.i(TAG, "🛡️ Holder中仍有有效对象旧实例被替换静默处理")
mediaProjection = null
return return
} }
// ✅ 核心修复3如果权限已稳定停止处理 if (hasPermissionData) {
if (isPermissionStable || stopAllMonitoring) { Log.i(TAG, "🛡️ 权限数据仍存在,仅清理本地引用,不触发恢复")
Log.i(TAG, "🛡️ 权限已稳定或停止监听跳过onStop处理") mediaProjection?.unregisterCallback(this)
mediaProjection = null
return return
} }
// ✅ 核心修复4恢复冷却期检测 // 权限数据也丢失了,这是真正的权限丢失(用户主动停止)
if (isRecovering || (callbackTime - lastRecoveryTime) < RECOVERY_COOLDOWN_PERIOD) { Log.w(TAG, "❌ 权限数据已丢失,判定为用户主动停止")
Log.w(TAG, "❄️ 恢复冷却期内或正在恢复中,跳过处理") mediaProjection?.unregisterCallback(this)
logPermissionState("冷却期内跳过") mediaProjection = null
return isPermissionStable = true
} stopAllMonitoring = true
onPermissionLost()
// 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)
} }
} }
@@ -264,32 +223,19 @@ class Android15MediaProjectionManager(
} }
/** /**
* ✅ 新增:处理疑似权限丢失 * ✅ 新增:处理疑似权限丢失 - 简化版本
*
* 只清理本地引用,不触发任何恢复机制
*/ */
private fun handleSuspectedPermissionLoss(connectionTime: Long) { private fun handleSuspectedPermissionLoss(connectionTime: Long) {
Log.w(TAG, "❓ 处理疑似权限丢失 - 连接时长: ${connectionTime}ms") 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) mediaProjection?.unregisterCallback(mediaProjectionCallback)
val oldProjection = mediaProjection
mediaProjection = null mediaProjection = null
Log.d(TAG, "🧹 疑似丢失清理本地MediaProjection ${oldProjection?.hashCode()}") // 通知权限丢失
onPermissionLost()
// 🚨 关键暂时不清理MediaProjectionHolder数据先尝试恢复
// 启动渐进式恢复
startProgressiveRecovery()
} }
/** /**
@@ -377,213 +323,66 @@ class Android15MediaProjectionManager(
} }
/** /**
* ✅ 新增:渐进式恢复机制 - 替代激进的智能恢复 * ✅ 新增:渐进式恢复机制 - 只从 Holder 获取已有对象
*
* 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例
*/ */
private fun startProgressiveRecovery() { private fun startProgressiveRecovery() {
Log.i(TAG, "🔄 启动渐进式权限恢复") Log.i(TAG, "🔄 启动渐进式权限恢复(仅复用已有对象)")
logPermissionState("渐进式恢复开始")
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时启动恢复 // 检查 Holder 中是否有有效对象
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null val holderProjection = MediaProjectionHolder.getMediaProjection()
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null if (holderProjection != null) {
Log.i(TAG, "✅ Holder中已有有效对象直接复用")
if (hasPermissionData && hasMediaProjectionObj) { mediaProjection = holderProjection
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过渐进式恢复避免弹窗") connectionStartTime = System.currentTimeMillis()
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj")
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() onPermissionRecovered()
return@launch return
} }
// 第四阶段:延迟后再次尝试 // Holder 中无有效对象,通知权限丢失,等待外部重新授予
Log.d(TAG, "📊 阶段4延迟后再次尝试") Log.w(TAG, "❌ Holder中无有效对象通知权限丢失")
delay(5000) onPermissionLost()
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
}
}
} }
/** /**
* ✅ 新增:处理恢复失败 * ✅ 新增:处理恢复失败 - 简化版本,不再触发重新申请
*/ */
private fun handleRecoveryFailure(reason: String) { private fun handleRecoveryFailure(reason: String) {
Log.w(TAG, "❌ 权限恢复失败: $reason") 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() onPermissionLost()
} }
/** /**
* ✅ 静默权限恢复(改进版本) * ✅ 静默权限恢复(改进版本)
* *
* 🚨 核心修复:优先检查 Holder 中是否已有有效对象, * 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例!
* 避免重复调用 getMediaProjection() 创建新实例导致旧实例被系统 stop * 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调
* 这是权限频繁掉落的根因。 * 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。
*
* 只从 Holder 获取已有对象,不重新创建。
*/ */
private suspend fun attemptSilentRecovery(): Boolean { private suspend fun attemptSilentRecovery(): Boolean {
return try { return try {
Log.i(TAG, "🤫 尝试静默权限恢复") Log.i(TAG, "🤫 尝试静默权限恢复(仅复用已有对象)")
logPermissionState("静默恢复开始")
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时进行恢复 // 从 Holder 获取已有对象
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null val existingProjection = MediaProjectionHolder.getMediaProjection()
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null if (existingProjection != null) {
Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用")
if (hasPermissionData && hasMediaProjectionObj) { mediaProjection = existingProjection
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()}")
// 更新连接时间
connectionStartTime = System.currentTimeMillis() connectionStartTime = System.currentTimeMillis()
// 注册回调
projection.registerCallback(mediaProjectionCallback, Handler(Looper.getMainLooper()))
// 更新引用
mediaProjection = projection
MediaProjectionHolder.setMediaProjection(projection)
logPermissionState("静默恢复成功")
Log.i(TAG, "✅ 静默恢复成功")
return true return true
} else {
Log.w(TAG, "❌ MediaProjection创建失败")
return false
} }
// 🚨 Holder 中无有效对象,不再重新创建!
// 重新创建会导致旧实例被 stop形成死循环
Log.w(TAG, "❌ Holder中无有效MediaProjection静默恢复失败等待权限重新授予")
false
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "❌ 静默恢复异常", e) Log.e(TAG, "❌ 静默恢复异常", e)
return false false
} }
} }
@@ -672,39 +471,35 @@ class Android15MediaProjectionManager(
} }
/** /**
* ✅ 改进:智能恢复策略 - 避免频繁重新授权 * ✅ 改进:智能恢复策略 - 只在确实需要时才重新申请权限
*
* 🚨 注意:此方法会启动 MainActivity 重新申请权限,
* 只有在权限数据完全丢失时才应调用。
*/ */
private fun triggerPermissionReRequest() { private fun triggerPermissionReRequest() {
try { try {
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时触发权限重新申请 // 防御性检查:如果 Holder 中仍有有效对象,不需要重新申请
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null val holderProjection = MediaProjectionHolder.getMediaProjection()
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null if (holderProjection != null) {
Log.i(TAG, "🛡️ Holder中仍有有效对象跳过权限重新申请")
return
}
if (hasPermissionData && hasMediaProjectionObj) { val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过权限重新申请避免弹窗") if (hasPermissionData) {
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj") Log.i(TAG, "🛡️ 权限数据仍存在,跳过权限重新申请")
return return
} }
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
// 检查恢复频率,避免频繁弹出授权对话框
if (currentTime - lastRecoveryTime < MIN_RECOVERY_INTERVAL) { if (currentTime - lastRecoveryTime < MIN_RECOVERY_INTERVAL) {
Log.w(TAG, "⚠️ 距离上次权限恢复时间过短,跳过重新授权") Log.w(TAG, "⚠️ 距离上次权限恢复时间过短,跳过重新授权")
return return
} }
// 检查是否已经尝试过太多次
if (recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {
Log.w(TAG, "⚠️ 恢复尝试次数过多,转入稳定模式")
forceMarkAsStable()
return
}
lastRecoveryTime = currentTime lastRecoveryTime = currentTime
Log.i(TAG, "🚀 触发MediaProjection权限重新申请") Log.i(TAG, "🚀 触发MediaProjection权限重新申请")
// 发送权限重新申请广播
val intent = Intent(context, com.hikoncont.MainActivity::class.java).apply { val intent = Intent(context, com.hikoncont.MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
putExtra("AUTO_REQUEST_PERMISSION", true) putExtra("AUTO_REQUEST_PERMISSION", true)
@@ -713,43 +508,35 @@ class Android15MediaProjectionManager(
} }
context.startActivity(intent) context.startActivity(intent)
Log.i(TAG, "✅ 已启动MediaProjection权限重新申请")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "❌ 触发权限重新申请失败", e) Log.e(TAG, "❌ 触发权限重新申请失败", e)
} }
} }
/** /**
* ✅ 新增:创建MediaProjection并注册回调 * ✅ 创建MediaProjection并注册回调
*
* 🚨 注意:此方法只应在首次权限授予时调用一次!
* 后续所有组件应从 Holder 获取已有对象,禁止重复创建。
*/ */
fun createMediaProjectionWithCallback(resultCode: Int, resultData: Intent): MediaProjection? { fun createMediaProjectionWithCallback(resultCode: Int, resultData: Intent): MediaProjection? {
return try { return try {
Log.i(TAG, "🏭 创建MediaProjection并注册回调") Log.i(TAG, "🏭 创建MediaProjection并注册回调(通过安全创建入口)")
if (mediaProjectionManager == null) { // 🚨 核心修复:通过 Holder 的安全创建入口统一创建
mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager val projection = MediaProjectionHolder.safeGetOrCreateProjection(
} context, resultCode, resultData
)
val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
if (projection != null) { if (projection != null) {
// 设置连接开始时间
connectionStartTime = System.currentTimeMillis() connectionStartTime = System.currentTimeMillis()
// 注册回调
projection.registerCallback(mediaProjectionCallback, Handler(Looper.getMainLooper())) projection.registerCallback(mediaProjectionCallback, Handler(Looper.getMainLooper()))
// 更新本地引用
mediaProjection = projection mediaProjection = projection
// 设置权限获取时间
setPermissionGrantedTime() setPermissionGrantedTime()
Log.i(TAG, "✅ MediaProjection创建成功: ${projection.hashCode()}") Log.i(TAG, "✅ MediaProjection创建成功: ${projection.hashCode()}")
return projection return projection
} else { } else {
Log.w(TAG, "❌ MediaProjection创建失败") Log.w(TAG, "❌ MediaProjection创建失败安全创建入口返回null")
return null return null
} }

View File

@@ -2329,14 +2329,14 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
/** /**
* Android 15重新生成MediaProjection以解决单次令牌限制 * Android 15重新生成MediaProjection以解决单次令牌限制
* *
* 🚨 核心修复:优先从 Holder 获取已有对象,避免重复创建 * 🚨 核心修复:从 Holder 获取已有对象,禁止重复创建
*/ */
private fun regenerateMediaProjectionForAndroid15(): Boolean { private fun regenerateMediaProjectionForAndroid15(): Boolean {
return try { return try {
if (Build.VERSION.SDK_INT >= 35) { if (Build.VERSION.SDK_INT >= 35) {
Log.i(TAG, "🔄 Android 15尝试获取可用的MediaProjection") Log.i(TAG, "🔄 Android 15尝试获取可用的MediaProjection")
// ✅ 优先从 Holder 获取已有对象 // 从 Holder 获取已有对象
val existingProjection = MediaProjectionHolder.getMediaProjection() val existingProjection = MediaProjectionHolder.getMediaProjection()
if (existingProjection != null) { if (existingProjection != null) {
Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用") Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用")
@@ -2344,29 +2344,12 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
return true return true
} }
// Holder 中无有效对象,从权限数据创建(仅一次) // 🚨 不再重新创建!避免死循环
val permissionData = MediaProjectionHolder.getPermissionData() Log.w(TAG, "❌ Holder中无有效MediaProjection等待权限重新授予")
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重新生成失败无有效权限数据")
} }
false false
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "❌ Android 15重新生成MediaProjection异常", e) Log.e(TAG, "❌ Android 15获取MediaProjection异常", e)
false false
} }
} }

View File

@@ -108,11 +108,36 @@ class SmartMediaProjectionManager(
/** /**
* 智能MediaProjection回调 * 智能MediaProjection回调
* 能够智能判断权限丢失的原因 *
* 🚨 核心修复:如果 Holder 中仍有有效对象,说明是旧实例被替换,
* 不是真正的权限丢失,静默处理。
*/ */
private val smartCallback = object : MediaProjection.Callback() { private val smartCallback = object : MediaProjection.Callback() {
override fun onStop() { 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() val currentTime = System.currentTimeMillis()
lastPermissionLostTime = currentTime lastPermissionLostTime = currentTime
@@ -264,8 +289,11 @@ class SmartMediaProjectionManager(
/** /**
* 尝试静默恢复 * 尝试静默恢复
* *
* 🚨 核心修复:优先检查 Holder 中是否已有有效对象, * 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例!
* 避免重复调用 getMediaProjection() 创建新实例导致旧实例被 stop * 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调,
* 形成"权限丢失→恢复→再丢失"的死循环。
*
* 只从 Holder 获取已有对象,不重新创建。
*/ */
private suspend fun attemptSilentRecovery(): Boolean { private suspend fun attemptSilentRecovery(): Boolean {
val currentAttempts = silentRecoveryAttempts.get() val currentAttempts = silentRecoveryAttempts.get()
@@ -279,10 +307,9 @@ class SmartMediaProjectionManager(
Log.i(TAG, "🤫 尝试静默恢复 (${silentRecoveryAttempts.get()}/$MAX_SILENT_RECOVERY_ATTEMPTS)") Log.i(TAG, "🤫 尝试静默恢复 (${silentRecoveryAttempts.get()}/$MAX_SILENT_RECOVERY_ATTEMPTS)")
try { try {
// 延迟恢复,避免与系统操作冲突
delay(SILENT_RECOVERY_DELAY) delay(SILENT_RECOVERY_DELAY)
// ✅ 优先检查 Holder 中是否已有有效的 MediaProjection 对象 // 只从 Holder 获取已有对象
val existingProjection = MediaProjectionHolder.getMediaProjection() val existingProjection = MediaProjectionHolder.getMediaProjection()
if (existingProjection != null) { if (existingProjection != null) {
Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用") Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用")
@@ -292,22 +319,8 @@ class SmartMediaProjectionManager(
return true return true
} }
// Holder 中无有效对象,尝试从权限数据重新创建(仅一次) // 🚨 不再重新创建!避免死循环
val permissionData = MediaProjectionHolder.getPermissionData() Log.w(TAG, "❌ Holder中无有效MediaProjection等待权限重新授予")
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, "❌ 静默恢复失败,权限数据不可用")
return false return false
} catch (e: Exception) { } catch (e: Exception) {
@@ -318,19 +331,23 @@ class SmartMediaProjectionManager(
/** /**
* 安全地创建MediaProjection * 安全地创建MediaProjection
* 🚨 核心修复:通过 MediaProjectionHolder.safeGetOrCreateProjection() 统一创建,
* 避免重复调用 getMediaProjection() 导致旧实例被 stop 的死循环
*/ */
private fun createMediaProjectionSafely(resultCode: Int, resultData: Intent): MediaProjection? { private fun createMediaProjectionSafely(resultCode: Int, resultData: Intent): MediaProjection? {
return try { return try {
val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) val projection = MediaProjectionHolder.safeGetOrCreateProjection(
context, resultCode, resultData
)
if (projection != null) { if (projection != null) {
// 注册智能回调 // 注册智能回调
projection.registerCallback(smartCallback, Handler(Looper.getMainLooper())) projection.registerCallback(smartCallback, Handler(Looper.getMainLooper()))
mediaProjection = projection mediaProjection = projection
MediaProjectionHolder.setMediaProjection(projection) // safeGetOrCreateProjection 内部已设置到 Holder
Log.i(TAG, "✅ MediaProjection创建成功") Log.i(TAG, "✅ MediaProjection创建成功(通过安全创建入口)")
} }
projection projection

View File

@@ -5899,77 +5899,30 @@ class AccessibilityRemoteService : AccessibilityService() {
/** /**
* Android 15静默恢复方法 * Android 15静默恢复方法
* *
* 🚨 核心修复:优先检查 Holder 中是否已有有效 MediaProjection 对象, * 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例!
* 避免重复调用 getMediaProjection() 创建新实例导致旧实例被系统 stop * 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调
* 这是权限频繁掉落的根因。 * 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。
*
* 只从 Holder 获取已有对象,不重新创建。
*/ */
private fun attemptAndroid15SilentRecovery(): Boolean { private fun attemptAndroid15SilentRecovery(): Boolean {
return try { return try {
Log.i(TAG, "🤫 AccessibilityService尝试Android 15静默权限恢复") Log.i(TAG, "🤫 AccessibilityService尝试Android 15静默权限恢复(仅复用已有对象)")
logCurrentPermissionState("AccessibilityService静默恢复开始")
// ✅ 第一步:检查 Holder 中是否已有有效的 MediaProjection 对象 // 从 Holder 获取已有对象
val existingProjection = MediaProjectionHolder.getMediaProjection() val existingProjection = MediaProjectionHolder.getMediaProjection()
if (existingProjection != null) { if (existingProjection != null) {
Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用,无需重新创建") Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用")
setupScreenCaptureWithMediaProjection(existingProjection) setupScreenCaptureWithMediaProjection(existingProjection)
logCurrentPermissionState("复用已有对象恢复成功")
return true return true
} }
// ✅ 第二步Holder 中无有效对象,使用专用管理器恢复(仅创建一次) // 🚨 不再重新创建!避免死循环
if (android15MediaProjectionManager != null) { Log.w(TAG, "❌ Holder中无有效MediaProjection静默恢复失败等待权限重新授予")
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静默恢复失败")
false false
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "❌ Android 15静默恢复异常", e) Log.e(TAG, "❌ Android 15静默恢复异常", e)
logCurrentPermissionState("AccessibilityService静默恢复异常")
false false
} }
} }
@@ -6071,43 +6024,19 @@ class AccessibilityRemoteService : AccessibilityService() {
if (Build.VERSION.SDK_INT >= 30) { if (Build.VERSION.SDK_INT >= 30) {
Log.i(TAG, "📱 Android 11+设备MediaProjection 权限获取成功,尝试切换采集模式") Log.i(TAG, "📱 Android 11+设备MediaProjection 权限获取成功,尝试切换采集模式")
// 🚨 核心修复:优先从 Holder 获取已有对象,避免重复创建导致权限掉落 // 🚨 核心修复:从 Holder 获取已有对象,禁止重复创建
var mediaProjection = MediaProjectionHolder.getMediaProjection() val mediaProjection = MediaProjectionHolder.getMediaProjection()
if (mediaProjection != null) { if (mediaProjection != null) {
Log.i(TAG, "✅ Android 11+ 从Holder获取到已有MediaProjection直接复用") 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) setupScreenCaptureWithMediaProjection(mediaProjection)
// 切换到 MediaProjection 模式以获得更高帧率
if (::screenCaptureManager.isInitialized) { if (::screenCaptureManager.isInitialized) {
screenCaptureManager.switchToMediaProjectionMode() screenCaptureManager.switchToMediaProjectionMode()
Log.i(TAG, "📱 Android 11+设备:已切换到 MediaProjection 模式") Log.i(TAG, "📱 Android 11+设备:已切换到 MediaProjection 模式")
} }
} else { } else {
Log.w(TAG, "⚠️ Android 11+ MediaProjection 不可用") Log.w(TAG, "⚠️ Android 11+ Holder中无有效MediaProjection,等待权限授予")
} }
// 标记权限完成 // 标记权限完成

View File

@@ -134,23 +134,30 @@ class RemoteControlForegroundService : Service() {
Log.i(TAG, "✅ 通过AccessibilityService的Android 15专用管理器处理") Log.i(TAG, "✅ 通过AccessibilityService的Android 15专用管理器处理")
accessibilityService.handleMediaProjectionGranted() accessibilityService.handleMediaProjectionGranted()
} else { } else {
// 备用方案:直接创建但记录连接时间用于智能判断 // 备用方案:从 Holder 获取已有对象
mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) mediaProjection = MediaProjectionHolder.getMediaProjection()
if (mediaProjection != null) { if (mediaProjection != null) {
Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功(备用方案)") Log.i(TAG, "✅ Android 15 从Holder获取到已有MediaProjection")
MediaProjectionHolder.setMediaProjection(mediaProjection) notifyAccessibilityService()
} else {
// Holder 中无对象,通过安全创建入口创建
mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection(
this@RemoteControlForegroundService, resultCode, resultData
)
if (mediaProjection != null) {
Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功安全创建入口")
notifyAccessibilityService() notifyAccessibilityService()
} }
} }
}
} else { } else {
// 其他版本的正常处理逻辑 // 其他版本:通过安全创建入口创建
mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection(
this@RemoteControlForegroundService, resultCode, resultData
)
if (mediaProjection != null) { if (mediaProjection != null) {
Log.i(TAG, "MediaProjection对象创建成功") Log.i(TAG, "MediaProjection对象创建成功(安全创建入口)")
// 将MediaProjection传递给AccessibilityService
MediaProjectionHolder.setMediaProjection(mediaProjection)
// 通知AccessibilityService // 通知AccessibilityService
notifyAccessibilityService() notifyAccessibilityService()