Files
yenharvey b554dbe7bc fix(android): 重启输入的应用里打得快也不丢组句
之前(fbd5d342)同一输入框重启输入时,用重启给的光标把框里的拼音重新标成组合文字。打得快时这个光标与光标前的文字
是应用那边稍早的状态,我们后面的改动还在路上,对不上就丢掉组句;支付宝里淘宝闪购的搜索框快打快删时候选照样消失。
- 重启时只要在组句就留着,只记下「输入框里的组合文字标记没了」(重启后拼音还在框里,成了普通文字)
- 下一次真要改输入框时再读光标前的文字:认出它停在这段组句交过的哪一版拼音(最近 8 版里最长的那个),先删掉再写新的;
  重启那一刻新连接可能还没就绪,读不准,所以不在重启时读
- 读不到或哪一版都对不上就不删,组句照旧

取舍:重启到下一键之间框里的拼音没有下划线,下一键就恢复。

测试:真机(小米 14 Pro)支付宝淘宝闪购搜索框快打 nihao、连删三下,候选一直在、框里没有重复字母,空格上屏干净的 你;
点联想词 是、国家 一路接着给下一批;用户真机按平常速度试过
2026-11-05 18:38:21 +08:00
..
2026-10-20 20:35:18 +08:00
2026-10-20 20:35:18 +08:00
2026-10-20 20:35:18 +08:00
2026-10-20 20:35:18 +08:00
2026-10-20 20:35:18 +08:00

青简 Android

Kotlin 的 InputMethodService 壳 + Rust 的 libqingjian_android.so。Android 的输入法跑在自己的进程里, 没有 Server / 薄壳之分,Engine 直接装在 .so 里,经 JNI 调用。

功能怎么用见 docs/user/platforms/android.md,各功能怎么接到 Core、取舍与踩坑见 docs/notes/android.md。

目录

  • core/:Rust crate qingjian-android(workspace 成员)。assembly 装配 Engine;session/ 是 Session,把软键盘操作翻成 Engine 调用 (keys 打字选词与补全、scheme 切方案与语言、options 键盘设置、学习语言与私密输入、dictionaries 附加词库重挂、display 候选条上看得见的候选、cloud 快捷翻译与释义兜底、prediction 云联想与整句补全、clipboard 拆词与常用语重挂、voice 语音结果上屏、按设置换识别方与录音进出、swipe 滑行解出的字母进组句);frame/ 是回给壳的一帧(JSON,另有语音识别的事 VoiceEvent); store/ 是不经会话的数据读写(catalog 符号 / 表情面板目录、clipboard / phrases 剪贴板与常用语、code 代码显示、dictionaries 词库页的列出导入删除、learning 高级页导出与清空的文件清单、shuangpin 方案与键印、stats / vocabulary App 的统计与生词本); cloud/ 是云端配置与测试连接;appearance/ 是外观(内置主题列表、按主题推出的键盘配色、设置页的主题预览、字号档);strip/ 把帧里的候选画成候选条位图(StripPainter),Handle 是句柄背后的会话 + 画笔; jni/ 是出口(mod.rs 句柄生命周期,operations.rs 各个操作,strip.rs 候选条位图:renderStrip 画、copyStrip 拷像素,theme.rs 主题列表、键盘配色、预览与候选条换主题,voice.rs 语音与识别配置、测试连接,swipe.rs 滑行)。
  • app/:Android 应用模块,包名 app.qingjian.inputmethod,按职责分包,同一职能的再分子包:
    • core/:JNI 封装(Native / Engine,「青简翻译」用的轻量句柄 TranslationEngine)与首次启动解随包数据(BundledData);frame/ 帧的数据类,strip/ 画好的一帧候选条(位图 + 分界 + 可点区域), store/ 不要句柄的数据(面板目录、剪贴板与常用语、双拼方案、主题与预览图、统计、生词本)。
    • service/:QingjianInputMethodService 只管生命周期、设置与视图,InputPanel 拼出整块视图,GameMode 游戏键盘(GameFrame 是它的浮层),CloudServices 把云联想、释义兜底、快捷翻译与语音识别的配置交给引擎; input/ 是输入逻辑:Composer(键 → Engine 操作 → 输入框)、Language 输入语言、EditorKind 哪些输入框不过引擎、EnterLabel 回车键文字。
    • keyboard/:自绘键盘。KeyboardView 管触摸,SideColumn 九宫格拼音列,BottomActionsView 全面屏底部操作条,Haptics 震动,SwipeTrail 一笔滑行的判定、轨迹与交 Rust 的 JSON; layout/ 键位与排版(Key、KeyboardLayout 键位表、KeyboardMetrics),paint/ 绘制(KeyPainter、KeyIcons)。
    • theme/:配色。Palette 键盘与面板(由 Rust 按设置里的主题推出来),Appearance 深浅色,TextSize 候选字号档,AppColors App 页面(不跟主题)。
    • panel/:盖住候选条与键盘的面板。FunctionPanel / ToolbarPanel(定制工具栏)/ KeyboardPickerPanel / CatalogPanel(符号与表情)各一种,PanelKind 面板种类,PanelActions 是面板能做的事(服务实现); base/ 是共用的底子(PanelView 表头与滚动、Tabs、ScrollState、Ink 画笔、PanelIcons),clipboard/ 剪贴板与常用语(ClipboardPanel、拆词 PiecesPanel), editor/ 浮在键盘上方接管打字的编辑条(FloatingEditor、常用语编辑框 PhraseEditor)。 VoicePanel 是说话时盖上的语音面板。
    • voice/:语音输入。VoiceInput 是唯一入口(长按空格、工具栏话筒、底栏语音键),VoiceRecognizer 识别方式的接口(占位 UnavailableRecognizer、Rust 侧识别方 EngineRecognizer),MicrophonePermissionActivity 代输入法申请录音权限,VoiceConfig 是「语音识别」页的配置(识别服务、计费方式、密钥密文,与云端大模型分开存)。
    • translate/:快捷翻译,翻译条 TranslateBar 与流程 QuickTranslate;系统文字选择菜单的「青简翻译」ProcessTextActivity;cloud/ 是云端配置、密钥(Android Keystore)与当前网络(Network,「只在 Wi-Fi 下使用」)。
    • clipboard/:系统剪贴板的监听与图片的 content:// 出口。
    • candidates/:键盘上方那一条。CandidateBarView 在三种样子之间切:CandidateStrip 候选(贴 StripSource 给的位图,拼音行钉住、候选随滚动)、 Toolbar 工具栏(浮在键盘上的圆角卡片,面板表头同样是浮起卡片)、ResizeHandle 调高度手柄。
    • popup/:ChoicePopup 长按气泡,候选条(译词)与数字键(字母 / 数字)共用。
    • settings/:Settings 设置快照、BottomAction 与工具栏能放的 ToolbarTool;App 各页分三块:home/ 启动页(MainActivity + 三个 AppPage:HomePage / VocabularyPage / MePage), page/ 二级页(KeyboardSettingsActivity / AppearanceActivity / DictionariesActivity / StatsActivity / AdvancedActivity / AboutActivity / VoiceSettingsActivity 继承 PageActivity),widget/ 控件积木(SettingsWidgets、TabBar、Wash 渐变底、BarChart 柱状图、ThemePreview 主题预览)。

构建

需要 Android SDK(platform 36、NDK 29.0.13599879)、JDK 17 以上、Rust 目标与 cargo-ndk;不需要 Android Studio。

rustup target add aarch64-linux-android
cargo install cargo-ndk --locked
echo "sdk.dir=$HOME/Library/Android/sdk" > apps/android/local.properties

cd apps/android
./gradlew installDebug        # 编 .so、放随包数据、打包、装到连着的设备
./gradlew installDebug -PsideBySide  # 手机上装着发版签名的包时并排装(包名多 .dev),不用卸载、不丢数据
adb logcat -s qingjian        # Rust 侧日志

preBuild 之前会跑两个任务:cargoNdk(cargo ndk 编 arm64-v8a 的 release .so)与 stageData (data/generated/ 有 dict.qj 就带产品数据,没有退回 assets/sample/ 样例)。只编 arm64。

装好后打开「青简」,按页面上两步启用并切换输入法。

./gradlew assembleRelease 出 release 包;版本号取自 core/Cargo.toml,签名用的环境变量与发版流程见 docs/notes/release.md。

Rust 侧在本机就能测:cargo test -p qingjian-android。

约定

  • JNI 句柄只在主线程用;每个出口拦 panic,出事返回 null,壳当空帧处理。
  • 随包数据首次启动从 assets 解到 files/data/(.qj 要 mmap,领域词库在 dicts/ 下),装了新包才重解;学习数据在 files/user/,导入的词库在 files/user/dicts/。
  • 解数据和装配在后台线程,没装好之前字母原样上屏。
  • 密码、网址、邮箱、非文本输入框不过引擎。
  • 不依赖 qingjian-platform:它带进的 native-tls / OpenSSL 在 Android 交叉编译过不了;云端直接引 qingjian-predict 并关缺省 feature、开 rustls。