线程职责速查表: 下表对比三条线程在 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 架构图所示。
**控制流与信任链路规则**
- **方向性**:所有异步消息都采用 **单向** 传递,禁止双向依赖。
- **信任边界**: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