从一次无障碍服务频繁崩溃,看Android系统下UiAutomator与AccessibilityService的底层冲突
一份完整的现场日志分析 + 问题定位 + 解决方案实录
一、问题现象
某车载Android应用的语音无障碍服务 VoiceAccessibilityService 频繁出现无法正常工作的情况。从运行日志观察,该服务在半小时内经历了多次完整的生命周期轮回:
onUnbind→onDestroy→onCreate→onServiceConnected- 每次重建后,服务存活时间极短(最短仅约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.459→onUnbind11:25:02.460→onDestroy11:25:52.411→onCreate(相隔约50秒)11:25:52.416→onServiceConnected11:26:05.154→onUnbind(存活仅约13秒)11:26:05.154→onDestroy
服务存活时间极短,且每次销毁后都被重建。这种模式高度类似系统资源冲突导致的强制回收。
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 died→onCreate:2 毫秒onCreate→onServiceConnected:5 毫秒
这种微秒级的响应不可能是业务代码主动触发的,而是系统级的同步回收与重建动作。
2.5 排除其他可能性
- 多个无障碍服务冲突? 日志中未发现其他无障碍服务(如TalkBack)的运行痕迹。
- 代码主动调用
stopSelf()? 源码中没有此逻辑。 - 系统资源不足? 同一时段设备负载正常,无内存不足或ANR日志。
唯一合理的解释就是 UiAutomator 与普通 AccessibilityService 存在底层资源竞争。
三、根本原因分析
3.1 Android 无障碍服务底层机制
Android 的 AccessibilityService 和 UiAutomation 共享同一套底层基础设施 ------ AccessibilityManagerService。两者都需要向系统注册,并通过 Binder 通信接收窗口变化、事件通知等。
关键冲突点:
UiAutomation通常以 独占模式 运行。当它被激活时(例如执行uiautomator runtest),系统会优先保障其资源需求。- 为了给
UiAutomation让出资源,或者因为两者对同一事件的争抢,系统可能会主动杀死或重建其他已注册的无障碍服务。 - 当
UiAutomation进程异常退出时(如测试脚本崩溃、手动停止),系统清理其残留资源(包括窗口令牌、Binder连接等)的过程中,往往会连带影响其他无障碍服务,导致它们被销毁后重建。
3.2 日志中的确凿证据
WindowManager.removeWindowToken失败 → UiAutomator 相关窗口已异常退出。UiAutomationManager: UiAutomation service owner died→ 存在一个活跃的 UiAutomation 服务,且其宿主进程死亡。ActivityManager: Receiver already registered→ 服务进程被强制杀死,状态残留。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 进程:
bashadb shell ps | grep uiautomator | awk '{print $2}' | xargs adb shell kill或直接重启设备。
4.4 代码健壮性改进
- 线程安全 :在
onDestroy中释放所有线程资源(如businessThread.quitSafely()),避免下一次重建时出现资源残留。 - 事件副本 :使用
AccessibilityEvent.obtain(event)创建事件副本再传递给异步线程,避免因生命周期问题导致packageName为空(这也是日志中Invalid parameters错误的原因之一)。 - 注册清理 :在
onUnbind或onDestroy中注销 BroadcastReceiver,避免重建时的重复注册警告。
五、总结
通过分析日志中连续出现的 WindowManager.removeWindowToken → UiAutomationManager: UiAutomation service owner died → VoiceAccessibilityService: onCreate 时间线,我们确认了问题的根本原因:
UiAutomator(及其依赖的 UiAutomation 服务)与普通 AccessibilityService 存在底层资源冲突。当 UiAutomator 进程异常退出时,系统在清理其窗口资源和服务连接的过程中,会强制销毁并重建其他无障碍服务,导致服务频繁中断,无法稳定工作。
这一现象在同时使用自动化测试工具和无障碍服务的项目中非常典型。解决方案包括:
- 修复服务自身的配置缺失 (取消注释
setServiceInfo())。 - 运行时检测 UiAutomator 并采取降级策略(检测包名或自动化服务状态)。
- 使用时间隔离,避免 UiAutomator 和无障碍服务同时运行。
- 增强服务的健壮性(线程安全、事件副本、资源清理)。
希望本文详细的日志分析和解决方案能为遇到类似问题的开发者提供参考,也欢迎在评论区交流更多 Android 无障碍开发的实践心得。