fix: MediaProjection权限丢失后无法回退无障碍截图和Web画面闪烁
- 采集循环每帧检测mediaProjection对象有效性,null时立即回退无障碍截图 - 统一fallbackToAccessibilityCapture方法,消除7处重复回退代码 - 无障碍截图失败后等待MIN_CAPTURE_INTERVAL剩余时间,避免截图间隔太短错误 - Web端canvas尺寸锁定策略:首帧锁定尺寸,后续帧统一缩放绘制,消除闪烁
This commit is contained in:
@@ -435,8 +435,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
} else {
|
||||
// MediaProjection 不可用,回退到无障碍截图
|
||||
Log.i(TAG, "Android 11+:MediaProjection 不可用,回退到无障碍截图模式")
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
fallbackToAccessibilityCapture()
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Log.i(TAG, "Android 5-10:使用 MediaProjection VirtualDisplay 连续流式捕获")
|
||||
@@ -464,13 +463,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
// 先确保 MediaProjection 可用
|
||||
if (!ensureMediaProjection()) {
|
||||
Log.e(TAG, "MediaProjection 不可用,回退到无障碍截图")
|
||||
// 回退到无障碍截图作为兜底
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
} else {
|
||||
startFallbackCapture()
|
||||
}
|
||||
fallbackToAccessibilityCapture()
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -481,12 +474,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
|
||||
if (virtualDisplay == null) {
|
||||
Log.e(TAG, "VirtualDisplay 创建失败,回退到无障碍截图")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
} else {
|
||||
startFallbackCapture()
|
||||
}
|
||||
fallbackToAccessibilityCapture()
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -505,18 +493,22 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
|
||||
while (isCapturing) {
|
||||
try {
|
||||
// MediaProjection对象有效性检测:
|
||||
// onStop回调会将Holder中的对象清理为null,
|
||||
// 此时继续采集毫无意义,应立即回退到无障碍截图
|
||||
if (mediaProjection == null) {
|
||||
Log.w(TAG, "MediaProjection对象已失效(被系统回收或onStop清理),立即回退到无障碍截图")
|
||||
cleanupVirtualDisplayOnly()
|
||||
fallbackToAccessibilityCapture()
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Surface有效性检测:BufferQueue被系统abandoned后立即回退
|
||||
// 防止acquireLatestImage()持续返回null触发无限重建循环
|
||||
val currentSurface = imageReader?.surface
|
||||
if (currentSurface == null || !currentSurface.isValid) {
|
||||
Log.w(TAG, "ImageReader Surface已失效(null=${currentSurface == null}, isValid=${currentSurface?.isValid}), 停止采集并回退到无障碍截图")
|
||||
Log.w(TAG, "ImageReader Surface已失效(null=${currentSurface == null}, isValid=${currentSurface?.isValid}), 回退到无障碍截图")
|
||||
cleanupVirtualDisplayOnly()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
} else {
|
||||
startFallbackCapture()
|
||||
}
|
||||
fallbackToAccessibilityCapture()
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -559,12 +551,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
Log.w(TAG, "MediaProjection连续${consecutiveBlackFrames}个黑屏帧,切换到无障碍截图模式")
|
||||
safeRecycleBitmap(bitmap)
|
||||
cleanupVirtualDisplayOnly()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
} else {
|
||||
startFallbackCapture()
|
||||
}
|
||||
fallbackToAccessibilityCapture()
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -600,12 +587,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
if (virtualDisplayRebuildCount > MAX_VIRTUAL_DISPLAY_REBUILD_ATTEMPTS) {
|
||||
Log.w(TAG, "重建次数已达上限,回退到无障碍截图")
|
||||
cleanupVirtualDisplayOnly()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
} else {
|
||||
startFallbackCapture()
|
||||
}
|
||||
fallbackToAccessibilityCapture()
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -619,10 +601,7 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
if (virtualDisplay == null || rebuiltSurface == null || !rebuiltSurface.isValid) {
|
||||
Log.w(TAG, "VirtualDisplay 重建后无效(vd=${virtualDisplay != null}, surface=${rebuiltSurface != null}, valid=${rebuiltSurface?.isValid}), 回退到无障碍截图")
|
||||
cleanupVirtualDisplayOnly()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
}
|
||||
fallbackToAccessibilityCapture()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
@@ -642,12 +621,25 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一回退到无障碍截图的方法
|
||||
* Android 11+ 使用无障碍截图,低版本使用测试图像兜底
|
||||
*/
|
||||
private fun fallbackToAccessibilityCapture() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
enableAccessibilityScreenshotMode()
|
||||
startAccessibilityScreenCapture()
|
||||
} else {
|
||||
startFallbackCapture()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 MediaProjection 可用
|
||||
*
|
||||
* 核心修复:禁止重复调用 getMediaProjection(resultCode, resultData)
|
||||
* 每次调用都会创建新实例,系统会自动 stop 旧实例,触发 onStop 回调,
|
||||
* 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。
|
||||
* 形成"权限丢失->恢复->再丢失"的死循环,这是权限频繁掉落的根因。
|
||||
*
|
||||
* 只从 MediaProjectionHolder 获取已有对象,不重新创建。
|
||||
*/
|
||||
@@ -727,11 +719,19 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
}
|
||||
}
|
||||
|
||||
// 截图成功时按帧率延迟,失败时短延迟后立即重试
|
||||
// 截图成功时按帧率延迟,失败时等待最小截图间隔后重试
|
||||
if (consecutiveFailures == 0) {
|
||||
delay(1000 / dynamicFps.toLong())
|
||||
} else {
|
||||
delay(50) // 失败时50ms后重试,让系统截图间隔限制自己控制节奏
|
||||
// 无障碍截图有系统级最小间隔限制(约3秒),
|
||||
// 失败时等待剩余间隔时间,避免"截图间隔太短"错误
|
||||
val elapsed = System.currentTimeMillis() - lastScreenshotTime
|
||||
val remaining = MIN_CAPTURE_INTERVAL - elapsed
|
||||
if (remaining > 0) {
|
||||
delay(remaining)
|
||||
} else {
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: CancellationException) {
|
||||
|
||||
Reference in New Issue
Block a user