Ubuntu ARM 平台下 Flutter 应用 CJK 字体显示修复工具
用于修复 Ubuntu ARM64 桌面环境中 Flutter 应用 CJK(中日韩)和部分非 ASCII 字符显示为方框的问题,支持 Snap 和非 Snap 应用。
适用范围:Flutter 3.50.0 之前、未启用 Fontconfig 的 ARM64 Linux Flutter 应用。工具会通过 Engine Hash 自动识别 Flutter 版本,优先使用已校验的 SO 替换;Snap 应用无精确 SO 时可回退到字体映射。
官方修复状态:Flutter 官方已 merge 我的 PR flutter/flutter#180235。从预计进入正式版的 Flutter 3.50.0 开始,ARM64 Linux Desktop Engine 会启用 Fontconfig。这个长期影响 ARM64 Linux 桌面用户的问题能在上游修复,我非常开心。检测到 Flutter 3.50.0 或之后版本时,本工具会直接跳过 SO 替换和字体映射。
在 Ubuntu ARM64 桌面环境(例如树莓派、飞腾、RK3588、Parallels 虚拟机等)中,部分 Flutter 应用会把 CJK 字符显示为方框。
已验证的根因是:旧版官方 ARM64 libflutter_linux_gtk.so 未链接 Fontconfig,Flutter Engine 无法通过系统字体配置完成字体回退。
Flutter 官方已合入我的 PR flutter/flutter#180235,将 ARM64 Linux Desktop 和 Embedded Engine 构建拆分,使桌面版启用 Fontconfig,同时避免影响嵌入式 Linux 构建。按照 Flutter 2026 发布窗口,这个修复预计进入 Flutter 3.50.0 正式版。
文本渲染链路:
Flutter App → libflutter_linux_gtk.so → Skia → Fontconfig
# 旧版官方 ARM64 Engine:无输出
readelf -d libflutter_linux_gtk.so | grep fontconfig
# 启用 Fontconfig 的 Engine:应包含 libfontconfig.so.1
readelf -d libflutter_linux_gtk.so | grep fontconfig详细分析见:FONTCONFIG_BUG_INVESTIGATION.md
本项目由 Krystic 使用 VS Code + Codex 协同开发。根因定位依赖自编译 Flutter Engine 并与官方产物对比验证。
对 Flutter 3.50.0 之前的受影响应用,使用启用 Fontconfig 的 libflutter_linux_gtk.so 替换旧版官方 SO:
- Snap 应用:使用
mount --bind,不修改应用文件 - 非 Snap 应用:直接替换 SO,自动备份并记录 SHA-256,恢复时校验原文件和备份
- 在线 SO 必须通过
SHA256SUMS校验后才会写入缓存
从 Flutter 3.50.0 起,本工具默认认为官方 ARM64 Linux Desktop Engine 已包含 Fontconfig 修复。检测到 Flutter 3.50.0 或之后版本时,flutter-font-fix 会提示官方已修复并返回成功,不会替换 SO、创建备份、写入修复记录或回退字体映射。
Snap 应用没有精确版本 SO 时,会回退到 Noto Sans CJK 字体映射。该方案只解决 CJK 字符,不能完整替代 Fontconfig。
- 🎯 双应用类型支持
- Snap 应用(
-a):精确 SO 替换;无匹配时回退字体映射 - 非 Snap 应用(
-e):按可执行文件定位 SO,支持精确版本和相似版本选择
- Snap 应用(
- 🔍 版本智能检测
- 从 SO 提取 Engine Hash,匹配 Flutter 版本
- 缓存版本对照表 7 天
- Flutter 3.50.0+ 自动跳过修复
- 📦 安全下载:在线 SO 先下载到临时文件,通过
SHA256SUMS校验后原子写入缓存 - 🔄 开机自启:systemd 服务可在重启后恢复 Snap 映射
- 🛠️ 智能管理
- 自动安装字体依赖(
fonts-noto-cjk) - 配置持久化(
/etc/flutter-cjk/) - 状态查看、移除、恢复、卸载
- Bash 自动补全
- 自动安装字体依赖(
curl -fsSL https://raw.githubusercontent.com/krystic/flutter-arm-cjk-fix/main/install.sh | sudo bash功能:
- ✅ 自动检测 ARM64 Linux 架构(非 ARM64 会提示并退出)
- ✅ 安装主脚本到
/usr/local/bin/flutter-font-fix - ✅ 安装功能模块到
/usr/local/lib/flutter-font-fix/ - ✅ 安装必要依赖(
bash-completion,fonts-noto-cjk) - ✅ 初始化配置目录(
/etc/flutter-cjk/) - ✅ 注册并启用 systemd 服务
- ✅ 安装 Bash 自动补全
# 下载脚本
sudo wget https://github.com/krystic/flutter-arm-cjk-fix/raw/main/flutter-font-fix \
-O /usr/local/bin/flutter-font-fix
# 赋予执行权限
sudo chmod +x /usr/local/bin/flutter-font-fix
# 下载功能模块
sudo mkdir -p /usr/local/lib/flutter-font-fix
for module in engine non-snap snap system cli; do
sudo wget \
"https://raw.githubusercontent.com/krystic/flutter-arm-cjk-fix/main/src/${module}.sh" \
-O "/usr/local/lib/flutter-font-fix/${module}.sh"
done
# 首次运行初始化(创建配置文件)
sudo flutter-font-fix -l
# 安装 Tab 补全(可选)
sudo flutter-font-fix -i
# 安装 systemd 服务(可选)
sudo flutter-font-fix --install-service使用精确版本的 SO 文件替换,无匹配时回退到字体映射:
sudo flutter-font-fix -a snap-store
# 输出示例:
# [OK] [snap-store] Flutter 引擎已替换为 libflutter_linux_gtk.so.3.38.1
# Flutter 引擎已替换,字体问题已根治。支持精确版本和相似版本选择(如 3.24.3 用于 3.24.5):
sudo flutter-font-fix -e rustdesk
# 或
sudo flutter-font-fix -e /usr/bin/rustdesk
# 相似版本选择示例:
# [WARN] 未找到精确版本 3.24.5
# [INFO] 找到兼容版本:
# [1] 3.24.3 [本地/local]
# [2] 3.24.1 [线上/online]
# 尝试这些版本之一吗?[1]: 工作原理:
- 从 SO 文件提取 40 位 Engine Hash
- 查询 Flutter GitHub 匹配版本(7 天缓存)
- 本地精确版本时自动跳过线上查询
- 支持 CDN 加速:
--cdn https://raw.staticdn.net/
交互式选择应用内的字体文件并映射:
sudo flutter-font-fix -c snap-store
# 脚本会:
# 1. 搜索应用内所有字体文件
# 2. 显示编号列表供选择(支持逗号分隔多选,'a' 全选)
# 3. 为每个字体选择目标 Noto 字重(Regular/Bold/Light/Medium)
# 4. 保存配置并立即应用# 简要列表
flutter-font-fix -l
# 详细信息
flutter-font-fix -d
# 移除映射
sudo flutter-font-fix -r <app_name>
# 恢复非 Snap 应用(名称或完整可执行文件路径)
sudo flutter-font-fix -r rustdesk
sudo flutter-font-fix -r /usr/bin/rustdesk
# 移除全部
sudo flutter-font-fix --remove-all| 命令 | 功能说明 |
|---|---|
sudo flutter-font-fix -e <exe> |
修复非 Snap 应用(支持精确版本和相似版本) |
sudo flutter-font-fix -a <app> |
修复 Snap 应用(仅使用精确版本,未匹配时回退字体映射) |
sudo flutter-font-fix -c <app> |
使用自定义字体修复 Snap 应用 |
sudo flutter-font-fix -r <app> |
移除/卸载映射(包括 SO 和字体) |
flutter-font-fix -l | --list |
列出已映射应用 |
flutter-font-fix -d | --detail |
查看详细映射信息 |
sudo flutter-font-fix --remove-all |
移除全部映射 |
sudo flutter-font-fix --uninstall-service |
卸载 systemd 服务 |
sudo flutter-font-fix --uninstall |
完全卸载 |
sudo flutter-font-fix -i | --install-completion |
安装 Bash 补全 |
| 参数 | 功能说明 |
|---|---|
--cdn <prefix> |
覆盖 GitHub Raw CDN 前缀(含末尾斜杠) |
CDN 加速
# 使用 CDN 镜像
sudo flutter-font-fix --cdn https://raw.staticdn.net/ -a snap-store
sudo flutter-font-fix --cdn https://ghproxy.com/https://raw.githubusercontent.com/ -e rustdesk完全卸载
sudo flutter-font-fix --uninstall修复 Snap Store
sudo flutter-font-fix -a snap-store修复 rustdesk
sudo flutter-font-fix -e rustdesk查看状态
flutter-font-fix -d
# 输出:
# - snap-store [so/SO替换]
# • Flutter Engine: libflutter_linux_gtk.so.3.38.1
# - rustdesk [so/SO替换]
# • Flutter Engine: libflutter_linux_gtk.so.3.24.3/etc/flutter-cjk/ # 配置目录
├── ubuntu.conf # 官方模式应用列表(格式:app|so 或 app|font)
├── flutter.engine.hash.version # Flutter Engine Hash→版本对照表缓存(7天)
├── executables/ # 非 Snap 应用修复记录
│ └── <executable-path-sha256>.conf # 路径、版本、备份及文件校验值
└── <app_name>.conf # 自定义模式配置文件
/usr/local/lib/flutter-cjk/ # SO 文件本地缓存
└── libflutter_linux_gtk.so.X.Y.Z # 下载的 SO 文件
/etc/systemd/system/
└── flutter-font-fix.service # 系统启动服务
/etc/bash_completion.d/
└── flutter-font-fix # Tab 补全脚本
/usr/local/bin/
└── flutter-font-fix # 主执行脚本
/usr/local/lib/flutter-font-fix/ # 主程序模块
├── engine.sh # Engine 版本、下载与 SHA-256 校验
├── non-snap.sh # 非 Snap 替换、记录与安全恢复
├── snap.sh # Snap SO 挂载与字体映射
├── system.sh # 配置、服务、补全和状态管理
└── cli.sh # 参数解析与命令入口
<repository>/lib/ # GitHub 仓库 SO 文件库
└── libflutter_linux_gtk.so.X.Y.Z # 预编译的 SO 文件(30-40MB)
ubuntu.conf - 应用配置列表(新格式)
snap-store|so # SO 引擎替换
desktop-security-center|font # 字体映射
old-app # 旧格式(兼容)
ubuntu.conf仅记录 Snap 应用。非 Snap 应用使用executables/下的独立记录,避免同名可执行文件和包含空格的路径发生冲突。
flutter.engine.hash.version - Flutter Engine Hash→版本缓存
# 格式: hash | version
2c9bc1e4b1... | 3.38.1
a7f8e9d2c3... | 3.24.3缓存有效期 7 天,避免重复查询 Flutter GitHub 仓库
<app>.conf - 自定义模式配置示例
# Flutter Font Fix - Custom Font Configuration
# App: snap-store
# Generated: 2025-12-20 10:30:45
/snap/snap-store/current/fonts/Ubuntu-R.ttf|/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc
/snap/snap-store/current/fonts/Ubuntu-B.ttf|/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc- Hash 精确检测:从 SO 文件提取 Flutter Engine Hash(40 位),查询 Flutter GitHub 建立版本对照表(7 天缓存)
- 双源查找:本地优先,精确版本时跳过线上查询,自动下载在线版本(30-40MB,进度条)
- 智能替换:
- Snap 应用:
mount --bind替换(仅精确版本) - 非 Snap 应用:直接替换系统 SO(支持相似版本,自动备份
.bak),并持久化记录原始与替换版本、路径和 SHA-256
- Snap 应用:
- 字体映射兜底:无匹配 SO 时自动回退到 Noto CJK 字体映射
- 开机自启:Systemd 服务
After=snapd.service确保 Snap 就绪后执行
下载在线 SO 时,工具会先获取 lib/SHA256SUMS,确认目标文件存在有效的
SHA-256 条目,再下载到临时文件。只有实际摘要与清单完全一致时才会原子移动到
/usr/local/lib/flutter-cjk/;清单缺失、条目无效或摘要不匹配都会终止操作并删除临时文件。
# 1. 提取 Hash
strings libflutter_linux_gtk.so | grep -E '^[0-9a-f]{40}$'
# → 2c9bc1e4b1a7f8e9d2c3456789abcdef01234567
# 2. 匹配版本(查询 Flutter GitHub,7天缓存)
# → 3.38.1
# 3. 查找/下载 SO
# 本地: /usr/local/lib/flutter-cjk/libflutter_linux_gtk.so.3.38.1
# 线上: https://raw.githubusercontent.com/.../libflutter_linux_gtk.so.3.38.1字体映射方案(兜底):
Ubuntu-R.ttf/RI → NotoSansCJK-Regular.ttc
Ubuntu-L.ttf/LI → NotoSansCJK-Light.ttc
Ubuntu-M.ttf/MI → NotoSansCJK-Medium.ttc
Ubuntu-B.ttf/BI → NotoSansCJK-Bold.ttc
自定义模式(-c)可自由选择映射关系。
[Unit]
Description=Flutter Font Auto-Mount Service (System Level)
After=snapd.service
Requires=snapd.service
ConditionPathExists=/etc/flutter-cjk
[Service]
Type=oneshot
ExecStartPre=/bin/sleep 3
ExecStart=/usr/local/bin/flutter-font-fix --startup
RemainAfterExit=yes
[Install]
WantedBy=multi-user.targetQ: -a 模式和 -e 模式有什么区别?
- -a 模式:针对 Snap 应用,仅使用精确版本的 SO 文件,未找到时回退到字体映射
- -e 模式:针对非 Snap 应用(如 rustdesk),支持精确版本和相似版本选择(如 3.24.3 用于 3.24.5)
Q: 什么是相似版本?
- 相似版本指主版本号相同的 Flutter 版本(如 3.24.1、3.24.3、3.24.5 都属于 3.24.x)
- 在
-e模式下,如果找不到精确版本,会提示选择相似版本 - Snap 应用(
-a模式)不使用相似版本,保证稳定性
Q: 如何恢复原始 SO 文件?
# 非 Snap 应用:校验当前文件和备份后安全恢复
sudo flutter-font-fix -r rustdesk
# 同名程序较多时可指定完整路径
sudo flutter-font-fix -r /usr/bin/rustdesk
# Snap 应用(-a 模式)使用 mount,直接卸载即可
sudo flutter-font-fix -r snap-store如果当前 SO 在修复后被应用升级或其他程序修改,工具会拒绝用旧备份覆盖,
保留修复记录并提示人工检查。--remove-all 会恢复全部非 Snap 应用;
--uninstall 在任何非 Snap 应用无法安全恢复时会中止,以免删除恢复依据。
Q: 修复后需要重启应用吗?
- 通常不需要。映射立即生效,但部分应用可能需要重启以重新加载字体缓存
Q: 会影响系统其他应用吗?
- 不会。Snap 应用的 SO 替换仅作用于该应用内部
- 非 Snap 应用会修改系统目录中的 SO,但会自动备份原文件
Q: 如何验证修复是否成功?
# 检查挂载状态(Snap 应用)
mount | grep flutter
# 查看配置
flutter-font-fix -d
# 检查 SO 文件(非 Snap 应用)
ldd /usr/bin/rustdesk | grep libflutter_linux_gtk.so
ls -la /path/to/libflutter_linux_gtk.so*
# 重启应用并观察中文显示Q: 缓存文件在哪里?
# Hash→版本对照表缓存(7天有效期)
/etc/flutter-cjk/flutter.engine.hash.version
# 下载的 SO 文件缓存
/usr/local/lib/flutter-cjk/libflutter_linux_gtk.so.*- 检查 Noto 字体是否已安装:
dpkg -l | grep fonts-noto-cjk - 验证挂载点是否生效:
mount | grep <app_name>
- 尝试重启应用或重新映射
- 检查服务状态:
sudo systemctl status flutter-font-fix.service
- 查看服务日志:
sudo journalctl -u flutter-font-fix.service
- 手动触发服务:
sudo systemctl start flutter-font-fix.service
- 重新加载补全脚本:
source /etc/bash_completion.d/flutter-font-fix - 或重新安装:
sudo flutter-font-fix -i
主脚本可以被安全地 source:只有直接执行时才进入 CLI 主函数。测试可通过
以下环境变量把所有路径切换到临时目录:
FLUTTER_CJK_CONFIG_DIRFLUTTER_CJK_SO_LIB_DIRFLUTTER_CJK_SNAP_ROOTFLUTTER_CJK_NOTO_DIRFLUTTER_CJK_SERVICE_FILE
安装时设置 FLUTTER_CJK_TARGET_MODULE_DIR 或 FLUTTER_CJK_SO_LIB_DIR 后,
安装器会将实际路径记录在程序旁的 .paths 文件中;后续运行和卸载会自动使用
这些路径,无需再次传入环境变量。运行时显式设置环境变量仍可临时覆盖记录值。
测试尚未合并的远程分支时,可在安装阶段设置
FLUTTER_CJK_REPO_REF=<branch>。安装器会将分支名一并写入 .paths,使入口、
模块、版本映射、SO 文件、SHA-256 清单和 GitHub API 版本查询始终使用同一分支。
未设置时默认使用 main。
本地运行质量检查:
bash -n flutter-font-fix install.sh src/*.sh tests/run-tests.sh
shellcheck flutter-font-fix install.sh src/*.sh tests/run-tests.sh
bash tests/run-tests.sh测试使用临时目录和模拟的 curl,不会修改 /etc、/usr/local、/snap,
也不会执行真实的挂载或 systemd 操作。GitHub Actions 的 quality.yml
会在相关文件变化时自动运行相同检查。
仓库中的 flutter-font-fix 是轻量入口和模块加载器,实际功能位于 src/:
| 模块 | 职责 |
|---|---|
engine.sh |
Engine Hash 解析、版本匹配、在线查询、下载与摘要校验 |
non-snap.sh |
非 Snap SO 替换、持久化记录和安全恢复 |
snap.sh |
Snap Engine 绑定挂载、字体映射及卸载 |
system.sh |
systemd、配置、补全、列表和自定义字体 |
cli.sh |
CLI 帮助、参数解析和主入口 |
本地运行时入口从同级 src/ 加载模块;安装后从
/usr/local/lib/flutter-font-fix/ 加载。缺少任何模块都会明确报错并停止,
不会在功能不完整的状态下继续运行。
本项目由 Krystic 使用 VS Code + Codex 协同完成。核心问题的定位依赖自编译 Flutter Engine、readelf 对比验证,以及社区中关于 Snap/Flutter 字体问题的技术讨论。
感谢 Flutter 社区和相关资料对问题定位的帮助。
欢迎通过 Issues 报告问题或提出建议。