perf: cache keyboard rows and skip redundant layout reloads - #44
Conversation
There was a problem hiding this comment.
Pull request overview
This PR reduces input latency by caching prebuilt keyboard row containers keyed by a layout/style signature, and by avoiding redundant layout reloads / IME-window relayout triggers during frequent state transitions (layer switching, shift-based language toggles, font refresh).
Changes:
- Add per-signature reusable row caching in
BaseKeyboard.reloadLayout()and cache invalidation hooks (composition changes, font refresh). - Refactor
TextKeyboardlayout signature computation (including sub-mode sensitivity detection + memoization) and mark applied signatures to skip redundant rebuilds. - Gate
onKeyboardHeightSourceChanged()notifications to only run when the resolved keyboard height override actually changes.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/BaseKeyboard.kt | Cache and reuse built row containers per layout/style signature; invalidate cache on composition changes; expose cache clearing API. |
| app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt | Compute more stable layout signatures (optionally excluding sub-mode), memoize sub-mode usage scanning, and track applied signatures. |
| app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyboardWindow.kt | Clear row cache on font refresh; avoid redundant IME-window relayout calls when keyboard height doesn’t change. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Layer switches (macrokey "layer to") and shift-based language toggles used to rebuild the entire keyboard view tree on every key press; on ColorOS/OnePlus a single reloadLayout cost 120-560ms (vs 5-28ms on MIUI). - Cache built rows per layout/style signature and reuse them on subsequent reloads. Cached rows are evicted before composition-state changes mutate them in place, and before font-set refreshes whose flag was already consumed, so cached rows are never reused with stale appearance. - Reuse only when the cached rows were built from the exact same KeyDef instances; providers that re-create defs on every call (e.g. the builtin fallback layout) fall back to a rebuild instead of silently re-registering mismatched state. - Extract selectedLayoutArray with explicit grouping to avoid the ?:/?. precedence trap, so flat layouts keep a stable signature across shift language toggles and no longer trigger any rebuild. - Record the applied layout signature after a layer switch to skip the redundant second rebuild. - Notify the IME window of keyboard height changes only when the height actually changed. - Memoize the sub-mode layout scan per layout revision. Measured on OnePlus 12 (ColorOS 16): repeated layer switches complete in ~8-16ms with cached rows (previously 121-561ms per rebuild); shift language toggles no longer rebuild at all.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/BaseKeyboard.kt:227
- currentLayoutSignature() is now part of the correctness boundary for row reuse, but it defaults to an empty string. Any future BaseKeyboard subclass that changes its KeyDef layout without overriding this will silently risk reusing the wrong cached rows. Consider making this method abstract (or otherwise enforcing/deriving a non-empty, layout-dependent signature) to prevent accidental misuse.
/**
* Signature identifying the semantic keyboard content (layer, input method, sub mode, ...).
* Subclasses rendering different KeyDef sets must override this so cached rows are never
* reused across different layouts.
*/
protected open fun currentLayoutSignature(): String = ""
app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyboardWindow.kt:257
- In switchLayout(), the
target == currentKeyboardNamefast-path still triggers onKeyboardHeightSourceChanged() whenever notifyHeightChange=true. Calls like consumeOneShotLayerIfNeeded() typically end up here (switching to TextKeyboard when it’s already current) and can still cause an expensive updateKeyboardSize() even when TextKeyboard.currentLayoutHeightPercentOverride() didn’t change.
updateCompositionState()
}
applyAuxActions(lastAuxActions)
if (notifyHeightChange) {
service.inputView?.onKeyboardHeightSourceChanged()
}
- Include splitGapPercent and expandKeypressArea in the row-cache signature so split-gap / key-area preference changes are re-applied instead of reusing rows built with the old values. - registerReusableRowState validates every row/key before touching the space/compose registries (two-phase), and returns false on any mismatch so reloadLayout falls back to a full rebuild without leaving partially registered state behind. - Only SpaceKey is registered into spaceKeys on reuse, matching the fresh-build path (MiniSpaceKey was previously added on reuse only). - Make currentLayoutSignature() abstract so every subclass must provide one, preventing future dynamic-layout subclasses from silently reusing wrong rows. - Replace the whole-cache clear with LRU eviction (access-ordered LinkedHashMap, capped at 12 entries). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

问题背景
每次按键(macrokey“切层”、Shift 切换中英文)都会把整个键盘视图树拆掉重建。同一布局在小米 14 上一次
reloadLayout只要 528ms,561ms,导致切层和中英文切换有明显的零点几秒卡顿。而在一加 12(ColorOS 16)上要 121
本 PR 的核心思路:按“布局/样式签名”把已构建好的行容器缓存起来,后续重建直接复用,跳过昂贵的视图构造;
同时去掉几个冗余的重建/窗口重布局触发点。
改动内容
onCompositionStateChanged会原地重建 compose 视图)之前,先失效当前签名对应的缓存条目,保证被原地修改过的行永远不会以过期外观被复用;
checkAndApplyFontRefresh消费刷新标志后)之前,先清空行缓存,保证新字体真正被重新应用;KeyDef实例构建时才复用;对每次调用都重建 KeyDef 的提供方(如无布局文件时的内置兜底布局),回退为整树重建,而不是静默跳过状态重注册。
selectedLayoutArray并显式分组,避开?:与?.的优先级陷阱——扁平布局在中英文切换之间保持稳定签名,shift 切中英文不再触发任何重建。
实测数据
一加 12(ColorOS 16),与小米 14 上相同的自定义布局:
reloadLayout,每次 121~561ms;测试矩阵
备注
updateKeyboardSize还附带单手模式/悬浮条等副作用,建议合入前按需真机复核。