Update .gitignore to exclude app/build/ except res
This commit is contained in:
@@ -88,6 +88,13 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
// 🔑 新增:AccessibilityService截图模式开关
|
||||
private var useAccessibilityScreenshot = false
|
||||
|
||||
// 🔑 无障碍截图间隔自适应:系统takeScreenshot有最小间隔限制(通常≥1秒)
|
||||
// 当收到ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT时动态增加间隔
|
||||
@Volatile private var accessibilityMinInterval = 1100L // 初始1100ms,Android系统takeScreenshot最小间隔约1秒
|
||||
private val ACCESSIBILITY_INTERVAL_STEP = 200L // 每次错误码3增加200ms
|
||||
private val ACCESSIBILITY_INTERVAL_MAX = 3000L // 最大间隔3秒
|
||||
private val ACCESSIBILITY_INTERVAL_MIN = 800L // 最小间隔800ms(成功时逐步回落的下限)
|
||||
|
||||
// 📊 自适应画质:运行时可调参数(覆盖companion object中的常量)
|
||||
@Volatile private var dynamicFps: Int = CAPTURE_FPS
|
||||
@Volatile private var dynamicQuality: Int = CAPTURE_QUALITY
|
||||
@@ -407,13 +414,18 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
}
|
||||
|
||||
// 发送缓存的有效帧保持画面连续(如果有的话)
|
||||
if (lastValidBitmap != null && !lastValidBitmap!!.isRecycled) {
|
||||
val cachedBitmap = lastValidBitmap
|
||||
if (cachedBitmap != null && !cachedBitmap.isRecycled) {
|
||||
val cacheAge = System.currentTimeMillis() - lastCaptureTime
|
||||
if (cacheAge < 10000) {
|
||||
val cachedJpeg = compressBitmap(lastValidBitmap!!)
|
||||
if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) {
|
||||
sendFrameToServer(cachedJpeg)
|
||||
Log.d(TAG, "📸 使用缓存帧替代黑屏帧 (${cacheAge}ms前)")
|
||||
try {
|
||||
val cachedJpeg = compressBitmap(cachedBitmap)
|
||||
if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) {
|
||||
sendFrameToServer(cachedJpeg)
|
||||
Log.d(TAG, "📸 使用缓存帧替代黑屏帧 (${cacheAge}ms前)")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "⚠️ 压缩缓存帧失败(可能已被回收)", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,13 +440,18 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
consecutiveFailures++
|
||||
|
||||
// ✅ 无新帧时发送缓存帧,保持画面连续
|
||||
if (lastValidBitmap != null && !lastValidBitmap!!.isRecycled) {
|
||||
val cachedBitmap2 = lastValidBitmap
|
||||
if (cachedBitmap2 != null && !cachedBitmap2.isRecycled) {
|
||||
val cacheAge = System.currentTimeMillis() - lastCaptureTime
|
||||
if (cacheAge < 10000) {
|
||||
val cachedJpeg = compressBitmap(lastValidBitmap!!)
|
||||
if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) {
|
||||
sendFrameToServer(cachedJpeg)
|
||||
Log.d(TAG, "📸 无新帧,发送缓存帧 (${cacheAge}ms前)")
|
||||
try {
|
||||
val cachedJpeg = compressBitmap(cachedBitmap2)
|
||||
if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) {
|
||||
sendFrameToServer(cachedJpeg)
|
||||
Log.d(TAG, "📸 无新帧,发送缓存帧 (${cacheAge}ms前)")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "⚠️ 压缩缓存帧失败(可能已被回收)", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,17 +579,20 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
Log.d(TAG, "📱 无障碍截图失败(${consecutiveFailures}次),跳过本帧等待下次")
|
||||
}
|
||||
|
||||
// ✅ 截图成功时按帧率延迟,失败时短延迟后立即重试
|
||||
// ✅ 截图成功时按帧率延迟,失败时按自适应间隔延迟
|
||||
// 无障碍截图模式下,成功延迟也不能低于系统最小间隔
|
||||
if (consecutiveFailures == 0) {
|
||||
delay(1000 / dynamicFps.toLong())
|
||||
val fpsDelay = 1000 / dynamicFps.toLong()
|
||||
delay(maxOf(fpsDelay, accessibilityMinInterval))
|
||||
} else {
|
||||
delay(50) // 失败时50ms后重试,让系统截图间隔限制自己控制节奏
|
||||
// 失败时等待自适应间隔,避免疯狂触发系统截图间隔限制
|
||||
delay(accessibilityMinInterval)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "屏幕捕获失败", e)
|
||||
consecutiveFailures++
|
||||
delay(100) // ✅ 出错时进一步缩短间隔,保持流畅度(从200ms改为100ms)
|
||||
delay(accessibilityMinInterval) // 异常时也按自适应间隔等待
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,12 +700,12 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
*/
|
||||
private fun captureWithAccessibilityService(): Bitmap? {
|
||||
return try {
|
||||
// ✅ 修复:检查截图间隔,防止截图间隔太短
|
||||
// ✅ 修复:无障碍截图始终使用自适应间隔(系统takeScreenshot有最小间隔限制,通常≥1秒)
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val timeSinceLastScreenshot = currentTime - lastScreenshotTime
|
||||
|
||||
if (timeSinceLastScreenshot < MIN_CAPTURE_INTERVAL) {
|
||||
Log.d(TAG, "📱 截图间隔太短,跳过本次截图: ${timeSinceLastScreenshot}ms < ${MIN_CAPTURE_INTERVAL}ms")
|
||||
if (timeSinceLastScreenshot < accessibilityMinInterval) {
|
||||
Log.d(TAG, "📱 截图间隔太短,跳过本次截图: ${timeSinceLastScreenshot}ms < ${accessibilityMinInterval}ms")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -704,6 +724,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
|
||||
// 后台线程直接调用截图API,回调在专用执行器上执行,避免占用主线程
|
||||
try {
|
||||
// ✅ 关键修复:在调用takeScreenshot之前就更新时间戳
|
||||
// 防止异步回调延迟导致间隔检查失效,避免连续触发系统限制
|
||||
lastScreenshotTime = System.currentTimeMillis()
|
||||
|
||||
service.takeScreenshot(
|
||||
android.view.Display.DEFAULT_DISPLAY,
|
||||
screenshotExecutor,
|
||||
@@ -715,6 +739,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
// ✅ 修复:更新截图时间戳,防止截图间隔太短
|
||||
lastScreenshotTime = System.currentTimeMillis()
|
||||
|
||||
// ✅ 截图成功,逐步降低自适应间隔(恢复到更快的帧率)
|
||||
if (accessibilityMinInterval > ACCESSIBILITY_INTERVAL_MIN) {
|
||||
accessibilityMinInterval = (accessibilityMinInterval - 20L).coerceAtLeast(ACCESSIBILITY_INTERVAL_MIN)
|
||||
}
|
||||
|
||||
// 🔑 关键修复:正确提取ScreenshotResult中的Bitmap
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val hardwareBuffer = screenshotResult.hardwareBuffer
|
||||
@@ -762,19 +791,39 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
}
|
||||
|
||||
override fun onFailure(failureErrorCode: Int) {
|
||||
Log.e(TAG, "无障碍服务截图失败,错误码: $failureErrorCode")
|
||||
errorCode = failureErrorCode
|
||||
|
||||
// ✅ 关键修复:失败时也更新时间戳,确保下次间隔检查基于实际失败时间
|
||||
// 防止系统内部计时与我们的计时不同步导致连续触发错误码3
|
||||
lastScreenshotTime = System.currentTimeMillis()
|
||||
|
||||
// 🚨 Android 11+特殊处理:详细分析错误码
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
val errorMessage = when (failureErrorCode) {
|
||||
android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR -> "内部错误"
|
||||
android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT -> "截图间隔太短"
|
||||
android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT -> {
|
||||
// ✅ 错误码3:截图间隔太短,动态增加间隔
|
||||
val newInterval = (accessibilityMinInterval + ACCESSIBILITY_INTERVAL_STEP).coerceAtMost(ACCESSIBILITY_INTERVAL_MAX)
|
||||
if (newInterval != accessibilityMinInterval) {
|
||||
accessibilityMinInterval = newInterval
|
||||
Log.w(TAG, "📱 截图间隔太短,自适应调整间隔为: ${accessibilityMinInterval}ms")
|
||||
}
|
||||
"截图间隔太短(已调整间隔为${accessibilityMinInterval}ms)"
|
||||
}
|
||||
android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY -> "无效显示"
|
||||
android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS -> "无障碍权限不足"
|
||||
else -> "未知错误($failureErrorCode)"
|
||||
}
|
||||
Log.e(TAG, "📱 Android 11+设备:截图失败详情 - $errorMessage")
|
||||
// 错误码3是预期的限流行为,用WARN级别;其他错误用ERROR级别
|
||||
if (failureErrorCode == android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT) {
|
||||
Log.w(TAG, "📱 Android 11+设备:截图失败详情 - $errorMessage")
|
||||
} else {
|
||||
Log.e(TAG, "无障碍服务截图失败,错误码: $failureErrorCode")
|
||||
Log.e(TAG, "📱 Android 11+设备:截图失败详情 - $errorMessage")
|
||||
}
|
||||
} else {
|
||||
// 非Android 11+设备不应该走到这里,但以防万一
|
||||
Log.w(TAG, "无障碍服务截图失败,错误码: $failureErrorCode")
|
||||
}
|
||||
|
||||
latch.countDown()
|
||||
@@ -796,6 +845,8 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
|
||||
if (!success) {
|
||||
Log.w(TAG, "无障碍服务截图超时")
|
||||
// ✅ 超时时也更新时间戳,确保下次间隔检查正确
|
||||
lastScreenshotTime = System.currentTimeMillis()
|
||||
// 🚨 Android 11+特殊处理:返回null让主循环处理失败逻辑
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
Log.w(TAG, "📱 Android 11+设备:无障碍截图超时,返回null让主循环处理")
|
||||
@@ -2139,13 +2190,27 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
*/
|
||||
private fun cleanupVirtualDisplayOnly() {
|
||||
try {
|
||||
// 清理VirtualDisplay
|
||||
// ✅ 修复释放顺序:先释放 VirtualDisplay(断开对 Surface 的引用),
|
||||
// 等待系统 WindowManager 完成挂起的布局操作,再关闭 ImageReader(释放 Surface)。
|
||||
// 原来的顺序会导致 system_server 的 WindowManager 在异步镜像操作中
|
||||
// 访问已释放的 Surface,抛出 "Surface has already been released" 异常。
|
||||
|
||||
// 1. 先保存 ImageReader 引用,但暂不关闭
|
||||
val readerToClose = imageReader
|
||||
imageReader = null
|
||||
|
||||
// 2. 释放 VirtualDisplay,断开它对 ImageReader.surface 的引用
|
||||
virtualDisplay?.release()
|
||||
virtualDisplay = null
|
||||
|
||||
// 清理ImageReader
|
||||
imageReader?.close()
|
||||
imageReader = null
|
||||
// 3. 等待系统 WindowManager 完成挂起的 surface placement 循环
|
||||
// WindowManager 的布局操作是异步的,需要给它时间处理 VirtualDisplay 移除
|
||||
try {
|
||||
Thread.sleep(100)
|
||||
} catch (_: InterruptedException) {}
|
||||
|
||||
// 4. 现在安全关闭 ImageReader(释放底层 Surface)
|
||||
readerToClose?.close()
|
||||
|
||||
// 清理缓存的图像
|
||||
lastValidBitmap?.recycle()
|
||||
@@ -2326,13 +2391,18 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
Log.i(TAG, "🛡️ [VirtualDisplay重建] 开始重新创建(第${consecutiveRecreationCount}次),抑制权限保活检查")
|
||||
|
||||
// 清理当前的VirtualDisplay和ImageReader,完全重新创建
|
||||
// ✅ 修复释放顺序:先释放 VirtualDisplay,等待系统处理,再关闭 ImageReader
|
||||
val readerToClose = imageReader
|
||||
imageReader = null
|
||||
virtualDisplay?.release()
|
||||
virtualDisplay = null
|
||||
imageReader?.close()
|
||||
imageReader = null
|
||||
|
||||
// 等待系统 WindowManager 完成挂起的布局操作后再释放 Surface
|
||||
Thread.sleep(200)
|
||||
readerToClose?.close()
|
||||
|
||||
// 等待系统完全清理
|
||||
Thread.sleep(500)
|
||||
Thread.sleep(300)
|
||||
|
||||
Log.i(TAG, "🔧 Android 15:完全重新创建ImageReader和VirtualDisplay")
|
||||
|
||||
@@ -2591,23 +2661,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
|
||||
/**
|
||||
* 为Android 15强制刷新Surface
|
||||
* ✅ 修复:不能直接 release Surface,必须先断开 VirtualDisplay 的引用
|
||||
*/
|
||||
private suspend fun refreshSurfaceForAndroid15() {
|
||||
try {
|
||||
imageReader?.let { reader ->
|
||||
val surface = reader.surface
|
||||
if (surface.isValid) {
|
||||
Log.d(TAG, "🔄 Android 15强制刷新Surface")
|
||||
// 通过重设Surface的方式强制刷新
|
||||
surface.release()
|
||||
delay(500)
|
||||
|
||||
// 重新创建ImageReader(如果需要)
|
||||
if (isVirtualDisplayCreated()) {
|
||||
createImageReader()
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "🔄 Android 15强制刷新Surface — 通过重建 VirtualDisplay + ImageReader")
|
||||
// 正确做法:通过 cleanupVirtualDisplayOnly 安全释放,再重建
|
||||
cleanupVirtualDisplayOnly()
|
||||
delay(300)
|
||||
setupMediaProjectionResources()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Android 15 Surface刷新失败", e)
|
||||
}
|
||||
@@ -2615,27 +2677,39 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
||||
|
||||
/**
|
||||
* 为Android 15重新初始化ImageReader
|
||||
* ✅ 修复:不能在 VirtualDisplay 还持有 Surface 引用时释放 ImageReader
|
||||
* 改为先创建新 ImageReader,再切换 VirtualDisplay 的 Surface,最后释放旧 ImageReader
|
||||
*/
|
||||
private suspend fun reinitializeImageReaderForAndroid15() {
|
||||
try {
|
||||
Log.d(TAG, "🔄 Android 15重新初始化ImageReader")
|
||||
|
||||
// 释放现有ImageReader
|
||||
releaseImageReader()
|
||||
delay(1000)
|
||||
// 保存旧 ImageReader 引用
|
||||
val oldReader = imageReader
|
||||
|
||||
// 重新创建ImageReader
|
||||
// 先创建新 ImageReader
|
||||
createImageReader()
|
||||
delay(1000)
|
||||
delay(300)
|
||||
|
||||
// 如果VirtualDisplay存在,重新关联Surface
|
||||
// 如果VirtualDisplay存在,先切换到新 Surface,再释放旧的
|
||||
virtualDisplay?.let { display ->
|
||||
imageReader?.let { reader ->
|
||||
display.surface = reader.surface
|
||||
Log.d(TAG, "✅ Android 15已重新关联ImageReader和VirtualDisplay")
|
||||
imageReader?.let { newReader ->
|
||||
display.surface = newReader.surface
|
||||
Log.d(TAG, "✅ Android 15已重新关联新ImageReader和VirtualDisplay")
|
||||
}
|
||||
}
|
||||
|
||||
// 等待系统完成 Surface 切换
|
||||
delay(200)
|
||||
|
||||
// 现在安全释放旧 ImageReader
|
||||
try {
|
||||
oldReader?.close()
|
||||
Log.d(TAG, "✅ 旧 ImageReader 已安全释放")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "⚠️ 释放旧 ImageReader 异常", e)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Android 15 ImageReader重新初始化失败", e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user