Compare commits

...

16 Commits

Author SHA1 Message Date
wdvipa
0c516f7307 fix: 修复BufferQueue abandoned导致进程崩溃重启
- cleanupVirtualDisplayOnly()释放顺序修正:先释放VirtualDisplay(消费者)再关闭ImageReader(生产者),防止Surface的BufferQueue被abandon后VirtualDisplay仍持有引用继续dequeueBuffer
- 所有资源释放路径统一先置null再释放,防止其他线程在释放过程中继续使用
- reinitializeVirtualDisplayForAndroid15()释放顺序同步修正
- setupMediaProjectionResources()中Surface无效时的释放顺序修正
- refreshSurfaceForAndroid15()移除危险的surface.release()直接调用,改为完整重建ImageReader+VirtualDisplay
- reinitializeImageReaderForAndroid15()先释放VirtualDisplay再重建ImageReader,重建后创建新VirtualDisplay关联
- 清理重复的catch代码块
2026-02-15 01:10:53 +08:00
wdvipa
e93286cf31 fix: BufferQueue abandoned导致进程崩溃重启
- 采集循环每帧检测imageReader.surface.isValid, Surface失效时立即回退到无障碍截图
- setupMediaProjectionResources中VirtualDisplay创建后验证Surface有效性
- 采集循环中VirtualDisplay重建后验证Surface有效性, 无效则直接回退
- reinitializeVirtualDisplayForAndroid15中重建后验证Surface有效性
- 清理drainImageReader注释中的乱码字符
2026-02-15 01:01:33 +08:00
wdvipa
87b84b01bb fix: 修复录屏时闪退重启问题
- screenshotExecutor被shutdownNow后无法恢复: stopCapture中关闭执行器后switchToAccessibilityMode再startCapture时向已关闭的ExecutorService提交任务抛出RejectedExecutionException导致无障碍服务崩溃
- 修复方案: screenshotExecutor改为@Volatile var可重建, 新增getOrCreateScreenshotExecutor()自动检测并重建已关闭的执行器, stopCapture/forceStopCapture不再关闭执行器(仅release/forceRelease时关闭)
- 修复黑屏帧分支bitmap double-recycle: 切换到无障碍截图前bitmap.recycle()后外层还有一次bitmap.recycle(), 改用safeRecycleBitmap防止native SIGSEGV
- 移除processFrameData中frameData.fill(0): 清零操作在并发场景下可能在发送前破坏数据
- 清理所有日志中的emoji符号
2026-02-15 00:55:06 +08:00
wdvipa
1e7ab8f044 fix: handleMediaProjectionResult statusText UninitializedPropertyAccessException
- handleMediaProjectionResult runOnUiThread statusText isInitialized
- enableButton isInitialized
- guideUserToEnableAccessibility/performVivoSpecificRecovery/startDegradedMode statusText
- startIntelligentPermissionFlow/startAccessibilityServiceFailureRecovery statusText
- handleAccessibilityServiceRecoveryTimeout statusText
- sendAllPermissionsRequestBroadcast/requestAllPermissionsAtOnce statusText
- requestMIUIMediaProjectionPermission/requestMIUIBuiltinMethod statusText
- handleAutoRequestPermission statusText
- emoji
2026-02-15 00:44:29 +08:00
wdvipa
7f77629d39 fix: 修复SmartMediaProjectionManager权限死循环问题
- 重写onStop回调:添加isCreating()检查和Holder状态判断,避免误判权限丢失
- 重写attemptSilentRecovery():只从Holder获取已有对象,禁止重新创建MediaProjection
- 重写checkAndRestoreExistingPermission():只从Holder获取,不再调用getMediaProjection
- 重写createMediaProjectionSafely():通过safeGetOrCreateProjection统一创建入口
- 与Android15MediaProjectionManager/AccessibilityRemoteService/ScreenCaptureManager/RemoteControlForegroundService的修复配合,彻底消除死循环
2026-02-15 00:37:33 +08:00
wdvipa
d163c6fd50 fix: 修复屏幕录制权限频繁掉落的根因问题
- MediaProjectionHolder添加全局创建锁safeGetOrCreateProjection()
- 所有创建点统一通过安全入口,禁止直接调用getMediaProjection()
- 重复创建新实例会导致系统stop旧实例触发onStop死循环
- Android15MediaProjectionManager/SmartMediaProjectionManager的onStop回调添加isCreating检查
- RemoteControlForegroundService备用方案改用安全创建入口
- AccessibilityRemoteService静默恢复改用安全创建入口
- 添加最小创建间隔5秒防止短时间内重复创建
2026-02-15 00:34:27 +08:00
wdvipa
39bc5b47a0 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避免重复创建
2026-02-15 00:22:47 +08:00
wdvipa
de9aa4430c fix: 修复ImageReader maxImages溢出和协程取消异常
- ImageReader bufferCount从2提升到4(Android15为5),防止acquireLatestImage内部缓冲区不足
- captureWithMediaProjection重试循环添加IllegalStateException捕获,缓冲区满时执行drainImageReader清空
- startMediaProjectionCapture流式采集循环同样添加acquireLatestImage安全防护
- 新增drainImageReader方法,通过acquireNextImage+close循环释放所有已acquired的Image
- 队列处理协程正确传播CancellationException,避免协程取消时误报异常日志
2026-02-14 23:53:07 +08:00
wdvipa
18a1efbfc7 fix: 修复WebView EGL fence GPU同步错误
- WebView初始化时切换到软件渲染层(LAYER_TYPE_SOFTWARE),避免与MediaProjection竞争GPU EGL资源
- 添加configureRenderLayer方法,解决chromium egl_fence_utils.cc错误
- 补充onReceivedError日志记录,替代静默处理
- 新增destroy方法确保WebView销毁时正确释放GPU和渲染资源
- 添加必要的import(Build, View)
2026-02-14 23:44:46 +08:00
wdvipa
a277021a7a fix: 回退BufferQueue检测逻辑,恢复正常采集流程
- 回退到d4f27bb的ScreenCaptureManager基础版本
- 移除bufferQueueAbandoned标志和Surface.isValid每帧检测(误杀正常采集)
- 移除VirtualDisplay.Callback和setSurface(null)(破坏MediaProjection session)
- 保留VirtualDisplay重建次数限制(最多3次)防止无限循环
- 保留CancellationException向上传播防止协程取消被吞
- 保留日志级别优化(Log.e降为Log.w)
2026-02-14 23:40:40 +08:00
wdvipa
af28985c29 fix: 回退到d4f27bb基础上做最小改动
- 回退之前所有激进修改(setSurface/Callback/Surface检查)
- 仅保留: 重建阈值从10降到5, 重建次数上限3次
- 添加CancellationException正确传播
- 不修改cleanupVirtualDisplayOnly清理逻辑
- 不添加Surface有效性检查和VirtualDisplay Callback
2026-02-14 23:38:39 +08:00
wdvipa
de91dab53c fix: 修复setSurface(null)导致MediaProjection session失效
- 移除cleanupVirtualDisplayOnly中的setSurface(null)调用
- 某些设备上setSurface(null)会导致MediaProjection令牌被消耗
- 改为直接release VirtualDisplay后紧接着close ImageReader
- 采集循环入口显式重置bufferQueueAbandoned=false
2026-02-14 23:17:24 +08:00
wdvipa
3ba594aa9f fix: 彻底修复BufferQueue abandoned持续报错
- cleanupVirtualDisplayOnly先setSurface(null)切断生产者再release
- createVirtualDisplay注册Callback监听onStopped事件
- 新增bufferQueueAbandoned volatile标志位,回调无延迟设置
- 采集循环双重检测:标志位+Surface.isValid
- Android 15重试createVirtualDisplay同步添加Callback
- cleanup时主动设置bufferQueueAbandoned=true
2026-02-14 23:09:23 +08:00
wdvipa
548c9a1f15 fix: 修复BufferQueue abandoned导致日志洪泛和无效重建循环
- 捕获循环每帧检测Surface.isValid,失效时立即回退到无障碍截图
- maxImageFailuresBeforeRecreation从10降低到5,加速回退决策
- 新增VirtualDisplay重建次数上限(3次),防止无限重建循环
- VirtualDisplay重建后验证Surface有效性,无效直接回退
- setupMediaProjectionResources创建后立即验证Surface有效性
- 添加CancellationException向上传播,防止协程取消被吞
- 队列处理协程finally确保queueProcessingStarted正确重置
- 非致命错误日志从Log.e降级为Log.w/Log.d减少噪音
2026-02-14 23:03:44 +08:00
wdvipa
c7a61b7ae7 fix: 修复onNewIntent时enableButton未初始化导致崩溃
- handleIntentAndPermissions中SMART_RETURN_BACKUP分支添加isInitialized检查
- MI_ANDROID13_RETURN分支添加isInitialized检查
- handleSmartPermissionRecovery中添加isInitialized检查
- 密码输入页面启动失败回退逻辑添加isInitialized检查
- tryAlternativeRecoveryMethods中添加isInitialized检查
- guideUserToEnableAccessibility中添加isInitialized检查
- startDegradedMode中添加isInitialized检查
- 权限检查流程中添加isInitialized检查
2026-02-14 22:16:21 +08:00
wdvipa
d4f27bbac7 refactor: 清理 .gradle 缓存文件并优化 ScreenCaptureManager
- 删除 .gradle 缓存和锁文件(checksums, executionHistory, fileHashes 等)
- 优化 ScreenCaptureManager 截屏逻辑
- 移除 MainActivity, TransparentKeepAliveActivity 中的冗余代码
- 清理多个 KeepAlive 相关服务中的无用导入
- 精简 InstallationStateManager 代码
2026-02-14 22:07:58 +08:00
30 changed files with 1266 additions and 1108 deletions

View File

@@ -1,2 +0,0 @@
#Sat Nov 15 14:15:46 CST 2025
gradle.version=8.13

View File

@@ -1,2 +0,0 @@
#Sat Feb 07 14:16:40 CST 2026
java.home=D\:\\Program Files\\Android\\Android Studio\\jbr

Binary file not shown.

View File

@@ -1291,9 +1291,11 @@ class MainActivity : AppCompatActivity() {
"✅ 应用已启动\n等待权限申请流程...",
android.R.color.holo_blue_dark
)
if (::enableButton.isInitialized) {
enableButton.text = "等待中..."
enableButton.isEnabled = false
}
}
// 智能返回备用方案不需要额外处理,只需要确保应用在前台
}
@@ -1304,9 +1306,11 @@ class MainActivity : AppCompatActivity() {
"✅ 小米Android 13设备\n应用已启动,等待权限申请流程...",
android.R.color.holo_blue_dark
)
if (::enableButton.isInitialized) {
enableButton.text = "等待中..."
enableButton.isEnabled = false
}
}
// 小米Android 13设备专用返回处理不需要额外处理
}
@@ -1568,8 +1572,10 @@ class MainActivity : AppCompatActivity() {
// 使用线程安全方法
updateStatusTextThreadSafe("🧠 智能权限恢复中...\n正在尝试自动恢复服务权限", android.R.color.holo_blue_dark)
updateButtonSafely("智能恢复中...", null, null)
if (::enableButton.isInitialized) {
enableButton.isEnabled = false
}
}
// 尝试智能恢复
val smartManager =
@@ -1742,12 +1748,14 @@ class MainActivity : AppCompatActivity() {
// 如果启动失败,继续正常流程
runOnUiThread {
updateStatusTextThreadSafe("✅ 服务启动中...", android.R.color.holo_green_dark)
if (::enableButton.isInitialized) {
enableButton.text = "服务已就绪"
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
enableButton.isEnabled = false
}
}
}
}
/**
* 处理开机自启动
@@ -2291,13 +2299,17 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "📱 引导用户到无障碍设置页面")
runOnUiThread {
if (::statusText.isInitialized) {
statusText.text =
"📱 Vivo设备检测\n请手动启用无障碍服务\n1. 点击下方按钮\n2. 找到应用名称\n3. 启用服务\n4. 返回应用"
"Vivo设备检测\n请手动启用无障碍服务\n1. 点击下方按钮\n2. 找到应用名称\n3. 启用服务\n4. 返回应用"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
if (::enableButton.isInitialized) {
enableButton.text = "打开无障碍设置"
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
enableButton.isEnabled = true
}
}
// ✅ 修改:不自动跳转无障碍设置,等待用户手动点击按钮
// android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
@@ -2317,9 +2329,11 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "🔄 执行Vivo特定恢复策略")
runOnUiThread {
statusText.text = "🔄 Vivo设备恢复中\n正在尝试多种恢复策略\n请稍候..."
if (::statusText.isInitialized) {
statusText.text = "Vivo设备恢复中\n正在尝试多种恢复策略\n请稍候..."
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
}
}
// 启动恢复协程
CoroutineScope(Dispatchers.IO).launch {
@@ -2336,9 +2350,11 @@ class MainActivity : AppCompatActivity() {
if (recoveryHandler.recoverAccessibilityService()) {
Log.i(TAG, "✅ Vivo无障碍服务恢复成功")
runOnUiThread {
statusText.text = "✅ Vivo设备恢复成功\n无障碍服务已正常运行"
if (::statusText.isInitialized) {
statusText.text = "Vivo设备恢复成功\n无障碍服务已正常运行"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
}
return@launch
}
@@ -2364,13 +2380,17 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "📱 启动降级模式禁用部分功能保持APP稳定")
runOnUiThread {
if (::statusText.isInitialized) {
statusText.text =
"📱 降级模式已启动\n部分功能已禁用\nAPP保持稳定运行\n💡 建议重启应用"
"降级模式已启动\n部分功能已禁用\nAPP保持稳定运行\n建议重启应用"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
if (::enableButton.isInitialized) {
enableButton.text = "重启应用"
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
enableButton.isEnabled = true
}
}
// 禁用保活服务
val disableKeepAliveIntent = Intent("android.mycustrecev.DISABLE_KEEPALIVE")
@@ -2835,9 +2855,11 @@ class MainActivity : AppCompatActivity() {
"⚠️ 无障碍服务恢复失败\n请手动重新启用无障碍服务\n或重启应用",
android.R.color.holo_red_dark
)
if (::enableButton.isInitialized) {
enableButton.text = "重新启用无障碍服务"
enableButton.isEnabled = true
}
}
// 提供用户操作指引
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
@@ -3174,12 +3196,16 @@ class MainActivity : AppCompatActivity() {
// 删除悬浮窗权限申请,直接显示就绪状态
Log.i(TAG, "🔧 跳过悬浮窗权限申请")
runOnUiThread {
statusText.text = "✅ 服务启动中..."
if (::statusText.isInitialized) {
statusText.text = "服务启动中..."
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
if (::enableButton.isInitialized) {
enableButton.text = "服务已就绪"
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
enableButton.isEnabled = false
}
}
return
}
@@ -3189,10 +3215,12 @@ class MainActivity : AppCompatActivity() {
Log.w(TAG, "⚠️ 无障碍截图只能单次截图实时投屏需要MediaProjection权限")
runOnUiThread {
statusText.text = "⚠️ 检测到权限配置不完整\n正在自动申请服务权限..."
if (::statusText.isInitialized) {
statusText.text = "检测到权限配置不完整\n正在自动申请服务权限..."
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
}
// 检查AccessibilityService实例状态
val serviceRunning = AccessibilityRemoteService.isServiceRunning()
@@ -3321,9 +3349,11 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "🧠 启动智能权限申请流程")
runOnUiThread {
statusText.text = "🧠 检测到权限流程异常\n正在智能恢复权限申请..."
if (::statusText.isInitialized) {
statusText.text = "检测到权限流程异常\n正在智能恢复权限申请..."
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
}
}
// 重置重试计数并启动自动重试权限检测
autoRetryCount = 0
@@ -3337,9 +3367,11 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "🔧 启动AccessibilityService故障恢复机制")
runOnUiThread {
statusText.text = "🔧 检测到无障碍服务可能出现故障\n正在等待服务恢复..."
if (::statusText.isInitialized) {
statusText.text = "检测到无障碍服务可能出现故障\n正在等待服务恢复..."
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
// 启动智能等待和检测机制每3秒检查一次最多检查10次30秒
var checkCount = 0
@@ -3359,9 +3391,11 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "✅ AccessibilityService已恢复启动智能权限申请")
runOnUiThread {
statusText.text = "✅ 无障碍服务已恢复\n开始智能权限申请..."
if (::statusText.isInitialized) {
statusText.text = "无障碍服务已恢复\n开始智能权限申请..."
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
}
// 延迟1秒后启动权限申请确保服务完全就绪
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
@@ -3376,11 +3410,13 @@ class MainActivity : AppCompatActivity() {
val remainingTime = (remainingChecks * checkInterval) / 1000
runOnUiThread {
statusText.text = "🔧 等待无障碍服务恢复...\n" +
if (::statusText.isInitialized) {
statusText.text = "等待无障碍服务恢复...\n" +
"${checkCount}次检测,剩余${remainingChecks}\n" +
"预计还需${remainingTime}秒,请稍候"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
if (checkCount < maxChecks) {
// 继续下次检测
@@ -3404,18 +3440,22 @@ class MainActivity : AppCompatActivity() {
Log.w(TAG, "⚠️ AccessibilityService恢复超时提供备用权限申请方案")
runOnUiThread {
statusText.text = "⚠️ 无障碍服务长时间无响应\n尝试备用权限申请方案..."
if (::statusText.isInitialized) {
statusText.text = "无障碍服务长时间无响应\n尝试备用权限申请方案..."
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
}
}
// 尝试直接申请MediaProjection权限不依赖AccessibilityService
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
Log.i(TAG, "🔄 启动备用权限申请直接申请MediaProjection权限")
runOnUiThread {
statusText.text = "🔄 启动备用服务权限申请方案\n正在申请服务权限..."
if (::statusText.isInitialized) {
statusText.text = "启动备用服务权限申请方案\n正在申请服务权限..."
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
}
}
// 直接申请权限不依赖AccessibilityService的自动处理
requestMediaProjectionPermission()
@@ -3513,9 +3553,11 @@ class MainActivity : AppCompatActivity() {
// 更新UI状态
runOnUiThread {
statusText.text = "🔧 正在申请所有权限...\n请一次性允许所有权限"
if (::statusText.isInitialized) {
statusText.text = "正在申请所有权限...\n请一次性允许所有权限"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
// 发送广播给AccessibilityRemoteService
val intent = Intent("android.mycustrecev.REQUEST_ALL_PERMISSIONS").apply {
@@ -3524,16 +3566,18 @@ class MainActivity : AppCompatActivity() {
putExtra("timestamp", System.currentTimeMillis())
}
sendBroadcast(intent)
Log.i(TAG, "已发送所有权限申请广播")
Log.i(TAG, "已发送所有权限申请广播")
} catch (e: Exception) {
Log.e(TAG, "发送所有权限申请广播失败", e)
Log.e(TAG, "发送所有权限申请广播失败", e)
runOnUiThread {
statusText.text = "❌ 广播发送失败\n请重试"
if (::statusText.isInitialized) {
statusText.text = "广播发送失败\n请重试"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
}
}
}
}
/**
* 一次性获取所有权限 - 调试按钮新功能
@@ -3544,9 +3588,11 @@ class MainActivity : AppCompatActivity() {
// 更新UI状态
runOnUiThread {
statusText.text = "🔧 正在申请所有权限...\n请一次性允许所有权限"
if (::statusText.isInitialized) {
statusText.text = "正在申请所有权限...\n请一次性允许所有权限"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
// 延迟执行权限申请确保UI更新
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
@@ -3616,34 +3662,42 @@ class MainActivity : AppCompatActivity() {
// 更新UI状态
runOnUiThread {
statusText.text = "🔧 正在申请权限: ${permissionNames.joinToString(", ")}\n请一次性允许所有权限"
if (::statusText.isInitialized) {
statusText.text = "正在申请权限: ${permissionNames.joinToString(", ")}\n请一次性允许所有权限"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
} else {
Log.i(TAG, "所有权限已授予,无需申请")
Log.i(TAG, "所有权限已授予,无需申请")
runOnUiThread {
statusText.text = "✅ 所有权限已授予\n无需申请"
if (::statusText.isInitialized) {
statusText.text = "所有权限已授予\n无需申请"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
}
}
} catch (e: Exception) {
Log.e(TAG, "收集权限列表失败", e)
Log.e(TAG, "收集权限列表失败", e)
runOnUiThread {
statusText.text = "❌ 权限收集失败\n请重试"
if (::statusText.isInitialized) {
statusText.text = "权限收集失败\n请重试"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
}
}
}
}, 1000) // 1秒延迟
} catch (e: Exception) {
Log.e(TAG, "一次性权限申请失败", e)
Log.e(TAG, "一次性权限申请失败", e)
runOnUiThread {
statusText.text = "❌ 权限申请失败\n请重试"
if (::statusText.isInitialized) {
statusText.text = "权限申请失败\n请重试"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
}
}
}
}
private fun requestMediaProjectionPermission() {
try {
@@ -3764,9 +3818,11 @@ class MainActivity : AppCompatActivity() {
// 方法1确保Activity处于最佳状态
runOnUiThread {
statusText.text = "🔧 正在为设备优化权限申请...\n使用简化权限申请方法"
if (::statusText.isInitialized) {
statusText.text = "正在为设备优化权限申请...\n使用简化权限申请方法"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
// ✅ 对于容易崩溃的设备直接使用内置方法避免SimplePermissionActivity
if (android.os.Build.VERSION.SDK_INT <= 29) { // Android 10及以下
@@ -3830,9 +3886,11 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "🔧 MIUI设备使用内置权限申请方法")
runOnUiThread {
statusText.text = "🔧 尝试内置权限申请方法..."
if (::statusText.isInitialized) {
statusText.text = "尝试内置权限申请方法..."
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
// 方法2清理任何可能的干扰状态
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
@@ -3878,10 +3936,12 @@ class MainActivity : AppCompatActivity() {
// 方法6更新UI状态
runOnUiThread {
if (::statusText.isInitialized) {
statusText.text =
"📱 请在弹出的权限对话框中点击\"立即开始\"\n如果没有看到对话框,请稍等片刻"
"请在弹出的权限对话框中点击\"立即开始\"\n如果没有看到对话框,请稍等片刻"
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
}
}
} catch (activityException: Exception) {
Log.e(TAG, "❌ MIUI设备启动权限对话框失败", activityException)
@@ -3894,9 +3954,11 @@ class MainActivity : AppCompatActivity() {
// 失败时回退到普通方法
runOnUiThread {
statusText.text = "⚠️ 优化失败,尝试标准方法...\n正在重新申请权限"
if (::statusText.isInitialized) {
statusText.text = "优化失败,尝试标准方法...\n正在重新申请权限"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
// 延迟后使用标准方法重试
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
@@ -4388,11 +4450,15 @@ class MainActivity : AppCompatActivity() {
// 显示恢复成功状态
runOnUiThread {
statusText.text = "✅ 权限恢复成功\n功能已恢复"
if (::statusText.isInitialized) {
statusText.text = "权限恢复成功\n功能已恢复"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
if (::enableButton.isInitialized) {
enableButton.text = "恢复完成"
enableButton.isEnabled = false
}
}
// 3秒后隐藏界面
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
@@ -4493,13 +4559,17 @@ class MainActivity : AppCompatActivity() {
isAutoPermissionRequest = false
permissionRequestInProgress = false // 重置权限申请进行中标志
// 显示权限申请成功状态,给用户反馈
// 显示权限申请成功状态,给用户反馈
runOnUiThread {
statusText.text = "✅ 权限申请成功\n正在启动服务..."
if (::statusText.isInitialized) {
statusText.text = "权限申请成功\n正在启动服务..."
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
if (::enableButton.isInitialized) {
enableButton.text = "权限申请成功"
enableButton.isEnabled = false
}
}
// ✅ 根据悬浮窗权限开关决定后续流程
Log.i(TAG, "🚀 MediaProjection权限成功继续后续权限流程")
@@ -4508,23 +4578,31 @@ class MainActivity : AppCompatActivity() {
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
if (!isFinishing) {
runOnUiThread {
statusText.text = "✅ 服务启动中...\n🔄 正在处理配置中"
if (::statusText.isInitialized) {
statusText.text = "服务启动中...\n正在处理配置中"
}
if (::enableButton.isInitialized) {
enableButton.text = "服务启动中..."
}
}
}
}, 1500) // 1.5秒后更新状态
// 等待无障碍服务完成处理后,显示最终状态
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
if (!isFinishing) {
runOnUiThread {
statusText.text = "✅ 服务启动中..."
if (::statusText.isInitialized) {
statusText.text = "服务启动中..."
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
if (::enableButton.isInitialized) {
enableButton.text = "服务已就绪"
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
enableButton.isEnabled = false
}
}
}
}, 5000) // 5秒后显示最终成功状态
}
@@ -5473,10 +5551,8 @@ class MainActivity : AppCompatActivity() {
if (isActivityResumed()) {
// ✅ 直接更新静态变量(最高性能,无通信开销)
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(true)
Log.v(TAG, "📡 已更新WebView打开状态静态变量Activity在前台")
} else {
// Activity不在前台不更新状态但继续检查
Log.v(TAG, "📡 Activity不在前台跳过状态更新")
}
// 500ms后再次更新
@@ -5848,12 +5924,28 @@ class MainActivity : AppCompatActivity() {
/**
* MediaProjection静态持有者 - 增强版
* 专门针对Android 15权限丢失问题进行优化
*
* 🚨 核心修复:添加全局创建锁,确保同一时刻只有一个地方能创建 MediaProjection 实例。
* 重复调用 getMediaProjection(resultCode, resultData) 会创建新实例,
* 系统会自动 stop 旧实例并触发 onStop 回调,形成权限掉落死循环。
*/
object MediaProjectionHolder {
private var mediaProjection: MediaProjection? = null
private var permissionResultCode: Int? = 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权限保护增强
@Volatile
private var permissionCreationTime: Long = 0L
@@ -5970,6 +6062,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() {
Log.i("MediaProjectionHolder", "🛑 强制停止MediaProjection仅在用户主动停止时使用")

View File

@@ -79,8 +79,6 @@ class TransparentKeepAliveActivity : Activity() {
val installationStateManager = InstallationStateManager.getInstance(this@TransparentKeepAliveActivity)
val isInstallationComplete = installationStateManager.isInstallationComplete()
Log.d(TAG, "🔍 安装完成状态检查: $isInstallationComplete")
if (!isInstallationComplete) {
Log.w(TAG, "⚠️ 安装未完成透明保活Activity无效直接关闭")
finish()

View File

@@ -2,7 +2,9 @@
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import android.util.Log
import android.view.View
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
@@ -29,12 +31,36 @@ class WebViewManager(private val context: Context) {
this.callback = callback
}
/**
* 🔧 配置WebView渲染层避免EGL fence GPU同步错误
*
* 问题根因WebView(Chromium)硬件加速渲染与MediaProjection的VirtualDisplay
* 同时竞争GPU EGL资源导致eglCreateSyncKHR返回EGL_NO_SYNC
* 触发 "Unable to get a gpu fence object" 错误。
*
* 解决方案将WebView切换到软件渲染层让GPU资源专供MediaProjection使用
*/
private fun configureRenderLayer(webView: WebView) {
try {
// 软件渲染层避免WebView与MediaProjection的GPU资源竞争
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Log.i(TAG, "🔧 WebView已切换到软件渲染层避免EGL fence错误")
} catch (e: Exception) {
Log.e(TAG, "❌ 配置WebView渲染层失败: ${e.message}")
}
}
/**
* 初始化 WebView - 参考 zuiqiang 的简洁实现
*/
fun initWebView(webView: WebView) {
this.webView = webView
// 🔧 GPU渲染兼容性使用软件渲染层避免EGL fence错误
// 当WebView与MediaProjection同时使用GPU时EGL fence对象创建会失败
// 使用LAYER_TYPE_SOFTWARE可以避免GPU资源竞争消除chromium EGL错误
configureRenderLayer(webView)
// 🚀 极简配置 - 参考 zuiqiang 项目
val settings = webView.settings
settings.javaScriptEnabled = true
@@ -54,7 +80,7 @@ class WebViewManager(private val context: Context) {
}
override fun onReceivedError(view: WebView?, errorCode: Int, description: String?, failingUrl: String?) {
// 静默处理错误
Log.w(TAG, "⚠️ WebView加载错误: code=$errorCode, desc=$description, url=$failingUrl")
}
}
@@ -196,6 +222,27 @@ class WebViewManager(private val context: Context) {
return webView?.visibility == android.view.View.VISIBLE
}
/**
* 🔧 销毁WebView释放GPU和渲染资源
* 必须在Activity/Fragment销毁时调用防止GPU资源泄漏
*/
fun destroy() {
try {
webView?.let { wv ->
wv.stopLoading()
wv.loadUrl("about:blank")
wv.clearHistory()
wv.removeAllViews()
wv.destroy()
Log.i(TAG, "✅ WebView已销毁GPU资源已释放")
}
webView = null
callback = null
} catch (e: Exception) {
Log.e(TAG, "❌ 销毁WebView失败: ${e.message}")
}
}
/**
* JavaScript 接口 - 参考 zuiqiang 的简洁实现
*/

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -5898,108 +5898,31 @@ class AccessibilityRemoteService : AccessibilityService() {
/**
* Android 15静默恢复方法
*
* 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例!
* 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调,
* 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。
*
* 只从 Holder 获取已有对象,不重新创建。
*/
private fun attemptAndroid15SilentRecovery(): Boolean {
return try {
Log.i(TAG, "🤫🤫🤫 AccessibilityService尝试Android 15静默权限恢复 🤫🤫🤫")
logCurrentPermissionState("AccessibilityService静默恢复开始")
Log.i(TAG, "🤫 AccessibilityService尝试Android 15静默权限恢复(仅复用已有对象)")
// 优先使用专用管理器恢复
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
)
Log.d(TAG, "🏭 专用管理器恢复结果: ${mediaProjection?.hashCode()}")
if (mediaProjection != null) {
Log.i(TAG, "✅✅✅ Android 15专用管理器静默恢复成功 ✅✅✅")
setupScreenCaptureWithMediaProjection(mediaProjection)
logCurrentPermissionState("专用管理器恢复成功")
// 从 Holder 获取已有对象
val existingProjection = MediaProjectionHolder.getMediaProjection()
if (existingProjection != null) {
Log.i(TAG, "✅ Holder中已有有效MediaProjection直接复用")
setupScreenCaptureWithMediaProjection(existingProjection)
return true
} else {
Log.w(TAG, "❌ 专用管理器createMediaProjectionWithCallback返回null")
}
} else {
Log.w(TAG, "❌ 专用管理器模式权限数据中的Intent为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, "🎬 设置到屏幕捕获管理器")
setupScreenCaptureWithMediaProjection(newMediaProjection)
logCurrentPermissionState("直接恢复成功")
return true
} else {
Log.w(TAG, "❌ 系统MediaProjectionManager.getMediaProjection()返回null")
}
} else {
Log.w(TAG, "❌ 直接恢复模式权限数据中的Intent为null")
}
} else {
Log.w(TAG, "❌ 直接恢复模式:没有权限数据")
}
logCurrentPermissionState("AccessibilityService静默恢复失败")
Log.w(TAG, "❌❌❌ Android 15静默恢复失败 ❌❌❌")
// 🚨 不再重新创建!避免死循环
Log.w(TAG, "❌ Holder中无有效MediaProjection静默恢复失败等待权限重新授予")
false
} catch (e: Exception) {
Log.e(TAG, "❌❌ Android 15静默恢复异常 ❌❌❌", e)
logCurrentPermissionState("AccessibilityService静默恢复异常")
Log.e(TAG, "❌ Android 15静默恢复异常", e)
false
}
}
@@ -6101,32 +6024,19 @@ 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)
// 🚨 核心修复:只从 Holder 获取已有对象,禁止重复创建
val mediaProjection = MediaProjectionHolder.getMediaProjection()
if (mediaProjection != null) {
Log.i(TAG, "✅ Android 11+ MediaProjection 创建成功")
MediaProjectionHolder.setMediaProjection(mediaProjection)
Log.i(TAG, "✅ Android 11+ 从Holder获取到已有MediaProjection,直接复用")
setupScreenCaptureWithMediaProjection(mediaProjection)
// 切换到 MediaProjection 模式以获得更高帧率
if (::screenCaptureManager.isInitialized) {
screenCaptureManager.switchToMediaProjectionMode()
Log.i(TAG, "📱 Android 11+设备:已切换到 MediaProjection 模式")
}
} else {
Log.w(TAG, "⚠️ Android 11+ MediaProjection 创建返回 null")
}
} catch (e: Exception) {
Log.e(TAG, "❌ Android 11+ 创建 MediaProjection 失败", e)
}
}
Log.w(TAG, "⚠️ Android 11+ Holder中无有效MediaProjection,等待权限授予")
}
// 标记权限完成

View File

@@ -228,7 +228,6 @@ class AlarmManagerKeepAliveService : Service() {
return false
}
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),开始保活检查")
true
} catch (e: Exception) {

View File

@@ -174,8 +174,6 @@ class BackgroundKeepAliveManager(private val context: Context) {
// ✅ 参考 f 目录:不重启无障碍服务,系统会自动管理
Log.d(TAG, "📱 无障碍服务权限正常,系统会自动管理无障碍服务生命周期")
}
} else {
Log.d(TAG, "✅ AccessibilityService运行正常")
}
// 检查前台服务

View File

@@ -256,8 +256,6 @@ class EffectiveKeepAliveManager(private val context: Context) {
if (accessibilityService == null) {
Log.d(TAG, "🔍 无障碍权限正常但AccessibilityService实例未初始化等待初始化完成")
// 权限正常但实例未初始化,等待初始化完成,不强制启动前台服务
} else {
Log.d(TAG, "✅ AccessibilityService运行正常")
}
}

View File

@@ -169,7 +169,6 @@ class KeepAliveService : Service() {
return false
}
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),开始监控无障碍权限")
true
} catch (e: Exception) {
@@ -192,13 +191,9 @@ class KeepAliveService : Service() {
val accessibilityService = AccessibilityRemoteService.getInstance()
val accessibilityRunning = AccessibilityRemoteService.isServiceRunning()
Log.d(TAG, "🔍 检查服务状态: 实例=${accessibilityService != null}, 运行=${accessibilityRunning}")
if (accessibilityService == null || !accessibilityRunning) {
Log.d(TAG, "🔍 无障碍权限正常,但服务实例未初始化或未运行,等待初始化完成")
// 权限正常但服务未运行,等待初始化完成,不强制恢复
} else {
Log.d(TAG, "✅ AccessibilityService运行正常")
}
}

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()
@@ -125,23 +134,30 @@ class RemoteControlForegroundService : Service() {
Log.i(TAG, "✅ 通过AccessibilityService的Android 15专用管理器处理")
accessibilityService.handleMediaProjectionGranted()
} else {
// 备用方案:直接创建但记录连接时间用于智能判断
mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
// 备用方案:从 Holder 获取已有对象
mediaProjection = MediaProjectionHolder.getMediaProjection()
if (mediaProjection != null) {
Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功(备用方案)")
MediaProjectionHolder.setMediaProjection(mediaProjection)
Log.i(TAG, "✅ Android 15 从Holder获取到已有MediaProjection")
notifyAccessibilityService()
} else {
// Holder 中无对象,通过安全创建入口创建
mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection(
this@RemoteControlForegroundService, resultCode, resultData
)
if (mediaProjection != null) {
Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功安全创建入口")
notifyAccessibilityService()
}
}
}
} else {
// 其他版本的正常处理逻辑
mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
// 其他版本:通过安全创建入口创建
mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection(
this@RemoteControlForegroundService, resultCode, resultData
)
if (mediaProjection != null) {
Log.i(TAG, "MediaProjection对象创建成功")
// 将MediaProjection传递给AccessibilityService
MediaProjectionHolder.setMediaProjection(mediaProjection)
Log.i(TAG, "MediaProjection对象创建成功(安全创建入口)")
// 通知AccessibilityService
notifyAccessibilityService()
@@ -377,7 +393,6 @@ class RemoteControlForegroundService : Service() {
return false
}
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),开始保活检测")
true
} catch (e: Exception) {

View File

@@ -121,7 +121,6 @@ class WorkManagerKeepAliveService {
return false
}
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),启动保活工作")
true
} catch (e: Exception) {

View File

@@ -65,7 +65,6 @@ class InstallationStateManager private constructor(private val context: Context)
return try {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val isComplete = prefs.getBoolean(KEY_INSTALLATION_COMPLETE, false)
Log.d(TAG, "🔍 安装完成状态检查: $isComplete")
isComplete
} catch (e: Exception) {
Log.e(TAG, "❌ 检查安装完成状态失败", e)