fix: 修复Bitmap并发回收闪退和缓存帧竞态问题
- lastValidBitmap/lastCaptureTime添加@Volatile确保多协程可见性 - safeRecycleLastValidBitmap()添加@Synchronized防止并发double-recycle - 新增updateLastValidBitmap()原子替换缓存帧(synchronized) - 新增compressCachedFrame()安全压缩缓存帧(synchronized) - 新增safeCopyLastValidBitmap()安全复制缓存帧(synchronized) - 替换所有直接访问lastValidBitmap的代码为synchronized方法调用 - 涉及方法: startMediaProjectionCapture/startAccessibilityScreenCapture/handleAndroid11ScreenshotFailure/captureWithMediaProjection/forceRefreshAndroid15Images - 清理MainActivity和SocketIOManager中日志的emoji符号
This commit is contained in:
@@ -3729,11 +3729,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// 检查MediaProjectionManager是否已初始化
|
// 检查MediaProjectionManager是否已初始化
|
||||||
if (mediaProjectionManager == null) {
|
if (mediaProjectionManager == null) {
|
||||||
Log.e(TAG, "❌ MediaProjectionManager未初始化,重新初始化")
|
Log.e(TAG, "MediaProjectionManager未初始化,重新初始化")
|
||||||
mediaProjectionManager =
|
mediaProjectionManager =
|
||||||
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||||
if (mediaProjectionManager == null) {
|
if (mediaProjectionManager == null) {
|
||||||
Log.e(TAG, "❌ 重新初始化MediaProjectionManager失败")
|
Log.e(TAG, "重新初始化MediaProjectionManager失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
private var imageReader: android.media.ImageReader? = null
|
private var imageReader: android.media.ImageReader? = null
|
||||||
private var mediaProjection: android.media.projection.MediaProjection? = null
|
private var mediaProjection: android.media.projection.MediaProjection? = null
|
||||||
|
|
||||||
// 图像缓存机制
|
// 图像缓存机制 - @Volatile确保多协程可见性
|
||||||
private var lastValidBitmap: Bitmap? = null
|
@Volatile private var lastValidBitmap: Bitmap? = null
|
||||||
private var lastCaptureTime = 0L
|
@Volatile private var lastCaptureTime = 0L
|
||||||
private var lastScreenshotTime = 0L // 新增:记录上次截图时间,防止截图间隔太短
|
private var lastScreenshotTime = 0L // 新增:记录上次截图时间,防止截图间隔太短
|
||||||
|
|
||||||
// 状态跟踪
|
// 状态跟踪
|
||||||
@@ -159,12 +159,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
/**
|
/**
|
||||||
* 安全回收lastValidBitmap,防止多线程并发导致的native crash (SIGSEGV)
|
* 安全回收lastValidBitmap,防止多线程并发导致的native crash (SIGSEGV)
|
||||||
*
|
*
|
||||||
* 问题根因:多个协程(startMediaProjectionCapture、startAccessibilityScreenCapture、
|
* 使用synchronized确保原子性:先置null再recycle,
|
||||||
* captureWithMediaProjection等)并发访问lastValidBitmap,可能导致:
|
* 防止两个协程同时拿到同一个引用并double-recycle
|
||||||
* 1. double-recycle:Bitmap已被其他线程recycle但引用未置null
|
|
||||||
* 2. Hardware Bitmap的底层HardwareBuffer已被释放
|
|
||||||
* 两种情况都会在native层BitmapWrapper::freePixels()触发SIGSEGV
|
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
private fun safeRecycleLastValidBitmap() {
|
private fun safeRecycleLastValidBitmap() {
|
||||||
val bitmapToRecycle = lastValidBitmap
|
val bitmapToRecycle = lastValidBitmap
|
||||||
lastValidBitmap = null // 先置null,防止其他线程访问
|
lastValidBitmap = null // 先置null,防止其他线程访问
|
||||||
@@ -178,6 +176,63 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全更新lastValidBitmap缓存
|
||||||
|
* synchronized确保与safeRecycleLastValidBitmap互斥
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
private fun updateLastValidBitmap(newBitmap: Bitmap) {
|
||||||
|
val old = lastValidBitmap
|
||||||
|
lastValidBitmap = newBitmap
|
||||||
|
trackBitmap(newBitmap)
|
||||||
|
lastCaptureTime = System.currentTimeMillis()
|
||||||
|
// 回收旧的缓存
|
||||||
|
try {
|
||||||
|
if (old != null && !old.isRecycled) {
|
||||||
|
old.recycle()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "回收旧缓存Bitmap异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全压缩缓存帧并返回JPEG数据
|
||||||
|
* synchronized确保读取期间lastValidBitmap不被回收
|
||||||
|
* @param maxAge 缓存最大有效期(ms)
|
||||||
|
* @return JPEG数据,缓存无效时返回null
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
private fun compressCachedFrame(maxAge: Long): ByteArray? {
|
||||||
|
val cached = lastValidBitmap ?: return null
|
||||||
|
if (cached.isRecycled) return null
|
||||||
|
val age = System.currentTimeMillis() - lastCaptureTime
|
||||||
|
if (age > maxAge) return null
|
||||||
|
return try {
|
||||||
|
compressBitmap(cached)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "压缩缓存帧失败: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全复制lastValidBitmap
|
||||||
|
* synchronized确保复制期间不被其他线程回收
|
||||||
|
* @return Bitmap副本,缓存无效时返回null
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
private fun safeCopyLastValidBitmap(): Bitmap? {
|
||||||
|
val cached = lastValidBitmap ?: return null
|
||||||
|
if (cached.isRecycled) return null
|
||||||
|
return try {
|
||||||
|
cached.copy(cached.config ?: Bitmap.Config.ARGB_8888, false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "复制缓存Bitmap失败: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全回收任意Bitmap,防止native crash
|
* 安全回收任意Bitmap,防止native crash
|
||||||
*/
|
*/
|
||||||
@@ -484,10 +539,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
// 有效帧:发送并更新缓存
|
// 有效帧:发送并更新缓存
|
||||||
sendFrameToServer(jpegData)
|
sendFrameToServer(jpegData)
|
||||||
|
|
||||||
safeRecycleLastValidBitmap()
|
// 安全更新缓存帧
|
||||||
lastValidBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false)
|
val copy = bitmap.copy(Bitmap.Config.ARGB_8888, false)
|
||||||
lastValidBitmap?.let { trackBitmap(it) }
|
if (copy != null) {
|
||||||
lastCaptureTime = System.currentTimeMillis()
|
updateLastValidBitmap(copy)
|
||||||
|
}
|
||||||
|
|
||||||
consecutiveFailures = 0
|
consecutiveFailures = 0
|
||||||
consecutiveBlackFrames = 0
|
consecutiveBlackFrames = 0
|
||||||
@@ -513,20 +569,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send cached valid frame to maintain continuity (if available)
|
// Send cached valid frame to maintain continuity (if available)
|
||||||
val cachedBmp = lastValidBitmap
|
val cachedJpeg = compressCachedFrame(10000)
|
||||||
if (cachedBmp != null && !cachedBmp.isRecycled) {
|
if (cachedJpeg != null && cachedJpeg.size >= MIN_VALID_FRAME_SIZE) {
|
||||||
val cacheAge = System.currentTimeMillis() - lastCaptureTime
|
sendFrameToServer(cachedJpeg)
|
||||||
if (cacheAge < 10000) {
|
Log.d(TAG, "Used cached frame instead of black frame")
|
||||||
try {
|
|
||||||
val cachedJpeg = compressBitmap(cachedBmp)
|
|
||||||
if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) {
|
|
||||||
sendFrameToServer(cachedJpeg)
|
|
||||||
Log.d(TAG, "Used cached frame instead of black frame (${cacheAge}ms ago)")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to compress cached frame: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,20 +585,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
consecutiveFailures++
|
consecutiveFailures++
|
||||||
|
|
||||||
// Send cached frame when no new frame available
|
// Send cached frame when no new frame available
|
||||||
val cachedBmp2 = lastValidBitmap
|
val cachedJpeg2 = compressCachedFrame(10000)
|
||||||
if (cachedBmp2 != null && !cachedBmp2.isRecycled) {
|
if (cachedJpeg2 != null && cachedJpeg2.size >= MIN_VALID_FRAME_SIZE) {
|
||||||
val cacheAge = System.currentTimeMillis() - lastCaptureTime
|
sendFrameToServer(cachedJpeg2)
|
||||||
if (cacheAge < 10000) {
|
Log.d(TAG, "No new frame, sent cached frame")
|
||||||
try {
|
|
||||||
val cachedJpeg = compressBitmap(cachedBmp2)
|
|
||||||
if (cachedJpeg.size >= MIN_VALID_FRAME_SIZE) {
|
|
||||||
sendFrameToServer(cachedJpeg)
|
|
||||||
Log.d(TAG, "No new frame, sent cached frame (${cacheAge}ms ago)")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to compress cached frame: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连续失败过多,尝试重建 VirtualDisplay
|
// 连续失败过多,尝试重建 VirtualDisplay
|
||||||
@@ -662,12 +698,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
|
|
||||||
// 缓存成功的截图,用于防止闪烁
|
// 缓存成功的截图,用于防止闪烁
|
||||||
try {
|
try {
|
||||||
// 清理旧的缓存
|
val copy = screenshot.copy(screenshot.config ?: Bitmap.Config.ARGB_8888, false)
|
||||||
safeRecycleLastValidBitmap()
|
if (copy != null) {
|
||||||
// 保存当前成功的截图副本
|
updateLastValidBitmap(copy)
|
||||||
lastValidBitmap = screenshot.copy(screenshot.config ?: Bitmap.Config.ARGB_8888, false)
|
Log.d(TAG, "已缓存有效截图用于防闪烁: ${copy.width}x${copy.height}")
|
||||||
lastCaptureTime = System.currentTimeMillis()
|
}
|
||||||
Log.d(TAG, "已缓存有效截图用于防闪烁: ${lastValidBitmap?.width}x${lastValidBitmap?.height}")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "缓存有效截图失败", e)
|
Log.w(TAG, "缓存有效截图失败", e)
|
||||||
}
|
}
|
||||||
@@ -719,32 +754,20 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
|
|
||||||
Log.d(TAG, "Android 11+设备:截图失败 (连续${android11ConsecutiveFailures}次)")
|
Log.d(TAG, "Android 11+设备:截图失败 (连续${android11ConsecutiveFailures}次)")
|
||||||
|
|
||||||
// Prefer cached last frame to avoid flicker
|
// 安全读取缓存帧(synchronized保护)
|
||||||
val cachedBmp = lastValidBitmap
|
val cachedCopy = safeCopyLastValidBitmap()
|
||||||
if (cachedBmp != null && !cachedBmp.isRecycled) {
|
if (cachedCopy != null) {
|
||||||
val cacheAge = currentTime - lastCaptureTime
|
val cacheAge = currentTime - lastCaptureTime
|
||||||
|
|
||||||
// Cache is fresh (within 10s), use it directly
|
|
||||||
if (cacheAge < 10000) {
|
if (cacheAge < 10000) {
|
||||||
Log.d(TAG, "Android 11+ device: returning cached screenshot (${cacheAge}ms ago)")
|
Log.d(TAG, "Android 11+: 返回缓存截图(${cacheAge}ms前)")
|
||||||
return try {
|
return cachedCopy
|
||||||
cachedBmp.copy(cachedBmp.config ?: Bitmap.Config.ARGB_8888, false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to copy cached screenshot", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache is older but still usable (within 60s), use during short failures
|
|
||||||
if (cacheAge < 60000 && android11ConsecutiveFailures < 20) {
|
if (cacheAge < 60000 && android11ConsecutiveFailures < 20) {
|
||||||
Log.d(TAG, "Android 11+ device: returning older cached screenshot (${cacheAge}ms ago)")
|
Log.d(TAG, "Android 11+: 返回较旧缓存截图(${cacheAge}ms前)")
|
||||||
return try {
|
return cachedCopy
|
||||||
cachedBmp.copy(cachedBmp.config ?: Bitmap.Config.ARGB_8888, false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to copy cached screenshot", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 缓存过旧,回收copy
|
||||||
|
safeRecycleBitmap(cachedCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无可用缓存或缓存过旧,智能判断是否应该进入测试模式
|
// 无可用缓存或缓存过旧,智能判断是否应该进入测试模式
|
||||||
@@ -1038,10 +1061,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
consecutiveImageFailures = 0
|
consecutiveImageFailures = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
safeRecycleLastValidBitmap()
|
val copy = newBitmap.copy(Bitmap.Config.ARGB_8888, false)
|
||||||
lastValidBitmap = newBitmap.copy(Bitmap.Config.ARGB_8888, false)
|
if (copy != null) {
|
||||||
lastValidBitmap?.let { trackBitmap(it) }
|
updateLastValidBitmap(copy)
|
||||||
lastCaptureTime = currentTime
|
}
|
||||||
|
|
||||||
return newBitmap
|
return newBitmap
|
||||||
}
|
}
|
||||||
@@ -1063,19 +1086,15 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no new image available, check if cache can be used
|
// If no new image available, check if cache can be used
|
||||||
val cachedBmp3 = lastValidBitmap
|
val cachedCopy3 = safeCopyLastValidBitmap()
|
||||||
if (cachedBmp3 != null && !cachedBmp3.isRecycled) {
|
if (cachedCopy3 != null) {
|
||||||
val timeSinceLastCapture = currentTime - lastCaptureTime
|
val timeSinceLastCapture = currentTime - lastCaptureTime
|
||||||
if (timeSinceLastCapture < 30000) {
|
if (timeSinceLastCapture < 30000) {
|
||||||
Log.d(TAG, "Using cached image (${timeSinceLastCapture}ms ago) - static page")
|
Log.d(TAG, "Using cached image (${timeSinceLastCapture}ms ago) - static page")
|
||||||
return try {
|
return cachedCopy3
|
||||||
cachedBmp3.copy(Bitmap.Config.ARGB_8888, false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to copy cached bitmap: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Cached image expired (${timeSinceLastCapture}ms ago), cleaning")
|
Log.w(TAG, "Cached image expired (${timeSinceLastCapture}ms ago), cleaning")
|
||||||
|
safeRecycleBitmap(cachedCopy3)
|
||||||
safeRecycleLastValidBitmap()
|
safeRecycleLastValidBitmap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1115,9 +1134,10 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
val retryBitmap = convertImageToBitmap(retryImage)
|
val retryBitmap = convertImageToBitmap(retryImage)
|
||||||
if (retryBitmap != null) {
|
if (retryBitmap != null) {
|
||||||
Log.i(TAG, "Android 15重新初始化后成功获取图像")
|
Log.i(TAG, "Android 15重新初始化后成功获取图像")
|
||||||
safeRecycleLastValidBitmap()
|
val retryCopy = retryBitmap.copy(Bitmap.Config.ARGB_8888, false)
|
||||||
lastValidBitmap = retryBitmap.copy(Bitmap.Config.ARGB_8888, false)
|
if (retryCopy != null) {
|
||||||
lastCaptureTime = System.currentTimeMillis()
|
updateLastValidBitmap(retryCopy)
|
||||||
|
}
|
||||||
return retryBitmap
|
return retryBitmap
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -2615,10 +2635,11 @@ class ScreenCaptureManager(private val service: AccessibilityRemoteService) {
|
|||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
Log.i(TAG, "Android 15强制刷新成功:${bitmap.width}x${bitmap.height}")
|
Log.i(TAG, "Android 15强制刷新成功:${bitmap.width}x${bitmap.height}")
|
||||||
|
|
||||||
// 更新缓存
|
// 安全更新缓存
|
||||||
safeRecycleLastValidBitmap()
|
val copy = bitmap.copy(Bitmap.Config.ARGB_8888, false)
|
||||||
lastValidBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false)
|
if (copy != null) {
|
||||||
lastCaptureTime = System.currentTimeMillis()
|
updateLastValidBitmap(copy)
|
||||||
|
}
|
||||||
|
|
||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,9 +342,9 @@ class SocketIOManager(private val service: AccessibilityRemoteService) {
|
|||||||
socket?.let { socket ->
|
socket?.let { socket ->
|
||||||
|
|
||||||
socket.on(Socket.EVENT_CONNECT) {
|
socket.on(Socket.EVENT_CONNECT) {
|
||||||
Log.e(TAG, "✅✅✅ Socket.IO v4 连接成功!!! ✅✅✅")
|
Log.i(TAG, "Socket.IO v4 连接成功")
|
||||||
isConnected = true
|
isConnected = true
|
||||||
isDeviceRegistered = false // ✅ 重置注册状态,等待重新注册
|
isDeviceRegistered = false // 重置注册状态,等待重新注册
|
||||||
|
|
||||||
// ✅ 记录连接成功时间和网络类型
|
// ✅ 记录连接成功时间和网络类型
|
||||||
lastConnectTime = System.currentTimeMillis()
|
lastConnectTime = System.currentTimeMillis()
|
||||||
@@ -383,7 +383,7 @@ class SocketIOManager(private val service: AccessibilityRemoteService) {
|
|||||||
|
|
||||||
socket.on(Socket.EVENT_DISCONNECT) { args ->
|
socket.on(Socket.EVENT_DISCONNECT) { args ->
|
||||||
val reason = if (args.isNotEmpty()) args[0].toString() else "unknown"
|
val reason = if (args.isNotEmpty()) args[0].toString() else "unknown"
|
||||||
Log.e(TAG, "📴📴📴 Socket.IO v4 断开: $reason 📴📴📴")
|
Log.w(TAG, "Socket.IO v4 断开: $reason")
|
||||||
|
|
||||||
// ✅ 增强断开原因分析和统计
|
// ✅ 增强断开原因分析和统计
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
@@ -426,7 +426,7 @@ class SocketIOManager(private val service: AccessibilityRemoteService) {
|
|||||||
|
|
||||||
socket.on(Socket.EVENT_CONNECT_ERROR) { args ->
|
socket.on(Socket.EVENT_CONNECT_ERROR) { args ->
|
||||||
val error = if (args.isNotEmpty()) args[0].toString() else "unknown"
|
val error = if (args.isNotEmpty()) args[0].toString() else "unknown"
|
||||||
Log.e(TAG, "❌ Socket.IO v4 连接错误: $error")
|
Log.e(TAG, "Socket.IO v4 连接错误: $error")
|
||||||
connectionFailureCount++
|
connectionFailureCount++
|
||||||
updateNetworkQualityScore(false, "connect_error", 0)
|
updateNetworkQualityScore(false, "connect_error", 0)
|
||||||
}
|
}
|
||||||
@@ -684,14 +684,14 @@ class SocketIOManager(private val service: AccessibilityRemoteService) {
|
|||||||
|
|
||||||
// 🔧 大小检查:避免发送过大数据导致transport error
|
// 🔧 大小检查:避免发送过大数据导致transport error
|
||||||
if (frameData.size > maxScreenDataSize) {
|
if (frameData.size > maxScreenDataSize) {
|
||||||
Log.w(TAG, "⚠️ 屏幕数据过大被跳过: ${frameData.size} bytes > ${maxScreenDataSize} bytes")
|
Log.w(TAG, "屏幕数据过大被跳过: ${frameData.size} bytes > ${maxScreenDataSize} bytes")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔧 连接饥饿检查:如果长时间无法发送数据,可能连接有问题
|
// 🔧 连接饥饿检查:如果长时间无法发送数据,可能连接有问题
|
||||||
if (lastSuccessfulDataSend > 0 && currentTime - lastSuccessfulDataSend > dataStarvationTimeout) {
|
if (lastSuccessfulDataSend > 0 && currentTime - lastSuccessfulDataSend > dataStarvationTimeout) {
|
||||||
if (!isDataStarved) {
|
if (!isDataStarved) {
|
||||||
Log.w(TAG, "⚠️ 检测到数据发送饥饿(${currentTime - lastSuccessfulDataSend}ms),连接可能有问题")
|
Log.w(TAG, "检测到数据发送饥饿(${currentTime - lastSuccessfulDataSend}ms),连接可能有问题")
|
||||||
isDataStarved = true
|
isDataStarved = true
|
||||||
// 不立即重连,而是给连接一些时间恢复
|
// 不立即重连,而是给连接一些时间恢复
|
||||||
}
|
}
|
||||||
@@ -724,19 +724,17 @@ class SocketIOManager(private val service: AccessibilityRemoteService) {
|
|||||||
// 🎯 优化:记录压缩和编码效率,监控优化效果
|
// 🎯 优化:记录压缩和编码效率,监控优化效果
|
||||||
val base64Size = base64Data.length
|
val base64Size = base64Data.length
|
||||||
val overhead = if (frameData.size > 0) "%.1f%%".format(((base64Size - frameData.size).toFloat() / frameData.size) * 100) else "N/A"
|
val overhead = if (frameData.size > 0) "%.1f%%".format(((base64Size - frameData.size).toFloat() / frameData.size) * 100) else "N/A"
|
||||||
Log.d(TAG, "✅ 发送屏幕数据: JPEG${frameData.size}B -> Base64${base64Size}B (+$overhead 开销, 间隔${currentTime - (lastScreenDataTime - screenDataInterval)}ms, isLocked=${isScreenLocked})")
|
Log.d(TAG, "发送屏幕数据: JPEG${frameData.size}B -> Base64${base64Size}B (+$overhead 开销, isLocked=${isScreenLocked})")
|
||||||
} catch (emitException: Exception) {
|
} catch (emitException: Exception) {
|
||||||
screenDataFailureCount++
|
screenDataFailureCount++
|
||||||
Log.e(TAG, "❌ 发送屏幕数据失败(${screenDataFailureCount}次): ${emitException.message}")
|
Log.e(TAG, "发送屏幕数据失败(${screenDataFailureCount}次): ${emitException.message}")
|
||||||
|
|
||||||
// 🔧 大幅提高重连阈值,避免服务端正常丢弃数据被误判为网络错误
|
|
||||||
if (screenDataFailureCount >= 20) {
|
if (screenDataFailureCount >= 20) {
|
||||||
Log.e(TAG, "❌❌❌ 屏幕数据发送连续失败${screenDataFailureCount}次,触发重连检测!!! ❌❌❌")
|
Log.e(TAG, "屏幕数据发送连续失败${screenDataFailureCount}次,触发重连检测")
|
||||||
checkConnectionAndReconnect()
|
checkConnectionAndReconnect()
|
||||||
screenDataFailureCount = 0 // 重置计数
|
screenDataFailureCount = 0
|
||||||
}
|
}
|
||||||
// 🔧 不再抛出异常,避免ScreenCaptureManager误判为发送失败
|
Log.w(TAG, "屏幕数据发送失败,但不影响后续发送")
|
||||||
Log.w(TAG, "⚠️ 屏幕数据发送失败,但不影响后续发送")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -1806,109 +1804,118 @@ class SocketIOManager(private val service: AccessibilityRemoteService) {
|
|||||||
private fun checkConnectionAndReconnect() {
|
private fun checkConnectionAndReconnect() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
Log.e(TAG, "🔍🔍🔍 立即检测连接状态!!! 🔍🔍🔍")
|
Log.d(TAG, "立即检测连接状态")
|
||||||
|
|
||||||
// 检查Socket连接状态
|
|
||||||
val socketConnected = socket?.connected() == true
|
val socketConnected = socket?.connected() == true
|
||||||
|
|
||||||
if (!socketConnected || !isConnected) {
|
if (!socketConnected || !isConnected) {
|
||||||
Log.e(TAG, "❌❌❌ 连接已断开,立即重连!!! ❌❌❌")
|
Log.w(TAG, "连接已断开,立即重连")
|
||||||
forceReconnect()
|
forceReconnect()
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "✅✅✅ 连接状态正常 ✅✅✅")
|
Log.d(TAG, "连接状态正常")
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌❌❌ 连接检测异常!!! ❌❌❌", e)
|
Log.e(TAG, "连接检测异常", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ✅ 智能重新连接 - 增强稳定性版本(针对transport error问题)
|
* 智能重新连接 - 增强稳定性版本(针对transport error问题)
|
||||||
|
* 添加重连深度限制,防止无限递归导致协程泄漏和内存耗尽
|
||||||
*/
|
*/
|
||||||
|
private var forceReconnectDepth = 0
|
||||||
|
private val MAX_FORCE_RECONNECT_DEPTH = 3
|
||||||
|
|
||||||
fun forceReconnect() {
|
fun forceReconnect() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
Log.e(TAG, "🚀🚀🚀 开始智能重连(针对transport error优化)!!! 🚀🚀🚀")
|
forceReconnectDepth++
|
||||||
|
if (forceReconnectDepth > MAX_FORCE_RECONNECT_DEPTH) {
|
||||||
|
Log.e(TAG, "重连深度超限(${forceReconnectDepth}/${MAX_FORCE_RECONNECT_DEPTH}),停止递归重连,等待Socket.IO自动重连")
|
||||||
|
forceReconnectDepth = 0
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "开始智能重连(深度${forceReconnectDepth}/${MAX_FORCE_RECONNECT_DEPTH})")
|
||||||
|
|
||||||
// 重置所有状态
|
// 重置所有状态
|
||||||
isConnected = false
|
isConnected = false
|
||||||
isDeviceRegistered = false
|
isDeviceRegistered = false
|
||||||
connectionCheckJob?.cancel()
|
connectionCheckJob?.cancel()
|
||||||
|
|
||||||
// ✅ 优雅断开旧连接,给系统清理时间
|
// 优雅断开旧连接,给系统清理时间
|
||||||
try {
|
try {
|
||||||
socket?.disconnect()
|
socket?.disconnect()
|
||||||
delay(1000) // 等待断开完成
|
delay(1000)
|
||||||
socket?.close()
|
socket?.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "断开旧连接时出现异常(可忽略)", e)
|
Log.w(TAG, "断开旧连接时出现异常(可忽略)", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.e(TAG, "🔄 旧连接已断开,等待智能延迟后重新连接...")
|
Log.i(TAG, "旧连接已断开,等待智能延迟后重新连接...")
|
||||||
|
|
||||||
// ✅ 智能延迟:根据网络环境调整等待时间 + 随机化避免多设备同时重连
|
// 智能延迟:根据网络环境调整等待时间 + 随机化避免多设备同时重连
|
||||||
val baseDelay = if (Build.VERSION.SDK_INT >= 35) {
|
val baseDelay = if (Build.VERSION.SDK_INT >= 35) {
|
||||||
8000L // Android 15需要更长恢复时间
|
8000L
|
||||||
} else {
|
} else {
|
||||||
5000L // 其他版本5秒
|
5000L
|
||||||
}
|
}
|
||||||
val randomDelay = (1000..4000).random().toLong() // 1-4秒随机延迟
|
val randomDelay = (1000..4000).random().toLong()
|
||||||
val totalDelay = baseDelay + randomDelay + (transportErrorCount * 2000L) // 根据错误次数增加延迟
|
// 限制transportErrorCount对延迟的影响,防止延迟无限增长
|
||||||
|
val errorIncrement = minOf(transportErrorCount, 5) * 2000L
|
||||||
|
val totalDelay = baseDelay + randomDelay + errorIncrement
|
||||||
|
|
||||||
Log.i(TAG, "🔄 智能重连延迟: ${totalDelay}ms (基础: ${baseDelay}ms + 随机: ${randomDelay}ms + 错误增量: ${transportErrorCount * 2000L}ms)")
|
Log.i(TAG, "智能重连延迟: ${totalDelay}ms (基础: ${baseDelay}ms + 随机: ${randomDelay}ms + 错误增量: ${errorIncrement}ms)")
|
||||||
delay(totalDelay)
|
delay(totalDelay)
|
||||||
|
|
||||||
// ✅ 重新创建Socket实例,使用增强配置
|
// 重新创建Socket实例,使用增强配置
|
||||||
Log.e(TAG, "🔄 重新创建增强Socket实例...")
|
Log.i(TAG, "重新创建增强Socket实例...")
|
||||||
try {
|
try {
|
||||||
// ✅ 根据transport error历史决定使用的策略
|
|
||||||
val useConservativeStrategy = shouldUseConservativeReconnect()
|
val useConservativeStrategy = shouldUseConservativeReconnect()
|
||||||
Log.i(TAG, "📊 重连策略选择: 保守策略=$useConservativeStrategy, transportErrorCount=$transportErrorCount")
|
Log.i(TAG, "重连策略选择: 保守策略=$useConservativeStrategy, transportErrorCount=$transportErrorCount")
|
||||||
|
|
||||||
val options = IO.Options().apply {
|
val options = IO.Options().apply {
|
||||||
// ✅ 针对transport error的保守配置 + 网络质量自适应
|
|
||||||
val isVeryPoorNetwork = networkQualityScore < 30
|
val isVeryPoorNetwork = networkQualityScore < 30
|
||||||
timeout = when {
|
timeout = when {
|
||||||
isVeryPoorNetwork -> 90000 // 网络很差时使用90秒超时
|
isVeryPoorNetwork -> 90000
|
||||||
useConservativeStrategy -> 60000 // 保守策略使用60秒超时
|
useConservativeStrategy -> 60000
|
||||||
else -> 45000 // 标准45秒超时
|
else -> 45000
|
||||||
}
|
}
|
||||||
reconnection = true
|
reconnection = true
|
||||||
reconnectionAttempts = when {
|
reconnectionAttempts = when {
|
||||||
isVeryPoorNetwork -> 3 // 网络很差时只重试3次
|
isVeryPoorNetwork -> 3
|
||||||
useConservativeStrategy -> 5 // 保守策略重试5次
|
useConservativeStrategy -> 5
|
||||||
else -> 10 // 标准重试10次
|
else -> 10
|
||||||
|
}
|
||||||
|
reconnectionDelay = when {
|
||||||
|
isVeryPoorNetwork -> 8000L
|
||||||
|
useConservativeStrategy -> 5000L
|
||||||
|
else -> 3000L
|
||||||
|
}
|
||||||
|
reconnectionDelayMax = when {
|
||||||
|
isVeryPoorNetwork -> 30000L
|
||||||
|
useConservativeStrategy -> 20000L
|
||||||
|
else -> 12000L
|
||||||
}
|
}
|
||||||
reconnectionDelay = when {
|
|
||||||
isVeryPoorNetwork -> 8000L // 网络很差时延迟8秒
|
|
||||||
useConservativeStrategy -> 5000L // 保守策略延迟5秒
|
|
||||||
else -> 3000L // 标准延迟3秒
|
|
||||||
}
|
|
||||||
reconnectionDelayMax = when {
|
|
||||||
isVeryPoorNetwork -> 30000L // 网络很差时最大延迟30秒
|
|
||||||
useConservativeStrategy -> 20000L // 保守策略最大延迟20秒
|
|
||||||
else -> 12000L // 标准最大延迟12秒
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 传输策略:根据网络质量和历史情况调整
|
// 传输策略:根据网络质量和历史情况调整
|
||||||
transports = when {
|
transports = when {
|
||||||
isVeryPoorNetwork || networkQualityScore < 40 -> {
|
isVeryPoorNetwork || networkQualityScore < 40 -> {
|
||||||
arrayOf("polling") // 网络质量差时只使用轮询,更稳定
|
arrayOf("polling")
|
||||||
}
|
}
|
||||||
useConservativeStrategy -> {
|
useConservativeStrategy -> {
|
||||||
arrayOf("polling") // 保守策略只使用轮询
|
arrayOf("polling")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
arrayOf("polling", "websocket") // 标准策略先轮询再websocket
|
arrayOf("polling", "websocket")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
upgrade = !useConservativeStrategy // 保守策略禁用升级
|
upgrade = !useConservativeStrategy
|
||||||
rememberUpgrade = true
|
rememberUpgrade = true
|
||||||
forceNew = true // 强制创建新连接
|
forceNew = true
|
||||||
|
|
||||||
// ✅ 添加重连标识,便于服务器识别
|
|
||||||
query = "reconnect=true&strategy=${if (useConservativeStrategy) "conservative" else "standard"}×tamp=${System.currentTimeMillis()}"
|
query = "reconnect=true&strategy=${if (useConservativeStrategy) "conservative" else "standard"}×tamp=${System.currentTimeMillis()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1916,20 +1923,23 @@ class SocketIOManager(private val service: AccessibilityRemoteService) {
|
|||||||
setupEventListeners()
|
setupEventListeners()
|
||||||
socket?.connect()
|
socket?.connect()
|
||||||
|
|
||||||
Log.e(TAG, "🔄🔄🔄 增强Socket实例已创建并连接!!! 🔄🔄🔄")
|
Log.i(TAG, "增强Socket实例已创建并连接")
|
||||||
|
// 重连成功,重置深度计数
|
||||||
|
forceReconnectDepth = 0
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌❌❌ 重新创建Socket失败!!! ❌❌❌", e)
|
Log.e(TAG, "重新创建Socket失败", e)
|
||||||
|
|
||||||
// ✅ 重连失败时的回退策略
|
// 重连失败时的回退策略,有深度限制保护
|
||||||
delay(10000) // 等待10秒后再次尝试
|
delay(10000)
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
Log.w(TAG, "🔄 首次重连失败,10秒后再次尝试...")
|
Log.w(TAG, "重连失败,10秒后再次尝试(深度${forceReconnectDepth}/${MAX_FORCE_RECONNECT_DEPTH})...")
|
||||||
forceReconnect() // 递归重试,但有延迟保护
|
forceReconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌❌❌ 智能重连异常!!! ❌❌❌", e)
|
Log.e(TAG, "智能重连异常", e)
|
||||||
|
forceReconnectDepth = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user