内存优化基础:RAM与Flash的分配策略、减少动态内存分配、使用静态缓冲池
做TouchGFX开发,说白了就是跟内存打交道。我刚开始接手一个复杂UI项目时,总觉得内存不够用,动不动就崩。后来才明白,不是内存真的少,而是我没用好。这一章,咱们就聊聊内存分配的那些事儿。
RAM与Flash:谁该放什么?
先搞清楚两个概念。Flash是只读的,存代码、字体、图片这些不变的东西。RAM是读写的,存变量、缓存、动态数据。TouchGFX的UI框架本身会占用一部分RAM,你的业务逻辑、动画状态、屏幕缓存,全挤在RAM里。
我见过不少新手,把大图片直接当变量声明,结果RAM爆了。其实图片应该放Flash,运行时只加载到RAM里显示。你想想看,一张800x480的24位图,差不多1.1MB,RAM才多大?
| 存储类型 | 典型容量 | 适合存放的内容 | 不适合的内容 |
|---|---|---|---|
| Flash | 512KB ~ 16MB | 代码、字体、图片、字符串常量 | 运行时变量、动态缓存 |
| RAM | 64KB ~ 2MB | 栈、堆、屏幕缓存、对象实例 | 大块静态资源(图片、字体) |
核心原则:能放Flash的,绝不占RAM。TouchGFX的BitmapCache默认从Flash加载,别自己再copy一份到RAM里。
减少动态内存分配
动态分配,就是运行时用malloc、new去申请内存。TouchGFX的UI框架本身很少用动态分配,但你的业务代码里可能到处都是。为什么说它不好?
- 碎片化:频繁申请释放,内存越切越碎,最后大块申请失败。
- 不可预测:你不知道哪次分配会失败,UI可能突然卡死。
- 性能开销:每次分配都要走堆管理算法,费时。
我有个项目,客户反映UI偶尔闪退。查了两天,发现是一个弹窗里用了std::vector动态扩容,堆碎片导致分配失败。改成静态数组后,再没出过问题。
避坑指南:我曾经在TouchGFX的Screen构造函数里调用了new,结果切换屏幕时内存泄漏。后来全部改用预分配对象池,稳如老狗。
使用静态缓冲池
静态缓冲池,说白了就是提前在RAM里划一块固定区域,专门用来分配固定大小的对象。不用malloc,不用new,全在编译期定好。
TouchGFX里最典型的应用就是BitmapCache。它内部维护了一个静态缓冲池,用来缓存解码后的图片。你只需要在TouchGFXConfiguration.cpp里配置大小:
// 在 TouchGFXConfiguration.cpp 中
static uint8_t bitmapCacheBuffer[BITMAP_CACHE_SIZE];
BitmapCache::setCacheBuffer(bitmapCacheBuffer, BITMAP_CACHE_SIZE);
这样,所有图片解码后的数据都从这个池子里取,不会碎片化,也不会泄漏。
我自己写业务代码时,也喜欢用静态池。比如一个列表控件,最多显示20项,我就声明一个20个元素的数组,循环使用:
#define MAX_LIST_ITEMS 20
static ListItem listItems[MAX_LIST_ITEMS];
static uint8_t itemIndex = 0;
ListItem* getNextListItem() {
ListItem* item = &listItems[itemIndex];
itemIndex = (itemIndex + 1) % MAX_LIST_ITEMS;
return item;
}
这样,没有动态分配,没有内存泄漏,性能还高。你想想看,是不是比每次new一个对象靠谱多了?
小技巧:如果你的UI有多个屏幕,每个屏幕的控件数量是固定的,可以在Screen类里声明静态成员变量。这样所有屏幕实例共享同一块内存,省空间。
内存分配策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 动态分配 | 灵活,按需分配 | 碎片化、不可预测、性能差 | 极少使用,仅用于启动时一次性分配 |
| 静态全局数组 | 简单、无碎片、性能高 | 大小固定,浪费空间 | 控件池、缓存、固定大小的数据结构 |
| 静态缓冲池 | 可复用、无碎片、可控 | 实现稍复杂 | 图片缓存、消息队列、对象池 |
一张图看懂内存分配策略
下面这张图,是我自己总结的内存分配决策流程。每次写新功能前,我都会过一遍:
嗯,这张图我用了好几年。每次新功能来了,先问自己:大小固定吗?频繁分配吗?答案走下来,基本就知道该用哪种策略了。
实战:配置TouchGFX的静态缓冲池
最后,给一个完整的配置示例。假设你的MCU有512KB RAM,你打算给图片缓存留128KB:
// 在 TouchGFXConfiguration.cpp 中
#define BITMAP_CACHE_SIZE (128 * 1024) // 128KB
static uint8_t bitmapCacheBuffer[BITMAP_CACHE_SIZE];
void touchgfx_init() {
// 设置缓存池
BitmapCache::setCacheBuffer(bitmapCacheBuffer, BITMAP_CACHE_SIZE);
// 设置帧缓冲(双缓冲)
static uint16_t frameBuffer0[480 * 272]; // 假设分辨率480x272
static uint16_t frameBuffer1[480 * 272];
HAL::getInstance()->setFrameBufferStartAddresses(
(uint16_t*)frameBuffer0,
(uint16_t*)frameBuffer1
);
// 其余初始化...
}
这样配置后,所有图片解码都走静态池,不会产生堆碎片。帧缓冲也是静态的,不会动态申请。整个UI的内存行为变得可预测、可控。
个人习惯:我一般把缓存池大小设为总RAM的25%~30%。剩下的留给栈、全局变量和TouchGFX框架本身。如果图片多,可以适当调大,但别超过50%,否则其他模块可能不够用。
内存优化,说白了就是「预则立,不预则废」。提前规划好分配策略,比出了问题再调优要省心得多。这一章的内容,够你在实际项目中少踩不少坑了。