3. TouchGFX字体缓存机制:缓存的工作原理、缓存命中率优化、动态字体加载与卸载策略
字体缓存,说白了就是一块专门用来存放“已经渲染好的字形”的内存区域。你想想看,每次要显示一个字符,如果都去字体文件里重新解析轮廓、做抗锯齿、生成位图,那CPU可就累坏了。缓存的作用就是——把用过的字形存下来,下次再用直接拿,省掉重复计算。
我在项目中遇到过好几次这样的情况:界面刚启动时卡顿明显,后来一查,全是字体缓存没预热,每个字符都是第一次渲染,硬生生把启动时间拖长了一倍。嗯,这节我们就来彻底搞懂它。
3.1 缓存的工作原理
TouchGFX的字体缓存,本质上是一个键值对存储结构。键是字形ID(Glyph ID)加上字体大小和样式,值就是渲染好的像素数据。流程大致是这样:
- 应用层请求绘制一个字符,比如“中”。
- 渲染引擎先去缓存里查:这个“中”字,16号,粗体,有没有已经渲染好的版本?
- 如果有(缓存命中),直接取出位图数据,贴到画布上。
- 如果没有(缓存未命中),就去字体文件里解析轮廓,渲染成位图,存入缓存,再贴到画布上。
核心要点:缓存命中 = 零渲染开销。缓存未命中 = 完整渲染链路 + 缓存写入开销。所以优化的目标就是让命中率尽可能接近100%。
我习惯把缓存想象成一个“最近使用过的字形旅馆”。旅馆房间有限,住满了就得有人退房。谁退房?最久没被用过的那个退。这就是经典的LRU(Least Recently Used)淘汰策略。
个人经验:TouchGFX默认的缓存大小是4KB,对于简单界面够用。但如果你用了中文字体,一个16x16的字形位图就要256字节,4KB只能存16个汉字。所以做中文产品时,第一件事就是加大缓存。
3.2 缓存命中率优化
命中率上不去,缓存再大也是白搭。我总结了几条实战经验:
- 预热缓存:在界面初始化阶段,主动把常用字符渲染一遍。比如菜单上的“设置”、“返回”、“确认”,提前塞进缓存。启动时慢一点,但后面就快了。
- 控制字符集范围:不要加载整个字体文件。只加载你真正用到的字符。中文字体动辄几千个字符,但一个界面可能只用几十个。用
UnicodeList精确指定。 - 统一字体大小:同一个界面里,尽量用相同字号。不同字号在缓存里是不同条目,会稀释命中率。
- 避免频繁切换字体:字体切换会清空缓存。我见过一个项目,每个页面都换字体,结果缓存形同虚设。
| 优化手段 | 效果 | 代价 |
|---|---|---|
| 预热缓存 | 启动后首次显示流畅 | 增加启动时间 |
| 控制字符集 | 减少缓存污染 | 需要提前规划字符 |
| 统一字号 | 提高复用率 | 设计上可能受限 |
| 减少字体切换 | 避免缓存清空 | 需要全局规划 |
我曾经踩过的坑:有一个项目,字体缓存设了64KB,但命中率只有30%。查了半天,发现是每次刷新界面时,都会重新创建字体对象,导致缓存被重置。解决方案是把字体对象声明为全局单例,缓存就稳定了。
3.3 动态字体加载与卸载策略
动态加载,说白了就是“用的时候再加载,不用就卸掉”。这在内存受限的嵌入式设备上特别重要。你想想看,一个完整的宋体字库文件可能好几MB,但当前界面只需要显示数字和英文字母,那干嘛要把整个字库都塞进内存?
TouchGFX支持两种动态加载方式:
- 按需加载:每次遇到新字符,才从Flash或文件系统里读取对应的字形数据。适合字符集大但实际使用稀疏的场景。
- 批量预加载:在进入某个页面时,一次性把该页面用到的所有字符加载到缓存。适合页面内字符固定的场景。
我个人习惯的做法是:
- 先分析每个页面的字符使用情况,生成一个“字符使用清单”。
- 页面切换时,卸载上一个页面的字符缓存,加载当前页面的字符。
- 如果两个页面有重叠字符(比如都用了“确认”按钮),保留这些公共字符,只卸载独有部分。
卸载策略的关键:不要全部清空。保留高频公共字符,能显著减少页面切换时的重新加载开销。
下面这张图展示了动态加载与卸载的核心流程:
我的习惯:在项目初期就定义好一个FontCacheManager类,统一管理所有字体的加载和卸载。这样后期优化时,只需要改这一个类,不用到处翻代码。
3.4 缓存大小与性能的平衡
缓存不是越大越好。太大了浪费RAM,太小了命中率低。怎么找到那个平衡点?我一般这样估算:
- 统计界面上同时显示的最大字符数。比如一个列表页,最多显示20个汉字,每个汉字16x16像素,那就是20×256=5KB。
- 加上一些余量,比如再预留50%给滚动时的新字符。这样大概8KB就够了。
- 如果界面有动画,字符变化频繁,那就再翻一倍。
| 界面类型 | 建议缓存大小 | 说明 |
|---|---|---|
| 静态菜单 | 4KB - 8KB | 字符固定,预热后命中率高 |
| 列表滚动 | 8KB - 16KB | 需要覆盖滚动时的新字符 |
| 文本输入 | 16KB - 32KB | 用户输入不可预测,需要大缓存 |
| 多语言切换 | 32KB - 64KB | 不同语言字符集差异大 |
注意:缓存大小设置后,一定要在实际硬件上跑压力测试。模拟器上跑得飞起,真机上可能因为内存碎片导致缓存分配失败。我曾经就因为这个,在真机上看到字符突然变成方块,排查了两天才找到原因。