### 3. TalkBack 无障碍服务线程模型:AccessibilityService 主线程、EventDispatch 线程、GestureDispatcher 线程的职责与交互 TalkBack 作为 Android 系统级无障碍服务,其稳定性和响应速度直接决定视障用户的国际化使用体验。在 RTL(从右至左)布局与多语言合成加速的混合压力下,仅理解 API 用法远远不够——必须深入剖析它的三大线程:**AccessibilityService 主线程**、**EventDispatch 线程**与 **GestureDispatcher 线程**。这三条线程构成了 TalkBack 内部的消息骨架和时序护栏,也正是一切线程安全争议与死锁风险的源头。 本章将围绕三条线程的模块边界、系统分层、数据流与控制流、选型权衡、经典风险,以及可落地的验收方法展开,为后续 RTL 布局适配与 TTS 调度优化提供线程层面的认知基线。 #### 3.1 三层线程模型概览 TalkBack 的线程模型采用 **生产者-消费者 + 专属调度器** 的分层架构。如图所示:
线程职责速查表: 下表对比三条线程在 TalkBack 运行时的核心分工。
| 线程名称 | 生命周期 | 核心职责 | 运行模式 | 与 RTL 相关的敏感操作 | |---------|----------|---------|---------|---------------------| | AccessibilityService 主线程 | 随 Service 创建到销毁 | 接收系统回调、管理节点树、调度反馈 | 单线程 Looper | `onAccessibilityEvent` 中解析 `isScreenLayoutRTL` | | EventDispatch 线程 | 系统 Event 触发时活跃 | 分发原始 `AccessibilityEvent`,排队节流 | 独立 HandlerThread | 时间戳排序与分组,RTL 下顺序反转影响 | | GestureDispatcher 线程 | 随手势识别启动 | 解析触摸路径、触发操作 | 专有线程 + 令牌队列 | 滑动手势方向映射;RTL 下左右滑动需镜像 | 设计前提: Google 官方从 TalkBack 9.1 起逐步将事件处理与手势逻辑分离,目的正是让 EventDispatch 不阻塞主线程的焦点导航反馈,尤其是在长按节点语音播报时。 #### 3.2 AccessibilityService 主线程——系统与用户的唯一窗口 **模块边界** 主线程是 TalkBack 与 Android 无障碍框架(`AccessibilityService`)的生命期绑定者。它运行一个 `Handler` 主循环,处理来自 `onAccessibilityEvent()`、`onGesture()` 以及 `onKeyEvent()` 三个回调入口的消息。 **核心职责链** 1. **接收系统事件**:将 `onAccessibilityEvent` 中收到的原始事件转为内部 `EventContext`。 2. **节点树管理**:维护 `AccessibilityNodeInfo` 缓存,处理 RTL 下的区域段反序(详见第 5 章)。 3. **合成反馈**:向 TTS 引擎提交文本,并协调 `GestureDispatcher` 执行下一步。 4. **状态同步**:在 RTL 语言切换时刷新 `Configuration` 中的 `getLayoutDirection()`。 **关键设计决策** - 禁止在 `onAccessibilityEvent()` 内直接执行耗时超过 5ms 的语音合成请求;必须委托给异步 `Callback` 或 `HandlerThread`。 - 使用 `intent` 启动其他应用前,需确保不在 `GestureDispatcher` 持有的锁中执行,否则立即造成 ANR。 #### 3.3 EventDispatch 线程——系统事件的排队与节拍器 **模块边界** EventDispatch 是一个独立的 `HandlerThread`,不与主线程共享 `Looper`。它接收 `AccessibilityService` 主线程通过 `sendEvent()` 传过来的原始事件副本。 **核心职责链** 1. **时间戳去重**:当同区间快速连续上报多个内容重复的 `TYPE_WINDOW_CONTENT_CHANGED` 事件,仅保留最新一个。 2. **RTL 顺序重排序**:在 RTL 布局下,`TYPE_VIEW_ACCESSIBILITY_FOCUSED` 事件应先聚焦行内最右侧可聚焦元素;EventDispatch 负责修正事件队列顺序。 3. **节流与分组**:每 16ms 最多向主线程递送 1 个事件批次,避免主线程过载。 **数据流示意** 主线程 `→` EventDispatch.queue `→` [去重] `→` [批次压缩] `→` 主线程.handleEvent **选型权衡** 如果让主线程直接处理所有系统事件,在 RTL + 长文本播报场景下焦点切换延迟会超过 200ms。引入 EventDispatch 以额外 500μs 延迟换取主线程吞吐量 3 倍提升。 #### 3.4 GestureDispatcher 线程——触摸路劲与手势符号的翻译器 **模块边界** GestureDispatcher 仅在 `onGesture()` 触发后运行时创建。它与主线程共享部分状态(当前焦点节点指针),但拥有独立的 Looper 和令牌队列。 **核心职责链** 1. **手势解析**:将屏幕坐标路径转换为一组 `GestureStep`。 2. **方向镜像**:当检测到系统语言为 RTL(如阿拉伯语、希伯来语)时,将“右滑”手势的内部语义反转——右滑读上一个元素,左滑读下一个元素。 3. **令牌竞争控制**:通过 `AtomicInteger` 令牌保证同一时间只有一个手势被主线程消费,防止双击与长按冲突。 **风险控制** - `GestureDispatcher` 绝不允许直接持有 `AccessibilityNodeInfo` 引用;必须通过主线程的代理接口查询。 - 令牌阻塞时,超时机制设为 2 秒,超时后强制重置手势状态并丢弃当前令牌。 #### 3.5 线程间交互与通信链路 三条线程的通信路径如下面的 SVG 架构图所示。 TalkBack 三线程交互图 AccessibilityService 主线程 (Looper) EventDispatch HandlerThread GestureDispatcher 专有线程 + 令牌队列 onAccessibilityEvent sendEvent 异步 去重后批次回调 onGesture 令牌传递 手势结果回传 ⚠ 信任链路关键点 EventDispatch 不写共享状态;GestureDispatcher 持有临时令牌,不持节点引用。 **控制流与信任链路规则** - **方向性**:所有异步消息都采用 **单向** 传递,禁止双向依赖。 - **信任边界**:EventDispatch 是唯一同时接触系统事件源和 TalkBack 主线程的模块,它的去重逻辑需要配置白名单——哪些事件在 RTL 下需要重新排序(详见第 6 章配置表)。 - **令牌隔离**:GestureDispatcher 的令牌仅用于手势协调,不参与节点缓存竞争,避免锁传导。 #### 3.6 选型权衡与风险点 **权衡 1:线程数量 vs 上下文切换开销** 三条线程相比单线程方案,上下文切换开销增加约 12%,但换来的收益是焦点切换不卡语音。在低端设备(如 Android Go)上,应关闭 EventDispatch 的批次压缩,保留基本去重。 **权衡 2:HandlerThread 与 IntentService** EventDispatch 选择 `HandlerThread` 而非 `IntentService`,原因是后者处理完任务可能自动销毁线程,而 TalkBack 的 Event 是持续流式峰值,线程重用优于创建销毁。 **核心风险点** | 风险类型 | 出现场景 | 影响 | 检测线索 | |---------|---------|------|---------| | 主线程 ANR | RTL 下复杂节点树 + 长文本播报 | 系统弹出“TalkBack 无响应” | logs 中 `InputDispatcher` timeout | | 手势令牌泄漏 | 用户高频切换 RTL / LTR 语言 | 手势错位或双击失效 | `AtomicInteger` 值 > 1 | | Event 批次乱序 | EventDispatch 时间戳截断不当 | 焦点跳到错误方向 | 对比 `event.getEventTime()` 与真实先后 | | 锁传递死锁 | GestureDispatcher 误持节点锁后唤醒主线程 | 界面完全冻结 | Thread dump 中 `waiting for lock` | #### 3.7 落地步骤 在一次典型的 TalkBack 国际化适配中,团队应按以下步骤验证线程模型: 1. **安装日志探针**:在三线程的 `Handler.post` 前后加入追踪点,输出当前线程名称与毫秒时间戳。 2. **构造 RTL 压力测试**:使用 `adb shell setprop ro.sys.locale ar-XC` 强制应用走阿拉伯语,然后连续滑动 10 次“浏览”。 3. **抓取线程 Dump**:如果出现焦点抖动,执行 `kill -3 ` 阅读 `main`、`EventDispatch`、`GestureDispatcher` 三个线程的堆栈。 4. **调整去白名单**:在 `EventDispatch` 配置文件中增加或移除事件类型,例如 `TYPE_VIEW_HOVER_ENTER` 在 RTL 下不必重排序。 5. **验证令牌复位**:双击 10 次后检查 `GestureDispatcher` 的 `` 字段是否归零。 #### 3.8 验收标准 | 验收项 | 测试方法 | 通过标准 | |-------|---------|---------| | 主线程无 ANR | `monkey --pct-touch 70` 跑 30 分钟 | 抓取 ANR 报告为 0 | | 手势方向正确 | 在阿拉伯语下右滑定位到左一元素 | 焦点顺序严格从右到左 | | Event 不丢不重 | 连续 100 次窗口切换 | 日志中 `event_id` 连续且去重比率 < 3% | | 令牌无泄漏 | GC 后 hook `` 检查 | 始终为 0 或 1 | | RTL 与 LTR 切换不崩溃 | 运行时修改 `settings put secure system_locales` | 没有 NullPointerException | 本章对 TalkBack 三类线程的职责、边界、交互路径和风险做了体系化梳理。在后续章节中,将基于这套线程模型,逐层展开 RTL 布局感知、TTS 线程调度以及大规模回滚方案的架构设计——理解线程的隔离与通信,是确保无障碍服务在跨国语言环境下不崩溃、不卡顿的坚实基础。