Skip to content

引擎库 :ime-engine 迁移对照

本文档整合所有 Java → v4 引擎层(:ime-engine)的迁移对比内容,涵盖架构、核心引擎、消息体系、状态机、输入列表、字典系统、X-Pad、剪贴板与收藏八个维度。

内容来源:000 §5、100 §2/§7、170 §7.1/§7.2、200 §2/§7、300 §2/§7、600 §2/§4、700 §2/§5


1. 架构对比

Java 版本采用自定义消息驱动的 MVP 架构,v4 采用 MVI + StateFlow。以下为引擎相关维度的全面对比:

维度Java v3Kotlin v4变更说明
架构模式自定义 MVP + 手动消息路由MVI + StateFlow单向数据流更清晰,状态可追踪
消息系统3 套消息(UserKeyMsg, UserInputMsg, InputMsg)统一 Intent + StateFlow类型安全、响应式、自动生命周期管理
状态管理可变对象 + 手动同步不可变 data class + StateFlow线程安全、可追踪
异步CompletableFuture + HandlerCoroutine + Flow结构化并发、简化异步代码
键盘逻辑深层继承(3 层)组合模式 + Sealed class逻辑集中、类型安全
配置SharedPreferencesDataStore + Flow类型安全、异步、响应式
数据库手写 SQLiteOpenHelperRoom类型安全、编译期检查、官方推荐
依赖管理手动构造手动构造注入(同 Java,但更简洁)项目规模适中,手动注入足够
输出桥接手动 when 分发(2 处重复)ImeOutputBridge 语义化桥接(1 处分发)消除重复,桥梁实现者无需理解 ImeOutput 类型体系
日志Logger(仅 DEBUG 生效)ImeLog(分级 + 持久化 + 崩溃拦截 + 查看导出)release 也可用。核心基础设施在 :ime-engine,Android 特有实现在 :app

历史原因:Java 版本的 MVP + 手动消息路由虽然实现了单向数据流,但三套消息体系(共 53+ 种消息类型)导致理解成本高、分发易出错。IMEService 职责过重,既是消息路由器又负责 InputConnection 操作和生命周期管理。v4 将消息路由职责内化到引擎的 reduce() 函数,通过 StateFlow 自动传播状态变更,消除了手动分发。


2. 核心引擎 class 迁移

来源:170 §7.1

Java Classv4 对应变更说明
IMEditorImeEngine引擎核心入口点,MVI 模式替代消息中介。提供 statehandleGesture()handleIntent()updateConfig()attachOutputBridge()
IMEditorView(已废弃)View 层职责由 :ime-ui 的 Compose 组件承担,引擎不依赖 View
IMEditorDictImeDictProvider / ImeSqliteDictProvider不再单例,由引擎使用者显式创建并注入。内置 SQLite 实现从 assets/dict/ 加载预构建数据库
IMEConfigImeConfig统一配置,合并原引擎配置(ImeEngineConfig)与应用配置(Config),含 EngineConfigUiConfig 明确隔离
KeyInputKeysealed class 替代继承体系,子类:CharCtrlCandidateMathOpSymbolXPadNull
KeyFactoryInputKey 的 companion factory按键工厂,不再独立类
InputInputKey / InputWord拆分为按键和字词两个维度,职责更清晰
InputFactoryInputWord 的 companion factory输入工厂,不再独立类
KeyboardKeyboard:ime-engine domain)sealed class 组合模式替代继承树
BaseKeyboardKeyboard sealed class 基类行为从多层继承提取为独立共享组件
PinyinKeyboardPinyinKeyboard(domain)组合模式:注入 PinyinDictImeConfigKeyboardStateMachine
LatinKeyboardLatinKeyboard(domain)复用拼音的滑行 / X-Pad 模式
NumberKeyboardNumberKeyboard(domain)无子状态
SymbolKeyboardSymbolKeyboard(domain)直接迁移
EditorKeyboardEditorKeyboard(domain)直接迁移
EditorEditKeyboardEditorEditKeyboard(domain)直接迁移
MathKeyboardMathKeyboard(domain)嵌套 InputList
EmojiKeyboardEmojiKeyboard(domain)直接迁移
PinyinCandidateKeyboardPinyinCandidateKeyboard(domain)直接迁移
InputCandidateKeyboardInputCandidateKeyboard(domain)直接迁移
InputListCommitOptionKeyboardInputListCommitOptionKeyboard(domain)直接迁移
InputListInputList(domain)不可变数据模型重构,1100+ 行可变类 → data class + copy()
InputboardInputboard(domain)直接迁移
FavoriteboardFavoriteboard(domain)直接迁移(但功能拆分为 ClipboardService + FavoriteService
KeyboardContextKeyboardContext(domain)直接迁移
InputboardContextInputboardContext(domain)直接迁移
FavoriteboardContextFavoriteboardContext(domain)直接迁移
BaseInputContextBaseInputContext(domain)直接迁移
KeyboardStateMachineKeyboardStateMachine(domain)BaseKeyboard 中的状态管理逻辑提取为独立组件
InputListOperatorInputListOperator(domain)InputList 中的操作逻辑提取为独立组件
FeatureRegistryFeatureRegistry(domain)新增:管理 Feature 的启用 / 禁用

共享组件提取

Java 版本中 BaseKeyboard 的共享行为被提取为 v4 的独立组件:

Java BaseKeyboard 中的共享行为v4 独立组件变更说明
playKeyAudio()KeyAudioPlayer.play(keyType)独立音频播放组件
updateInputList()InputListOperator.apply(intent, list)独立输入列表操作器
fireInputMsg()StateFlow 自动传播不需要手动触发
evaluateInputWordKeys()CandidateQuery.query(dict, spell)独立候选查询
evaluatePinyinCandidates()PinyinCandidateEvaluator.evaluate(dict, input)独立拼音候选评估
evaluateLatinCompletion()LatinCompletionEvaluator.evaluate(dict, input)独立拉丁补全评估

3. 消息体系迁移

来源:170 §7.2

Java 版本有三套消息体系(共 53+ 种消息类型),v4 统一为 Intent + StateFlow 两层:

Java Classv4 对应变更说明
UserKeyMsg / UserKeyMsgType / UserKeyMsgData(7 种)InputGesture统一为坐标无关的逻辑手势描述(Tap/LongPress/Swipe/Flip/XPadZonePath/CandidateTap)
UserInputMsg / UserInputMsgType / UserInputMsgData(11 种)ImeIntent统一为用户意图(PressKey/SelectCandidate/PageCandidate/SwitchKeyboard/CommitInput/DeleteInput 等 16 种)
InputMsg / InputMsgType / InputMsgData(35+ 种)ImeOutput统一为引擎编辑输出(CommitText/RevokeCommit/InsertPairedSymbols/MoveCursor/SelectRange/PerformEdit 6 种)
InputMsgListenerImeOutputBridge桥接模式替代监听器。桥梁实现者只需实现语义方法,无需理解 ImeOutput 类型体系
UserMsgListener(已废弃)引擎内部统一 dispatchToTarget() 自动分发
BaseMsg(已废弃)v4 不使用通用消息基类

历史原因:Java 版本的三套消息体系导致了 IMEService 职责过重——它既是消息路由器(UserMsg → IMEditor, InputMsg → IMEditorView),又负责 InputConnection 操作。InputMsgType 枚举膨胀至 35+ 值,不同关注点混在一个枚举中。v4 将上行消息(UserKeyMsg + UserInputMsg)统一为 ImeIntent,将下行消息(InputMsg)替换为 StateFlow 自动传播 + ImeOutput 语义化输出,彻底消除了手动消息分发。

ImeOutput 与 InputMsg 的映射

InputMsgType(Java)ImeOutput(v4)说明
InputList_Commit_DoingImeOutput.CommitText提交文本,支持可替换字符列表
InputList_Committed_Revoke_DoingImeOutput.RevokeCommit撤销提交,由桥梁根据选区快照恢复
InputList_PairSymbol_Commit_DoingImeOutput.InsertPairedSymbols插入成对符号
Editor_Cursor_Move_DoingImeOutput.MoveCursor移动光标
Editor_Range_Select_DoingImeOutput.SelectRange扩展选区
Editor_Edit_DoingImeOutput.PerformEdit编辑操作(退格、全选、复制、粘贴、剪切、撤销、重做)

4. 状态机迁移

来源:100 §2 + §7

Java 版本通过 State 链表 + 10 个独立 StateData 类实现状态机,v4 重构为 Sealed class + 独立 KeyboardStateMachine 组件。

Java 状态类型 → v4 对应

Java 键盘类型Java 状态v4 对应变更说明
PinyinKeyboardInputChars_Input_WaitPinyinInput.Waiting直接对应
PinyinKeyboardInputChars_Slip_DoingPinyinInput.Slipping直接对应
PinyinKeyboardInputChars_Flip_DoingPinyinInput.Flipping直接对应
PinyinKeyboardInputChars_XPad_DoingPinyinInput.XPadding直接对应
PinyinCandidateKeyboardCandidate_ChooseCandidateSelection.Choosing直接对应
PinyinCandidateKeyboardCandidate_Filter_BasicCandidateSelection.Filtering直接对应
PinyinCandidateKeyboardCandidate_Filter_AdvanceCandidateSelection.AdvanceFiltering直接对应
InputListCommitOptionKeyboardCommit_Option_ChooseCommitOptionChoosing直接对应
EditorEditKeyboardEditor_Edit_CursorEditorEditing.CursorMoving直接对应
EditorEditKeyboardEditor_Edit_SelectionEditorEditing.TextSelecting直接对应
SymbolKeyboardSymbol_ChooseSymbolChoosing直接对应
EmojiKeyboardEmoji_ChooseEmojiChoosing直接对应
LatinKeyboardInputChars_Slip/XPadPinyinInput.Slipping/XPadding拉丁键盘复用拼音的滑行 / X-Pad 模式
NumberKeyboard无子状态Idle直接对应
MathKeyboard无子状态Idle直接对应
EditorKeyboard无子状态Idle直接对应

Java 状态数据类 → v4 对应

Java StateDatav4 对应变更说明
InputCharsSlipStateDataPinyinInput.Slipping 的 data class 字段合并到 sealed class 子类
InputCharsFlipStateDataPinyinInput.Flipping 的 data class 字段合并到 sealed class 子类
PagingStateDataCandidateSelection.Choosing.pageIndex/pageSize合并到候选选择状态
PinyinCandidateChooseStateDataCandidateSelection.Choosing合并到 sealed class 子类
PinyinCandidateFilterStateDataCandidateSelection.Filtering合并到 sealed class 子类
PinyinCandidateAdvanceFilterStateDataCandidateSelection.AdvanceFiltering合并到 sealed class 子类
EmojiChooseStateDataEmojiChoosing合并到 sealed class 子类
SymbolChooseStateDataSymbolChoosing合并到 sealed class 子类
EditorEditStateDataEditorEditing 子类合并到 sealed class 子类
InputListCommitOptionChooseStateDataCommitOptionChoosing合并到 sealed class 子类

状态管理机制对比

维度Java 版本v4 版本变更说明
状态定义10 个独立 StateData 类 + State 链表KeyboardState sealed class 层级编译期类型约束,非法状态转换不可构造
状态转换分散在 BaseKeyboard 各方法中KeyboardStateMachine.transition() 集中处理转换规则明确,易追踪
状态历史State.previous 链表(可能无限增长)ArrayDeque<KeyboardState>(maxSize = 10)有界历史栈,防止内存泄漏
状态回退通过 previous 链回退stateHistory.pop()简洁明确
键盘切换无清空逻辑切换时清空历史栈不同键盘类型之间无回退关系

历史原因:Java 版本的状态类型分散在 10 个独立的 StateData 类中,缺乏统一的类型约束,任何状态都可以转换到任何其他状态,没有编译期检查。State.previous 链表可能无限增长导致内存泄漏。状态转换逻辑分散在 BaseKeyboard 的各个方法中,没有集中定义转换规则。v4 通过 Sealed class 提供编译期类型安全,通过 KeyboardStateMachine 集中管理转换规则,通过有界栈替代无限链表。


5. 输入列表迁移

来源:200 §2 + §7

数据结构对比

Java InputList 功能v4 对应变更说明
inputs(ArrayList,可变)inputs: List<InputItem>(不可变 List)线程安全,无需同步
cursorIndexgapIndex: Int + init 断言显式边界检查,编译期断言
pending(CharInput,与 InputList 混合)PendingInput 独立数据类关注点分离:临时状态与持久化输入列表分开
completions(与 InputList 耦合)PendingInput.completions与 pending 绑定,补全由查询逻辑管理
gapSpacing(是否显示间隔开关)始终显示 GapInput简化:移除 gapSpacing 开关
mathExprNested(递归 InputList)InputList.mathExprNested递归结构保留
Revertion(撤销快照)InputListEditor(undo/redo 栈)标准化的撤销 / 重做,支持多步
append()appendChar()返回新实例(不可变)
delete()deleteCharBeforeCursor()返回新实例(不可变)
clean()clean()返回新实例(不可变)
commit()ViewModel 中处理提交逻辑不属于 InputList
evaluateInputViewData()InputViewData 计算分离视图数据计算
evaluateGapIndex()moveCursorTo()显式游标操作
对偶符号处理PairSymbol data class类型安全
剪贴板输入InputClip + InputTextType类型安全的剪贴板检测
收藏输入InputFavorite独立数据类

输入项类型对比

Java 输入项v4 对应变更说明
CharInputInputItem.Chardata class,不可变,支持 replacementsnextReplacement()
GapInputInputItem.Gapdata object,单例
MathExprInputInputItem.MathExpr嵌套 InputList

线程安全对比

问题Java 版本v4 版本
并发访问ArrayList 从主线程和异步字典回调线程同时访问,可能 ConcurrentModificationException不可变 List<InputItem> + StateFlow 原子性保证
状态变更可变对象直接修改copy() 创建新实例,原始实例不受影响
异步操作CompletableFuture + 手动同步协程 + reduce 串行执行

历史原因:Java 版本的 InputList 是一个 1100+ 行的可变类,存在线程安全风险(主线程 UI 更新 + 异步字典查询回调同时访问 inputs)。pending 临时状态与持久化的输入列表属于不同关注点却混在一起。cursorIndex 直接操作列表索引,插入 / 删除时需要手动维护,容易出错。v4 通过不可变 data class + copy() 模式彻底解决线程安全问题,通过 PendingInput 分离临时状态,通过 gapIndex 断言确保游标位置合法性。


6. 字典系统迁移

来源:300 §2 + §7

架构对比

Java 字典功能v4 对应变更说明
IMEditorDict 单例DictRepository + Room移除单例,依赖注入;ImeDictProvider 为对外接口
IMEditorDict.openAsync()Room 自动管理Room 处理数据库连接生命周期
IMEditorDict.close()Room 自动管理生命周期绑定
PinyinDict.findWords()PinyinWordDao.lookupBySpell()类型安全,编译期 SQL 检查
PinyinDict.findPhrases()PinyinPhraseDao.lookupBySpells()类型安全,编译期 SQL 检查
PinyinCharsTree(可变 Trie)不可变 PinyinCharsTree data class线程安全,Builder 模式构建
Hmm + Viterbi(纯 Java 实现)HmmModel + ViterbiDecodervalue class 封装状态 / 观察值,协程化大规模计算,不可变 Map
UserInputDataDict.record()UserInputDao.incrementFrequency()Room DAO,类型安全
UserInputDataDict.findCompletions()UserInputDao.getCompletions()Room DAO,类型安全
UserInputFavoriteDictFavoriteDao + Flow响应式查询,observeFavorites() 返回 Flow<List<InputFavorite>>
From_v0/v2_v3/v3_v4 升级类Room Migration标准化迁移框架
Emojis 枚举EmojiGroups data class不依赖数据库
SymbolGroup + SymbolSymbolGroup data class不依赖数据库
DictDBType 枚举Room Database 类移除,Room 管理 DB 类型
raw 字典资源复制createFromAsset("dict/...")assets/dict 组织,Room 自动复制

数据库方案对比

维度Java 版本v4 版本
ORM手写 SQLiteOpenHelperRoom
SQL 安全字符串拼接,运行时错误编译期 SQL 检查
查询结果Cursor 手动读取列值Entity data class 自动映射
异步CompletableFuture协程 + Room 内建 suspend 函数
响应式Flow<List<Entity>> 响应式查询
数据库升级分散的 From_vXRoom Migration 标准框架

对外接口对比

维度Java 版本v4 版本
字典创建IMEditorDict 单例,固定路径ImeDictProvider 接口 + ImeSqliteDictProvider 内置实现 + ImeInMemoryDictProvider 测试实现
字典替换不可替换第三方可实现 ImeDictProvider 替换整个字典层

历史原因:Java 版本的手写 SQL 无编译期检查,拼写错误只能在运行时发现。IMEditorDict 的单例模式使用 CompletableFuture 管理异步开关,逻辑复杂。数据库升级分散在不同类中,难以维护。查询结果以 Cursor 形式返回,需要手动读取列值,无类型安全。v4 通过 Room 提供编译期 SQL 检查、类型安全的 Entity/DAO、协程化的 suspend 函数和响应式 Flow 查询,彻底解决了这些问题。


7. X-Pad 迁移

来源:700 §2 + §5

核心组件对比

Java X-Pad 功能v4 对应变更说明
XPadView(自定义 View)XPadView(Compose Composable)声明式 UI,Canvas 绘制
XPainter 系列(5 个类:XTextPainter、XDrawablePainter、XPathPainter、XAlignPainter)Compose Canvas 直接绘制移除 Painter 体系,简化为直接绘制
XZoneXPadZone data class不可变,含 axiallabelchars
XPadStateKeyboardState.PinyinInput.XPadding集成到键盘状态机
XPadKeyInputKey.XPad sealed class 子类类型安全
Mixite 六边形网格自实现 HexGrid移除 Mixite 依赖,使用轴向坐标(axial coordinates)
HexagonDrawableCompose drawPathCompose 原生绘制
触摸事件处理Modifier.pointerInputCompose 手势 API
手势轨迹绘制Compose Canvas简化
PinyinKeyTable(使用 Mixite 生成)XPadLayout.fromPinyinKeys()保留键表生成逻辑,移除 Mixite
拉丁 X-Pad 模式XPadLayout.fromLatinKeys()保留复用逻辑

手势处理对比

维度Java 版本v4 版本
手势检测XPadView.onTouchEvent() + 区域查找Modifier.pointerInput + HexGrid.pixelToAxial()
区域高亮XPadView.onDraw() 中重绘GestureFeedbackState.xPadPathGestureFeedbackPanel
状态更新XPadState → 触发重绘KeyboardState.XPadding → StateFlow → UI 自动更新

历史原因:Java 版本依赖 Mixite 库处理六边形网格计算,但仅使用了其极小部分功能,引入了过重的依赖。XPainter 体系有 5 个接口 / 类,职责分散且代码量大。XPadView 同时处理触摸事件和绘制逻辑,耦合严重。XPadStateKeyboardState 的交互不清晰。v4 自实现轻量的 HexGrid 移除 Mixite 依赖,将 X-Pad 状态集成到键盘状态机的 XPadding 子状态,手势检测和视觉反馈分离到三层面板架构中。


8. 剪贴板与收藏迁移

来源:600 §2 + §4

职责分离

Java 版本将剪贴板和收藏混合在 Favoriteboard 中,v4 拆分为两个独立服务:

Java 功能v4 对应变更说明
Favoriteboard(混合职责)ClipboardService + FavoriteService职责分离:剪贴板检测和收藏管理各自独立

剪贴板功能对比

Java 剪贴板功能v4 对应变更说明
ClipboardManager.OnPrimaryClipChangedListenercallbackFlow 封装协程化,awaitClose 自动注销监听器
InputClip + InputTextType 枚举InputClip.from() + InputTextType.detect()类型安全的自动检测,集中管理正则
正则提取(分散在多处)ClipboardService.extractType()集中管理,按类型提取
剪贴板浮动提示(自定义 View)ClipTipPopup(Compose)声明式 UI
手动注册 / 注销监听器callbackFlow + awaitClose自动生命周期管理

收藏功能对比

Java 收藏功能v4 对应变更说明
UserInputFavoriteDictFavoriteService + FavoriteDaoRoom DAO + Flow,响应式收藏列表
收藏面板(自定义 RecyclerView)FavoriteListPanel + LazyColumnCompose 简化
收藏删除(手动按钮)SwipeToDismiss滑动删除,交互更自然
收藏使用计数FavoriteEntity.usageCount保留
收藏保存(从输入 / 剪贴板)FavoriteService.save()统一入口
用户数据备份不支持UserDataService(详见 040-用户数据导入导出

剪贴板数据类型对比

Java 类型v4 对应说明
URLInputTextType.Url保留
EmailInputTextType.Email保留
PhoneInputTextType.Phone保留
CaptchaInputTextType.Captcha保留
IdCardInputTextType.IdCard保留
CreditCardInputTextType.CreditCard保留
AddressInputTextType.Address保留
HtmlInputTextType.Html保留
InputTextType.Text新增:纯文本类型

历史原因:Java 版本的 Favoriteboard 同时处理剪贴板检测、剪贴板提示、收藏管理和智能提取四个职责,违反单一职责原则。剪贴板监听使用 OnPrimaryClipChangedListener 需要手动注册 / 注销,容易遗漏导致内存泄漏。收藏数据与字典耦合,UserInputFavoriteDict 直接操作数据库。类型检测正则分散在 InputClip 类和工具方法中。v4 将 Favoriteboard 拆分为 ClipboardService(协程化剪贴板监听)和 FavoriteService(Room DAO + Flow 收藏管理),各自拥有清晰的数据模型和业务逻辑。