4. 开发环境与工具链

内存优化做到一定程度,你会发现一个绕不开的问题:图片到底该不该缓存?

我刚开始做 TouchGFX 项目时,习惯把所有图片都塞进内存。反正 Flash 够大,RAM 也够用,何必折腾?结果产品迭代到第二版,需求加了 3 套皮肤、5 种语言图标,内存直接爆了。嗯,从那以后我才认真研究缓存策略。

说白了,缓存就是用 RAM 换性能。但嵌入式系统的 RAM 就那么点,你不可能全都要。所以关键问题是:哪些资源值得缓存?哪些应该随用随加载?

4.1 局部缓存:把热点数据留在内存

局部缓存,就是把频繁访问的资源常驻内存。TouchGFX 的 BitmapCache 就是干这个的。

我个人习惯把以下三类资源设为局部缓存:

  • 背景图:每个页面几乎都会用到,反复加载太亏
  • 常驻图标:比如返回按钮、菜单图标,切换页面时不会释放
  • 字体缓存:文字渲染的中间结果,缓存后能省大量 CPU 时间

配置方式很简单,在 TouchGFXConfiguration.cpp 里设置:

// 设置局部缓存大小(单位:字节)
BitmapCache::setCacheSize(200 * 1024);  // 200KB

// 手动将某张图片加入缓存
BitmapId bgId = Bitmap(BITMAP_MAIN_BG_ID);
BitmapCache::cacheBitmap(bgId);

你想想看,如果背景图是 800x480 的 16 位色图,一张就吃掉 768KB。要是每个页面都缓存一张,内存很快就见底了。所以局部缓存不是越多越好,而是越精越好。

核心原则:局部缓存只放「高频访问 + 体积可控」的资源。低频大图千万别往里塞。

4.2 动态加载:用时再读,用完就扔

动态加载正好相反——资源存在 Flash 里,用到时才读到 RAM,用完立即释放。

TouchGFX 的 DynamicBitmap 就是做这个的。我一般在以下场景用它:

  • 引导页图片:用户只看一次,没必要缓存
  • 弹窗背景:偶尔出现,加载一下不费事
  • 大尺寸插图:比如产品展示图,一张可能 1MB 以上

动态加载的代码也很直接:

// 从 Flash 动态加载图片
DynamicBitmap db;
db = Bitmap(DynamicBitmap::loadFromFlash(
    FLASH_SECTOR_ADDR, 
    BITMAP_PRODUCT_IMG_ID
));

// 使用完后释放
db = Bitmap();  // 析构时自动释放内存

小技巧:动态加载配合 DMA2D 使用,加载速度会快很多。我有个项目把加载时间从 120ms 降到了 35ms。

4.3 取舍决策:一张表说清楚

到底用局部缓存还是动态加载?我整理了一张决策表,你直接套用就行:

判断维度 局部缓存 动态加载
访问频率 高(每次页面切换都用到) 低(偶尔出现)
资源体积 小(单张 < 100KB) 大(单张 > 200KB)
加载延迟容忍 低(必须秒开) 高(可以接受短暂等待)
RAM 余量 充足(剩余 > 30%) 紧张(剩余 < 15%)
典型场景 主界面背景、导航栏图标 设置页插图、帮助页截图

举个例子。我之前做一款智能家居面板,主界面背景是常驻的,必须缓存。但设备详情页的配图有 20 多张,每张 300KB 左右。如果全缓存,RAM 直接爆掉。所以我只缓存了当前页面的配图,切换时动态加载新的、释放旧的。效果很好,RAM 占用从 1.2MB 降到了 480KB。

4.4 混合策略:缓存池 + 动态替换

实际项目中,纯局部缓存或纯动态加载都太极端。我推荐用缓存池的方式:

  1. 划出一块固定大小的缓存区(比如 512KB)
  2. 把最常用的 3-5 张图固定缓存
  3. 剩下的空间作为「热替换区」,按 LRU(最近最少使用)算法动态替换

TouchGFX 其实内置了 LRU 缓存机制,你只需要设置缓存大小:

// 启用 LRU 缓存,大小 512KB
BitmapCache::setCacheSize(512 * 1024);
BitmapCache::enableLRU(true);

这样,系统会自动管理哪些图片留在缓存里。你想想看,用户在主界面和设置页之间来回切换,主界面背景和设置页图标就会自动成为缓存热点。而用户很少点开的「关于」页面图片,就会被自动踢出缓存。

注意:LRU 缓存不是万能的。如果用户频繁切换大量不同页面,缓存命中率会下降,反而增加加载开销。我建议缓存池大小不要超过总 RAM 的 20%。

4.5 实战:一个完整的缓存配置示例

下面是我在一个量产项目中用过的配置,你可以参考:

// 内存布局
// RAM: 1MB 总可用
// 缓存池: 200KB(20%)
// 动态加载区: 100KB(10%)
// 其余留给系统和其他组件

void initCacheStrategy() {
    // 1. 设置缓存池
    BitmapCache::setCacheSize(200 * 1024);
    BitmapCache::enableLRU(true);
    
    // 2. 固定缓存核心资源
    BitmapCache::cacheBitmap(BITMAP_BG_MAIN_ID);     // 主背景 80KB
    BitmapCache::cacheBitmap(BITMAP_NAV_BAR_ID);     // 导航栏 30KB
    BitmapCache::cacheBitmap(BITMAP_LOGO_ID);        // Logo 20KB
    
    // 3. 预留动态加载区
    // 动态加载的图片不要超过 100KB
    // 超过的话需要分块加载
}

这个配置跑下来,主界面切换延迟 < 10ms,设置页图片加载延迟约 50ms,用户完全感觉不到。RAM 占用稳定在 650KB 左右,还有 350KB 余量给其他功能。

我的经验:配置完成后,一定要用 TouchGFX 的 MemoryMonitor 工具跑一遍全流程。我曾经有个 bug,缓存池设置成 200KB 但实际只用了 120KB,剩下的 80KB 被浪费了。用工具一看才发现,原来是某张图片的缓存标记没生效。

4.6 缓存策略决策流程图

下面这张图总结了整个决策过程,你可以贴在工位上:

缓存策略决策流程图 缓存策略决策流程图 开始:评估资源 访问频率高?(每次切换都用) 是 否 体积 < 100KB? 体积 > 200KB? 局部缓存 动态加载 混合策略:缓存池 实施并监控 提示:如果 RAM 余量 < 15%,优先考虑动态加载;如果 > 30%,可以多用局部缓存

这张图的核心逻辑很简单:高频小图用缓存,低频大图动态加载,中间地带用缓存池。照着这个思路走,基本不会出大问题。

好了,缓存策略就聊到这儿。记住一句话:没有最好的策略,只有最适合你项目的策略。动手试试,用数据说话。