4. 页面生命周期
在 TouchGFX 的多页面架构中,Screen 并不是一个“静态画面”,而是一个具有明确生命周期的对象。每当用户从首页跳转到设置页、从设置页返回首页,或者弹出模态对话框时,TouchGFX 都会按照既定顺序调用一系列回调函数。理解这些回调的触发时机,是避免内存泄漏、界面闪烁、数据错乱和触摸事件失效的关键。
TouchGFX 的页面生命周期可以概括为六个阶段:创建(Create)、初始化(Initialize)、激活(Activate)、暂停(Pause)、恢复(Resume)、销毁(Destroy)。其中“创建”和“初始化”通常只发生一次,而“激活—暂停—恢复”会在页面反复切换时多次出现。下面这张图展示了页面从被请求显示到最终释放的完整信号流与回调顺序。
一、核心原理:为什么需要生命周期回调
TouchGFX 采用“单帧渲染 + 事件驱动”的模型。屏幕上同一时刻通常只有一个前台 Screen 在接收触摸事件和刷新。当发生页面切换时,框架不会立即销毁旧页面,而是先将其置为暂停状态,再激活新页面。这样做的好处是:返回时可以快速恢复,避免重复创建控件带来的内存碎片和视觉闪烁。
因此,生命周期回调的本质是让开发者在正确的时机做正确的事:在创建时分配资源,在激活时刷新数据,在暂停时停止定时器,在恢复时重新同步状态,在销毁时释放内存。如果把这些操作放错位置,就会出现“返回后数据不更新”“定时器仍在后台跑”“控件重复创建”等典型问题。
二、方法流程:六个阶段的回调顺序
下面以“首页 → 设置页 → 返回首页”为例,说明各回调的触发顺序。假设首页为 HomeView,设置页为 SettingsView。
// 首次进入 HomeView
HomeView::setupScreen() // 绑定控件、设置初始值
HomeView::onCreate() // 动态创建对象(可选)
HomeView::handleTickEvent() // 每帧调用
// 从 HomeView 跳转到 SettingsView
HomeView::onPause() // 停止动画、保存临时状态
SettingsView::setupScreen() // 首次进入时调用
SettingsView::onCreate()
SettingsView::handleTickEvent()
// 从 SettingsView 返回 HomeView
SettingsView::onPause()
HomeView::onResume() // 恢复数据、重启定时器
HomeView::handleTickEvent()
// 页面被彻底移除时
HomeView::onDestroy() // 释放动态内存
需要特别注意的是:setupScreen() 和 onCreate() 只在页面第一次显示时调用一次;而 onResume() 和 onPause() 会在每次切换时反复调用。很多初学者把“刷新数据”写在 setupScreen() 里,结果返回首页时数据没有更新,就是因为 setupScreen() 不会再执行。
三、回调函数职责对照表
| 回调函数 | 触发时机 | 典型职责 | 常见误区 |
|---|---|---|---|
setupScreen() |
页面首次显示前 | 绑定控件、设置初始文本与颜色 | 放入需要每次刷新的数据 |
onCreate() |
页面创建后 | 动态 new 控件、注册回调 | 忘记在 onDestroy 中 delete |
onActivate() |
页面成为前台 | 启动动画、请求焦点 | 与 onResume 职责混淆 |
onPause() |
页面失去前台 | 停止定时器、保存临时状态 | 未停止后台任务导致 CPU 占用 |
onResume() |
页面重新回到前台 | 刷新数据、重启定时器 | 重复创建已存在的对象 |
onDestroy() |
页面被移除 | 释放动态内存、注销监听 | 遗漏导致内存泄漏 |
四、案例场景:设置页返回后数据不更新
假设首页有一个温度显示标签,设置页可以修改温度单位。用户从首页进入设置页,把单位从摄氏度改为华氏度,然后返回首页。如果首页的刷新逻辑写在 setupScreen() 中,返回时温度显示仍然是摄氏度,因为 setupScreen() 不会再次执行。
正确的做法是在 onResume() 中读取最新的单位配置并刷新标签:
void HomeView::onResume()
{
// 每次回到首页都重新读取配置
bool useFahrenheit = Settings::getInstance().isFahrenheit();
float temp = Sensor::getInstance().getTemperature();
if (useFahrenheit) {
temp = temp * 9.0f / 5.0f + 32.0f;
Unicode::snprintf(tempBuffer, TEMP_SIZE, "%.1f F", temp);
} else {
Unicode::snprintf(tempBuffer, TEMP_SIZE, "%.1f C", temp);
}
tempLabel.invalidate();
Screen::onResume();
}
onPause() 中停止,在 onResume() 中重新启动。否则页面虽然不可见,定时器仍在后台运行,既浪费 CPU,也可能在页面销毁后访问已释放的对象。
五、误区复盘与最佳实践
- 误区一:把 onResume 当成 onCreate 用。
onResume()可能被多次调用,不能在这里重复创建控件或重复注册回调,否则会导致控件叠加或回调多次触发。 - 误区二:在 onPause 中销毁对象。 暂停只是暂时离开前台,页面对象仍然存在。如果在
onPause()中 delete 控件,恢复时就会访问空指针。 - 误区三:忽略 onDestroy。 对于动态创建的控件、定时器、监听器,必须在
onDestroy()中释放,否则每次创建新页面都会泄漏内存。 - 最佳实践: 把“一次性初始化”放在
setupScreen(),把“每次显示都要刷新”放在onResume(),把“离开时暂停”放在onPause(),把“释放资源”放在onDestroy()。
掌握页面生命周期后,下一步就可以在页面之间安全地传递参数、共享数据模型,并设计出既省内存又响应迅速的多页面应用。