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

- 根因:多处代码重复调用getMediaProjection()创建新实例,系统自动stop旧实例触发onStop回调,形成权限丢失恢复再丢失的死循环
- ScreenCaptureManager.ensureMediaProjection():禁止重复创建,只从Holder获取已有对象
- ScreenCaptureManager.captureWithMediaProjection():移除重复创建逻辑
- ScreenCaptureManager.triggerPermissionRecovery():优先从Holder和SmartManager获取已有对象
- ScreenCaptureManager.regenerateMediaProjectionForAndroid15():优先复用Holder中已有对象
- SmartMediaProjectionManager.attemptSilentRecovery():先检查Holder是否已有有效对象
- Android15MediaProjectionManager.attemptSilentRecovery():先检查Holder复用已有对象
- Android15MediaProjectionManager.determineStopReason():修复误判逻辑,Holder中仍有有效对象时识别为旧实例被替换而非用户主动停止
- AccessibilityRemoteService.attemptAndroid15SilentRecovery():优先复用Holder中已有对象
- AccessibilityRemoteService.handleMediaProjectionGranted():Android 11+优先从Holder获取
- RemoteControlForegroundService.handleStartMediaProjection():优先检查Holder避免重复创建
This commit is contained in:
wdvipa
2026-02-15 00:22:47 +08:00
parent de9aa4430c
commit 39bc5b47a0
5 changed files with 161 additions and 175 deletions

View File

@@ -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, "❌ 无权限数据,静默恢复失败")

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)
// 🚨 核心修复:优先从 Holder 获取已有对象,避免重复创建导致权限掉落
var mediaProjection = MediaProjectionHolder.getMediaProjection()
// 切换到 MediaProjection 模式以获得更高帧率
if (::screenCaptureManager.isInitialized) {
screenCaptureManager.switchToMediaProjectionMode()
Log.i(TAG, "📱 Android 11+设备:已切换到 MediaProjection 模式")
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

View File

@@ -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()