Android 无障碍服务失效,一次AccessibilityService“离奇死亡”的完整破案实录

从一次无障碍服务频繁崩溃,看Android系统下UiAutomator与AccessibilityService的底层冲突

一份完整的现场日志分析 + 问题定位 + 解决方案实录

一、问题现象

某车载Android应用的语音无障碍服务 VoiceAccessibilityService 频繁出现无法正常工作的情况。从运行日志观察,该服务在半小时内经历了多次完整的生命周期轮回:

  • onUnbindonDestroyonCreateonServiceConnected
  • 每次重建后,服务存活时间极短(最短仅约13秒),根本无法稳定接收和处理 onAccessibilityEvent 回调。
  • 同时出现错误日志:Invalid parameters - event packageName isNullOrEmpty: true,表明服务收到的事件对象已损坏或为空。

典型日志片段如下:

text 复制代码
05-15 11:25:51.930  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:51.996  2110  2222 I CAE     : [CaeSocketAgent.cpp:1685]OnEncBitrateCallback():Gair Encode bitrate callback, encBitrate: 2500, totalBitrate: 2500 maxEncBitrate =2250 isV6=0 CaeParamStorage::GetInstance().GetAppId()=4
05-15 11:25:52.031  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.132  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.175  2112  2132 E FuseDaemon: failed to connect to jdwp control socket: Permission denied
05-15 11:25:52.196  2110  2222 I CAE     : [CaeSocketAgent.cpp:1685]OnEncBitrateCallback():Gair Encode bitrate callback, encBitrate: 2500, totalBitrate: 2500 maxEncBitrate =2250 isV6=0 CaeParamStorage::GetInstance().GetAppId()=4
05-15 11:25:52.234  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.330  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.348   454 15479 E WifiService: Permission violation - getConfiguredNetworks not allowed for uid=10115, packageName=com.autonavi.amapauto, reason=java.lang.SecurityException: UID 10115 has no location permission
05-15 11:25:52.392   217   217 W watchdogd: timeout expired while flushing socket, closing
05-15 11:25:52.395  2110  2222 I CAE     : [CaeSocketAgent.cpp:1685]OnEncBitrateCallback():Gair Encode bitrate callback, encBitrate: 2500, totalBitrate: 2500 maxEncBitrate =2250 isV6=0 CaeParamStorage::GetInstance().GetAppId()=4
05-15 11:25:52.405   454 15307 W WindowManager: removeWindowToken: Attempted to remove non-existing token: android.os.Binder@500ad9e
05-15 11:25:52.406   454 15307 W WindowManager: removeWindowToken: Attempted to remove non-existing token: android.os.Binder@390584c
05-15 11:25:52.409  2167  2167 I Matrix.ActivityThreadHacker: [handleMessage] msg.what:114 begin:74412340 isLaunchActivity:false SDK_INT=30
05-15 11:25:52.409   454 15307 V UiAutomationManager: UiAutomation service owner died
05-15 11:25:52.411  2167  2167 D VoiceAccessibilityService: onCreate
05-15 11:25:52.411   454 15307 W ActivityManager: Receiver with filter android.content.IntentFilter@807c038 already registered for pid 2167, callerPackage is com.tecent.cloudsmartvoice
05-15 11:25:52.412  2167  2167 I SVGAParser: ================ decode animation_ai_visible_click_300ms.svga from assets ================
05-15 11:25:52.412  9307  9307 W ViewRootImpl[MainActivity]: Cancelling event due to no window focus: MotionEvent { action=ACTION_CANCEL, actionButton=0, id[0]=0, x[0]=740.0, y[0]=718.0, toolType[0]=TOOL_TYPE_MOUSE, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=74412342, downTime=73961653, deviceId=-1, source=0x2002, displayId=0 }
05-15 11:25:52.412  2167  2167 I Matrix.ActivityThreadHacker: [handleMessage] msg.what:121 begin:74412343 isLaunchActivity:false SDK_INT=30
05-15 11:25:52.413  2167 15492 I SVGAParser: ================ decode animation_ai_visible_click_300ms.svga from input stream ================
05-15 11:25:52.413  2167 15493 I SVGAParser: inflate start
05-15 11:25:52.414  2167 15493 I SVGAParser: inflate complete
05-15 11:25:52.416  2167  2167 D VoiceAccessibilityService: onServiceConnected
05-15 11:25:52.416   273   273 W HWC     : Rendering fps is below 0.6 * target fps, but could be normal if the phone screen is still
05-15 11:25:52.429  2167 15493 I SVGAParser: SVGAVideoEntity prepare start
05-15 11:25:52.429  2167 15493 I SVGAParser: SVGAVideoEntity prepare success
05-15 11:25:52.430  2167 15493 I SVGAParser: ================ decode animation_ai_visible_click_300ms.svga from input stream end ================
05-15 11:25:52.430  2167  2167 I SVGAParser: ================ animation_ai_visible_click_300ms.svga parser complete ================
05-15 11:25:52.430  2167  2167 D HotWordClickUIManager: onComplete: videoItem = m5.s@ac09b2a
05-15 11:25:52.431  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.532  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.596  2110  2222 I CAE     : [CaeSocketAgent.cpp:1685]OnEncBitrateCallback():Gair Encode bitrate callback, encBitrate: 2500, totalBitrate: 2500 maxEncBitrate =2250 isV6=0 CaeParamStorage::GetInstance().GetAppId()=4
05-15 11:25:52.634  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.412  9307  9307 I chatty  : uid=1000(system) com.tecent.settings identical 2 lines
05-15 11:25:52.412  9307  9307 W ViewRootImpl[MainActivity]: Cancelling event due to no window focus: MotionEvent { action=ACTION_CANCEL, actionButton=0, id[0]=0, x[0]=740.0, y[0]=718.0, toolType[0]=TOOL_TYPE_MOUSE, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=74412342, downTime=73961653, deviceId=-1, source=0x2002, displayId=0 }
05-15 11:25:52.638   454   565 I PowerManagerService: checkInteractiveStatusAndNotify, interactive: false
05-15 11:25:52.638   454   454 I JobScheduler.InteractiveController: Phone is not in interactive mode, wait alarm to trigger
05-15 11:25:52.731  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.796  2110  2222 I CAE     : [CaeSocketAgent.cpp:1685]OnEncBitrateCallback():Gair Encode bitrate callback, encBitrate: 2500, totalBitrate: 2500 maxEncBitrate =2250 isV6=0 CaeParamStorage::GetInstance().GetAppId()=4
05-15 11:25:52.796  2110  2222 I CAE     : [CaeBitrateSampleSet.h:24]addSample():QOS==the history encbitrate data: ,2250,2250,2250,2250,2250,2250
05-15 11:25:52.796  2110  2222 I CAE     : [XquicGairServer.cpp:80]updateAVBitrateTask():--> dealAVBitrateThread videoBitrate: 1906  audioBitrate: 0 sum_no_audio=0 mLastAudioPtsTime = 1778813603670
05-15 11:25:52.796  2110  2222 I CAE     :  
05-15 11:25:52.796  2110  2222 E CAE     : [CaeSocketAgent.cpp:369]OnRecvGairEngineStatus():GairEngine GAIR_STATUS_HEALTH_CHECK ... !!! cph isopen=1
05-15 11:25:52.797  1733  1755 D GWMDT_Service : BeanDataTrackService: postCustomEvent,packageName:CloudAppEngine,customEventName:cae_event_network_rtt
05-15 11:25:52.797  1733  1755 D GWMDT_Service : DataCacheManager: cacheTrackItem cae_logout_type(1) KEY_LONGITUDE() KEY_LATITUDE() 
05-15 11:25:52.797  1733  1755 I GWMDT_Service : DataCacheManager: sceneEngine check engineReady: false,engineReady:0
05-15 11:25:52.797  1733  1755 D GWMDT_Service : DataCacheManager: tType:custom,pName:null,aType:null,eName:cae_event_network_rtt,pkName:CloudAppEngine,aInfo:55,db:false,eReady:false
05-15 11:25:52.797  2110  4082 I CAE     : [DataTrackWrapper.cpp:266]DataTrackWrapper_postCustomEvent():Data Track post post custom event result: 1,cae_event_network_rtt,rtt: 51&time: 2026_05_15_11_25_52&
05-15 11:25:52.831  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.836   454  1994 E AdbDebuggingManager: Caught an exception opening the socket: java.io.IOException: No such file or directory
05-15 11:25:52.933  1441  1441 E SensorManager: dispatchSensorEvent: disable sensor event for Sensor: {Sensor name="proximity-tmd3702", vendor="ams", version=1, type=8, maxRange=5.0, resolution=5.0, power=0.75, minDelay=0}
05-15 11:25:52.996  2110  2222 I CAE     : [CaeSocketAgent.cpp:1685]OnEncBitrateCallback():Gair Encode bitrate callback, encBitrate: 2500, totalBitrate: 2500 maxEncBitrate =2250

这种"刚连接上就断开,然后迅速重建"的循环,导致服务完全无法执行业务逻辑(如页面节点抓取、热词点击等)。

二、排查过程

2.1 第一印象:服务配置缺失

审查 VoiceAccessibilityService 源码,首先发现一个明显问题:

kotlin 复制代码
override fun onServiceConnected() {
    super.onServiceConnected()
    // HotWordsManager.setOnHotWordShotMethod{ nodeInfo -> performClick(nodeInfo)}
    // setServiceInfo()   // ← 被注释掉了!
}

setServiceInfo() 用于配置无障碍服务的事件类型、监听包名、反馈标志等关键参数。若不调用该方法,系统不会向该服务发送任何无障碍事件,服务形同虚设。但仅此一点无法解释服务频繁销毁重建的现象。

2.2 追踪生命周期:发现密集重建

仔细分析时间轴:

  • 11:25:02.459onUnbind
  • 11:25:02.460onDestroy
  • 11:25:52.411onCreate(相隔约50秒)
  • 11:25:52.416onServiceConnected
  • 11:26:05.154onUnbind(存活仅约13秒)
  • 11:26:05.154onDestroy

服务存活时间极短,且每次销毁后都被重建。这种模式高度类似系统资源冲突导致的强制回收

2.3 关键日志突破:UiAutomationManager

在服务被重建的同一毫秒级时间窗口(11:25:52.409 ~ 11:25:52.411),我们抓取到了决定性的日志:

text 复制代码
05-15 11:25:52.409   454 15307 V UiAutomationManager: UiAutomation service owner died
05-15 11:25:52.411  2167  2167 D VoiceAccessibilityService: onCreate
  • UiAutomationManager 是 Android 系统内部管理 UiAutomation 服务的核心类。
  • UiAutomation 是 Android 自动化框架的基础,UiAutomator、Appium、Robotium 等工具均依赖它运行。
  • 日志 UiAutomation service owner died 明确表示:持有 UiAutomation 服务的进程(即自动化测试进程)刚刚意外死亡

时间差仅为 2 毫秒 。这说明:UiAutomation 服务的异常终止事件,直接触发了系统对无障碍资源的重新分配,进而导致 VoiceAccessibilityService 被系统强制回收并立即重建。

2.4 更完整的日志揭示的前因后果

用户后续提供了更完整的日志片段,其中包含了冲突前、中、后的更多细节,让证据链更加严密。

2.4.1 冲突前的系统"混乱"信号
text 复制代码
05-15 11:25:52.405   454 15307 W WindowManager: removeWindowToken: Attempted to remove non-existing token: android.os.Binder@500ad9e
05-15 11:25:52.406   454 15307 W WindowManager: removeWindowToken: Attempted to remove non-existing token: android.os.Binder@390584c
05-15 11:25:52.409   454 15307 V UiAutomationManager: UiAutomation service owner died
  • WindowManager 试图移除一个不存在的窗口令牌 ------ 这表明 UiAutomation 所控制的某个窗口已经在异常退出时被系统清理,但 WindowManager 仍然收到了一次重复的清理请求。
  • 紧接着 UiAutomation service owner died 出现。两者结合可以推断:UiAutomation 的宿主进程正在非正常退出,导致其持有的窗口令牌和 UiAutomation 服务同时被系统回收
2.4.2 服务重建时的资源残留警告
text 复制代码
05-15 11:25:52.411   454 15307 W ActivityManager: Receiver with filter android.content.IntentFilter@807c038 already registered for pid 2167, callerPackage is com.tecent.cloudsmartvoice
  • ActivityManager 发现同一个 BroadcastReceiver 被重复注册 。进程 2167 是 VoiceAccessibilityService 所在进程,刚刚被重建,但系统中可能残留着之前进程未正常注销的 Receiver 记录。
  • 这进一步佐证了服务是被系统强制杀死并立刻重建,导致一些注册信息来不及清理。
2.4.3 精确到毫秒的时间线
时间戳 PID 日志内容
11:25:52.405 454 removeWindowToken (first)
11:25:52.406 454 removeWindowToken (second)
11:25:52.409 454 UiAutomation service owner died
11:25:52.409 2167 Matrix.ActivityThreadHacker (无关业务日志)
11:25:52.411 2167 VoiceAccessibilityService: onCreate
11:25:52.411 454 ActivityManager: Receiver already registered
11:25:52.416 2167 VoiceAccessibilityService: onServiceConnected

关键间隔

  • UiAutomation service owner diedonCreate2 毫秒
  • onCreateonServiceConnected5 毫秒

这种微秒级的响应不可能是业务代码主动触发的,而是系统级的同步回收与重建动作

2.5 排除其他可能性

  • 多个无障碍服务冲突? 日志中未发现其他无障碍服务(如TalkBack)的运行痕迹。
  • 代码主动调用 stopSelf() 源码中没有此逻辑。
  • 系统资源不足? 同一时段设备负载正常,无内存不足或ANR日志。

唯一合理的解释就是 UiAutomator 与普通 AccessibilityService 存在底层资源竞争

三、根本原因分析

3.1 Android 无障碍服务底层机制

Android 的 AccessibilityServiceUiAutomation 共享同一套底层基础设施 ------ AccessibilityManagerService。两者都需要向系统注册,并通过 Binder 通信接收窗口变化、事件通知等。

关键冲突点

  • UiAutomation 通常以 独占模式 运行。当它被激活时(例如执行 uiautomator runtest),系统会优先保障其资源需求。
  • 为了给 UiAutomation 让出资源,或者因为两者对同一事件的争抢,系统可能会主动杀死或重建其他已注册的无障碍服务。
  • UiAutomation 进程异常退出时(如测试脚本崩溃、手动停止),系统清理其残留资源(包括窗口令牌、Binder连接等)的过程中,往往会连带影响其他无障碍服务,导致它们被销毁后重建。

3.2 日志中的确凿证据

  1. WindowManager.removeWindowToken 失败 → UiAutomator 相关窗口已异常退出。
  2. UiAutomationManager: UiAutomation service owner died → 存在一个活跃的 UiAutomation 服务,且其宿主进程死亡。
  3. ActivityManager: Receiver already registered → 服务进程被强制杀死,状态残留。
  4. VoiceAccessibilityService: onCreate 紧随其后 → 系统在清理 UiAutomation 后立即重建了该服务。

3.3 为什么服务会被频繁重建?

UiAutomator 运行期间可能会多次执行 UiAutomation 的连接和断开操作(例如每个测试用例前后)。每次断开时,系统都会检查并重新调整无障碍服务列表。如果我们的服务被系统标记为"不稳定"或"优先级低",就会被反复杀死和重建。

从日志时间线看,重建后仅存活约13秒就被再次销毁,这印证了 UiAutomation 在连续执行测试用例或反复连接/断开,每次都连带影响了普通无障碍服务。

四、解决方案

4.1 立即修复:补全 setServiceInfo()

无论冲突是否存在,服务必须正确配置才能接收事件:

kotlin 复制代码
override fun onServiceConnected() {
    super.onServiceConnected()
    setServiceInfo()   // 取消注释
}

private fun setServiceInfo() {
    val info = AccessibilityServiceInfo()
    info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK
    info.notificationTimeout = 100
    info.flags = AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
    this.serviceInfo = info
}

4.2 运行时检测 UiAutomator 并降级

在服务中添加检测逻辑,当发现 UiAutomator 正在运行时,主动暂停部分功能或延迟处理:

kotlin 复制代码
private fun isUiAutomatorRunning(): Boolean {
    val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
    val enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
    return enabledServices.any {
        it.resolveInfo.serviceInfo.packageName.contains("uiautomator", ignoreCase = true) ||
        it.resolveInfo.serviceInfo.packageName.contains("test", ignoreCase = true) ||
        it.resolveInfo.serviceInfo.packageName.contains("automation", ignoreCase = true)
    }
}

onAccessibilityEvent 入口处判断:

kotlin 复制代码
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
    if (isUiAutomatorRunning()) {
        // 降级处理:只记录关键日志,不执行业务逻辑
        LogUtils.w(TAG, "UiAutomator is running, skip processing")
        return
    }
    // 正常处理...
}

4.3 环境隔离:错峰使用

如果是开发/测试环境:

  • 运行 UiAutomator 时 :手动在系统"无障碍"设置中关闭 VoiceAccessibilityService

  • 需要测试无障碍服务时 :停止所有 UiAutomator 进程:

    bash 复制代码
    adb shell ps | grep uiautomator | awk '{print $2}' | xargs adb shell kill

    或直接重启设备。

4.4 代码健壮性改进

  • 线程安全 :在 onDestroy 中释放所有线程资源(如 businessThread.quitSafely()),避免下一次重建时出现资源残留。
  • 事件副本 :使用 AccessibilityEvent.obtain(event) 创建事件副本再传递给异步线程,避免因生命周期问题导致 packageName 为空(这也是日志中 Invalid parameters 错误的原因之一)。
  • 注册清理 :在 onUnbindonDestroy 中注销 BroadcastReceiver,避免重建时的重复注册警告。

五、总结

通过分析日志中连续出现的 WindowManager.removeWindowTokenUiAutomationManager: UiAutomation service owner diedVoiceAccessibilityService: onCreate 时间线,我们确认了问题的根本原因:

UiAutomator(及其依赖的 UiAutomation 服务)与普通 AccessibilityService 存在底层资源冲突。当 UiAutomator 进程异常退出时,系统在清理其窗口资源和服务连接的过程中,会强制销毁并重建其他无障碍服务,导致服务频繁中断,无法稳定工作。

这一现象在同时使用自动化测试工具和无障碍服务的项目中非常典型。解决方案包括:

  1. 修复服务自身的配置缺失 (取消注释 setServiceInfo())。
  2. 运行时检测 UiAutomator 并采取降级策略(检测包名或自动化服务状态)。
  3. 使用时间隔离,避免 UiAutomator 和无障碍服务同时运行。
  4. 增强服务的健壮性(线程安全、事件副本、资源清理)。

希望本文详细的日志分析和解决方案能为遇到类似问题的开发者提供参考,也欢迎在评论区交流更多 Android 无障碍开发的实践心得。

相关推荐
懂懂tty3 小时前
Vue3 手写响应式原理
前端·vue.js
weixin_511875333 小时前
【无标题】
前端
罗超驿3 小时前
15.面试高频考点:MySQL索引底层原理与实战要点全梳理
mysql·面试·职场和发展
木斯佳3 小时前
前端八股文面经大全:质谱华章前端一面(2026-05-14)·面经深度解析
前端·面试·面经
水煮白菜王3 小时前
JSONEditor 使用指南
前端·javascript·chrome·json
_Evan_Yao3 小时前
从 select 到 epoll,再到 Agent 循环:如何用 I/O 多路复用撑起千军万马?
java·数据库·人工智能·后端
GISer_Jing3 小时前
从前端到AI Agent工程师:技能升级与职业跃迁指南
前端·人工智能·ai编程
yqcoder3 小时前
遍历的艺术:深入解析 for, for...in, for...of 的核心区别
前端·javascript
长谷深风1114 小时前
SpringBoot开发秘籍【个人八股】
java·spring boot·后端·spring·八股