Foxy 审查后的响应性修复与同条件验证
适用分支:feat/direct-rime-prototype。基础修复提交 8ef7c6c6, profile/诊断补齐 0ff6a0aa,就绪重绑边界修正 8634bbeb;最终构建源码为 12ab22cd82297638bee75ef30b31b3decb21decb,其相对 8634bbeb 只改 CI 入口。 本轮只验证修复后的直连版与用户提供的 Foxy 原 APK,不重新测试旧 Fcitx 或修复前版本。
两份新增真机日志说明了什么
两份日志均来自 9d5c7992 Debug,设备为 OnePlus PJD110 / Android 16, 进程号均为 9414。日志中没有进程创建,不能把 ColdStart 标签解释为进程冷启动。 原始日志不上传仓库;脱敏后的时间记录见本报告配套数据。
| 场景 | 原生操作及发布 | 其他阶段 | 可以得出的结论 |
|---|---|---|---|
| 20:29,generation 1515 首次点选 | 123.519ms | owner 等待 0.434ms;提交服务处理 3.604ms | 大头在原生调用及快照/发布区间 |
| 20:29,generation 1521 首次点选 | 135.267ms | owner 等待 0.305ms;事件等待 1.341ms | 不是事件队列积压造成全部延迟 |
| 20:31,generation 1576 第二次点选并上屏 | 150.415ms | owner 等待 0.173ms;提交服务处理 2.670ms | 第一次局部选词只有 10.102ms,不能与最终提交混为一谈 |
| 非输入框绑定 | 594–756ms 的会话创建 | 多次 needs_rime=false 之后仍 created=1 | 无效绑定破坏了原来可复用的真实编辑器会话 |
旧 native_publish 同时覆盖原生动作、快照读取及 Kotlin 发布,不等于纯 librime 时间。 20:31 日志还记录了展开候选一次请求 144 项,会占用唯一的原生 owner 线程。 万象 context_reorder.lua 会在首次需要时打开 LevelDB;仅凭现有日志无法把 123–150ms 精确归因于它,也没有依据修改用户方案、禁用学习或预先模拟输入。
第三份真机日志:慢选词与发送竞争
用户后续上传 13_35_08Z 日志,版本为已含初步 8ef7c6c6 修复的 57e4b9cd Debug(尚非最终 12ab22cd Release), 并确认“点发送后,所选候选一直没有出现”。这不能表述为已经解决或只是稍后显示。
| 北京时间 | 记录 | 含义 |
|---|---|---|
| 21:34:36.397 | 候选触摸 UP | 本次手势结束 |
| 21:34:36.401 | select_queue | 服务等待 1.281ms |
| 21:34:36.544 | select 完成 | owner 等待 0.765ms;core 140.019ms;原生及发布 141.725ms |
| 21:34:36.551 | commit 调用结束 | 事件等待 6.646ms;服务调用 1.404ms |
| 21:34:36.573 | restartInput,初始选择 [0,0] | 输入框重启;此前没有 [3,3]→[6,6] 更新 |
| 21:34:43.922 | 后一次 select 完成 | core 1.642ms,原生及发布 1.985ms |
| 21:34:43.943 | 后一次选择更新 | [3,3]→[6,6];随后才重启输入框 |
这一次新计时直接确认慢点在 librime 选词调用内部,读取 commit/context/status/tabs 合计仅几十微秒。core 是调用的墙钟时间,包含其内部 Lua、学习/数据库及可能的调度等待, 仍不能据此断定某个 Lua 或文件就是根因。ART/profile 与 JNI 查找优化不能解决整个 140ms。 首个字母另有 core 51.264ms、input tabs 12.213ms;绑定创建 core 976.764ms, 随后两次同应用重绑均复用、约 1ms。非输入框绑定已全部记录 native_create=false, 说明会话修复在这台真机上生效,但首次选词问题仍在。
输入法日志没有聊天应用的发送触摸、接收提交与发送内容,无法证明应用侧完整处理顺序。 原 service_commit_us 只计 InputConnection 调用;远程调用返回不等于文字已显示。 因此新增 request_result,区分无连接、请求被拒绝、请求被接受与剪贴板搜索分流; 拒绝/无连接另记警告,不记录文字。即便请求返回 true,也不能称为应用已经确认上屏。 不把不确定的提交自动重放到 restartInput 后的新连接,以免重复发送或跨输入框泄漏。 后续慢调用还记录 owner 线程 CPU、缺页与主动/被动上下文切换增量,用来区分 CPU 运算 与非 CPU 等待;这些计数仍不能单独确定具体阻塞文件或 Lua 函数。
已完成的产品修改
- 非输入框不再仅因窗口显示或 focus 创建 Rime 会话。 实际软键到达时先提升 会话需求、排入绑定,再接收该键的 generation ticket。硬键路径保持同样的顺序。 主动切换中英文或方案仍可以请求会话。同应用热会话可跨过非输入框页面保留; 不同真实编辑器应用、密码及敏感环境的隔离规则继续生效。 引擎 Ready/restart 的重绑路径同样按编辑器类型处理;重新应用 TYPE_NULL 描述时, 保留同一代次已经由真实首键提出的会话需求,避免冷启动恢复时反向撤销它。
- 展开候选按最多 8 项拆分原生任务。 逻辑分页仍可返回完整一页,任务之间允许 按键排入 owner。取消加载会停止后续批次;候选版本改变立即取消旧页,避免拼接 两个输入状态。版本按候选实际失效推进,不用会被无效 key-up 推进的快照 revision。
- 减少 JNI 重复查找及临时分配。 在
JNI_OnLoad缓存 Snapshot 构造器与 String 类引用,并在卸载时释放;候选列表预留容量、每次只取一次选择键。 - 恢复 Release 的 ART profile 管线。 启用 ProfileInstaller,加入应用/输入服务、 直连后端、Snapshot 与候选更新入口的规则,保留 R8/minify。 已核验最终 APK 内二进制 profile 的 DEX CRC 与方法编号,不能只凭文本规则宣称生效。 初轮检查还发现 Kotlin value class 导致
sendKey实际名字带后缀,原规则未匹配; 已修正通配符并补齐后端协程入口,构建时直接检查最终二进制方法表。 最终命中方法见配套审计 JSON;方法数量不代表性能提升幅度。 - 细分原生诊断。 区分 core、读取提交、context、status 和 input tabs;绑定另记 native begin 与状态恢复,并记录 core 的线程 CPU、缺页和上下文切换增量。 提交记录区分请求结果,不称作编辑器上屏确认。只在首几个操作或慢操作输出计时, 请求被拒/无连接另记警告,不记录输入内容。
没有增加跨应用会话池,没有禁用万象上下文调频,也没有加入伪造输入的预热。
验证与产物
- 484 项 JVM 测试通过。候选分页新增覆盖取消、版本过期和按键能插入批次之间的测试。
- 原生契约及 sanitizer 检查通过;Android 完整前端触摸/引擎验证通过。
- 产品修复 CI 与 JVM CI。
- ARM64 Release 构建与比较任务, 产品源码
12ab22cd82297638bee75ef30b31b3decb21decb。 下载最终 Release。 此任务的构建成功;最初比较未完整通过,不能把整个任务标为成功。 - APK SHA256:
fbd3aaa62b7bdfa054ddf726b35fab9b939bc1162d583644d3c5282319b564ec。 使用仓库 Release 签名,证书 SHA256:ba43006e1bfa3f60f46b2a9056f593a3a61445bd2571319fb1869e49cc36b1fe。 - 包内只有
libdirect-rime.so与libc++_shared.so,未发现旧原生 Fcitx 后端或依赖; ELF 16KiB 对齐验证通过。
Foxy 对比方法与限制
Foxy 是上传的 0.0.1-5458f3c ARM64 Release,SHA256 c81783716626c76469a20f1884abfeed63f05349a55ca052ee44079a01b7b80c,未改包或注入 IME 测试代码。 经用户明确授权,将原 APK 放入独立的临时测试分支供 Actions 读取,完成后删除该分支。 Foxy 原生 librime 为 1.17.0,但没有提供对应提交,不能确认与本项目固定源码及补丁完全一致。
两边均安装同一份万象 base v18.1.3 + 固定 LTS 模型,使用上传的两份 YAML; 仅补入用户确认开启的 context_reorder: true。保持全拼、简码、小字集与其他用户选项。 通过实际中文提交和进程映射验证引擎及 .gram 模型加载。
环境为 Android 30 Google APIs x86_64 模拟器。两款 APK 均为 ARM64, 使用同一 libndk_translation.so;这是相同翻译条件下的功能/响应观察,不是手机性能证明。 中立编辑器在自身进程注入真实触摸,记录注入前时间、InputConnection.commitText 入口、 TextWatcher 与 CPU onDraw,不把 onDraw 当成最终显示帧。 两边都等实际会话就绪后输入,统一固定等待 500ms 再点选;每个阶段首个候选保留截图。 通过实际编辑器逐次核验完整提交文本及唯一性,保留所有失败记录。 该方法隔离候选已出现后的选词成本,不测逐键编码响应或“显示瞬间即打字”。 预备部署与截图校准不算性能样本。首次校准因短服务名识别错误选中了系统键盘,已废弃并修正, 不能用该失败推断 Foxy 的输入能力或速度。
测试按单字“你”和三字短语“早上好”分别运行,每项六次新进程、每次八次选词, 随后跳到系统设置这个非输入框应用并返回编辑器,再做八次选词。顺序按产品与语料交替。 普通比较之外另设独立测试客户端:预置“前文”,点选候选后等待计划 20/100ms,再真实点击发送, 记录按钮实际读到的文本、清空/restartInput 后的草稿与 InputConnection 调用; 每次发送后观察 800ms。candidate_missing 只表示该观察窗口内未出现,不证明永久丢失。 必须按实际客户端 Send 时刻统计间隔;触摸按住和分发使其大于计划等待。 这不是用户的聊天应用,通过也不代表真机故障已经解决。 文件页缓存、已部署方案与学习数据库保留,因此“新进程”不等于“全新存储冷缓存”。 第三轮完整原始记录与所有新进程阶段的逐次统计均已归档;不能据此宣称本轮修复已消除真机卡顿。
测试脚本问题与修正
- 第一轮
39134935586只等键盘可见即输入,共 14 个未完整完成的轮次; Foxy 还有初次重新部署等待,不能将这些失败换算为选词延迟。 - 第二轮
39159215618增加会话就绪等待,但仍要求无障碍树出现完整输入拼音, 导致 13 个未完整完成的轮次。截图确认 Foxy 已正确显示“你”“早上好”, 直通版也已显示“早上好”。Foxy 的 TextView 子类 setText 只更新自己的绘制数据, 加上浮动编码可见性,不能依赖无障碍 getText 查询;万象模糊音的显示编码也可能不同。 因此不能把这些观察断言失败称作产品丢键或无法中文输入。 - 第三轮
39160792515改用实际提交核验。两款产品的所有 fresh 阶段均通过, 共 192 次,统计全部保留,见下表。Foxy 的 12 个返场阶段均失败,首键未形成预期编码: 脚本只查询“进程曾经 ready”,误用了上一编辑器/设置页面的 soft_cursor 回调, 返回当前编辑器后没有等其新会话完成,因此不能给出可比返场延迟。 - 同轮发送测试 Foxy 12 次均发送完整预期文本,直通 10 次完整、2 次前提不成立。 其中原始分类
candidate_missing的截图是“正在准备中州韵”,并无候选,日志中 没有 select 调用;脚本读到 generation 3 ready,却在 generation 5 尚未完成时打字/点选。 另一例截图中编码/候选仍在变化,后续提交为“找傻瓜好”,并非预期稳定候选。 不能把这两例称作复现了用户“选中候选后发送丢字”,原始数据与人工核验结论分别保留。
脚本错误由本次测试实现造成,不用产品优化掩盖。修正版本 ed298379 要求: 直通版最新 input_binding 的代次必须等于最后一个 ready bind;Foxy 最后一次 onStartInput 必须属于本测试编辑器,并有其后的 soft_cursor 回调。条件保持 500ms,再输入语料; 输入后固定等待 1000ms,并保存点选前截图。以一轮两次选词及一轮快速发送的小矩阵验证, 不重新跑已经完成的六轮新进程点选,也不把两种等待条件下的计时合并。 这些方法均不验证“键盘显示瞬间立即打字”的能力,没有模拟打字预热。
新进程阶段的可比较结果
下表来自 运行 39160792515。 两款产品所有新进程(fresh)阶段均完成,每款每个语料六个新进程、每进程八次点选, 共 192 次正确且唯一上屏,fresh 阶段没有排除样本。随后 Foxy 返场阶段的就绪判断有误, 使整轮失败;这部分不能给出可比返场延迟,不能把整个运行标为通过。
中位数,单位 ms。起点在注入真实 ACTION_UP 之前,文字终点为编辑器 TextWatcher; 每语料每产品首次 n=6、后续 n=42。first 是会话就绪且候选稳定后的首个点选。
| 语料 / 点选 | Foxy UP→IC | 直通 UP→IC | Foxy UP→文字 | 直通 UP→文字 |
|---|---|---|---|---|
| 你 / 首次 | 74.43 | 31.58 | 76.93 | 34.71 |
| 你 / 后续 | 13.97 | 12.73 | 14.91 | 13.53 |
| 早上好 / 首次 | 78.39 | 29.87 | 80.82 | 32.01 |
| 早上好 / 后续 | 15.14 | 14.03 | 16.22 | 14.91 |
这轮首次点选的编辑器入口及文字变化均是直通版更短,后续中位数相差约 1–2ms。 这是两款具体 Release 在同一翻译环境的差异,不能归因于某一条修复、ART profile 或去掉 Fcitx。 由于没有重测修改前版本,也不构成本轮修改的前后因果证明。
为避免只选择抬手起点,按下→IC 的首次中位数:单字 Foxy 106.28 / 直通 72.91ms, 短语 120.81 / 70.80ms;后续均约 53–55ms。按住与分发包含在其中。 首次 UP→文字的范围:单字 Foxy 63.38–97.76 / 直通 30.79–43.67ms; 短语 Foxy 64.69–93.33 / 直通 29.59–33.69ms。六个首次样本不支持稳定 P95/P99。 后续 UP→文字样本 P95:单字 17.98 / 15.78ms;短语 35.55 / 16.48ms(Foxy / 直通)。 CPU onDraw、完整范围及逐次时间见 fresh-summary.json / fresh-records.json,onDraw 不等于显示帧。
两边 ProfileInstaller 返回 result=1,编译命令成功,实际 dexopt 状态均为 speed-profile。 Foxy 首个新进程轮次仍触发过重新维护,启动到键盘显示超过两分钟;这些时间不计入点选表, 也不用于冷启动速度结论。两边保留 OS 页缓存,但不能保证每轮文件缓存状态完全相同。 同一模型映射、ARM64 翻译桥、运行时 YAML 开关、候选截图和全部失败记录都在原始归档。
当前绑定就绪修正后的补验
运行 39163211881 全部通过, 工具源码 ed298379,仍使用同一个 12ab22cd Release APK。 每语料每产品一轮、每阶段两次选词,新进程与返场合计 16 次正确且唯一上屏。 独立客户端的四个条件(单字/短语 × 计划 20/100ms)每产品各一次,8 次全部完整发送, 清空/restartInput 后草稿为空;点选前八张截图逐一核验,均已有目标候选。 因此之前返场/发送的旧就绪记录问题已在这个小矩阵中通过回归。
实际候选 UP→客户端 Send 为 69.27–234.03ms,不是 20/100ms 的精确发送期限: 直通版 69.27–176.76ms,Foxy 154.19–234.03ms,受注入、按住与调度影响。 这是一轮功能性观察,不能比较故障率,也不能证明用户手机上更短间隔/更慢原生调用时无丢失。 正常选词统计不与上面六轮 fresh 的计时合并;没有再重复整个性能矩阵。 完整摘要、逐次值及截图见 current-binding-validation.zip 与 guard-*.json。
临时公开 APK 分支 perf/foxy-ci-fixture-2026-10-18 已在测试结束后删除, 功能分支从未纳入 Foxy APK。删除的是远程分支引用,不表示公开期间已获取的副本可撤回。 Foxy workflow 改为仅手动入口,并要求明确提供测试 APK 所在提交,不再自动引用该临时分支。
尚未完成的体验目标
- 真机出现过的 140.019ms 原生选词及快速发送丢字,尚未证明修复。 本次修正已消除日志中可确认的多余会话创建路径,但它与慢选词并非同一个问题。
- 启动瞬间立即输入不在本轮候选稳定后比较的验证范围内,不能宣称已验证全部冷启动体验。
- 此处只覆盖两个重复语料、固定键速和少量新进程,不能代表自然长文本、多样新词、 长期学习词库、其他应用或手机的 P95/P99。上下文调频开关已确认,但重复词统计 不等于充分覆盖新增上下文记忆时的数据库写入成本。
- 真机下一次慢调用应先用新增 core CPU/缺页/调度计数定位:CPU 与墙钟接近时查算法/ Lua/学习链;墙钟明显较大时查 I/O、调度或等待其他线程,再取具体栈或文件证据。计数本身不是根因。 不能据此直接关闭万象功能、改写用户脚本,或将学习异步化后宣称行为等价。