LVGL开启页面切换同时清理内存引发的内存安全问题

一、前言

内存紧张情况下需要高度复用内存,页面切换时需要删除页面并释放内存,腾出空间给另一页面使用。

我为每个页面都设计了任务栏,有时间、SD卡、电量状态,这些状态需要周期刷新。结合我复用内存的操作会存在踩内存的可能。

另外你需要了解LVGL 线程不安全 。参考 函数可重入性(Reentrancy)概念详解,几乎所有涉及到LVGL的API使用前必须加锁,包括lv_task_handler(),但是lv_scr_act() 、lv_async_call 是不需要加锁的。

但加锁这个解决方案并不优雅,当UI设计比较复杂时加锁,任务时序会非常复杂难以调试,同时会导致锁竞争,即多个线程都需要使用LVGL来更新画面数据,造成非常大的性能开销。

此外我使用segger systemview分析操作系统任务时序,加锁后事件会非常多,造成事件Over flow。

二、两种方案

这两种方案不使用锁,同时也不使用事件通知等功能,我使用全局变量在线程与LVGL间通讯,即线程向全局变量写入数据,LVGL轮询线程读取全局变量统一更新界面。

1. 不使用锁,页面切换时不删除页面和回收内存

这种方案简单,易于实现,但不能删除页面和回收内存,因此对系统内存提出了一定要求;因为不删除页面回收内存,任何时候lvgl线程都可以访问修改任意页面的数据,这也不存在踩内存问题。

其他线程不调用LVGL API,把需要周期刷新时间、SD卡状态、电量的API直接扔到lv_task_handler();所在的线程内,这样lv_task_handler()函数退出后可以安全执行其他lvgl API。

实测效果非常稳定,没有出现系统异常问题

2. 不使用锁,页面切换时删除页面同时回收内存

在这种情况下使用第一种方法出现了踩内存问题 内存泄漏 & 内存溢出 & 踩内存 & malloc底层实现原理_踩内存和内存泄漏-CSDN博客

当页面切换时存在 准备切换→删除页面回收内存→完成切换 的过程,问题就出在中间删除页面回收内存的阶段,由于由于我使用的轮询方法会周期访问页面控件,当进入删除页面回收内存的阶段后LVGL轮询线程并不知道那些页面&控件被删除了,此时再尝试更新控件内容就会出现问题。

当然我也尝试了解决方案,在LVGL轮询线程更新控件之前检查LVGL当前页,只操作当前页内的控件,结果仍然会出现内存问题


解决方案

使用LVGL 内部 Timer 来替代LVGL轮询线程,将刷新任务放到Timer回调函数中执行,我的程序中创建了两个页面,这里直接创建两个周期Timer,一个Timer用来刷新第一页的控件,另一个Timer用来刷新第二页的控件。

当进入首页时先启动第一个Timer,停止第二个Timer

// Lvgl Handler
void lvglTaskEntry(void *argument)
{
	global_param.lvgl_timer_refresh[0] = lv_timer_create(lvgl_refresh_main, 500,  lv_scr_act());
	lv_timer_set_repeat_count(global_param.lvgl_timer_refresh[0],-1);
	lv_timer_ready(global_param.lvgl_timer_refresh[0]);
	
	global_param.lvgl_timer_refresh[1] = lv_timer_create(lvgl_refresh_setting, 500,  lv_scr_act());
	lv_timer_set_repeat_count(global_param.lvgl_timer_refresh[1],-1);
	lv_timer_ready(global_param.lvgl_timer_refresh[1]);
	lv_timer_pause(global_param.lvgl_timer_refresh[1]);
	
	for(;;)
	{
		/*------------------------------1.LVGL-Handle----------------------------------------*/
		osDelayUntil(5);
		#ifndef LVGL_LOCK
		// lvgl 线程不安全 所有lv开头函数必须加锁
		if(osMutexAcquire(global_param.os_lock_lvgl,100)==osOK)
		{
			lv_task_handler();
			osMutexRelease(global_param.os_lock_lvgl);
		}
		#else
		lv_task_handler();
		#endif
	}
}

当切换页面进入 准备切换阶段 时停止第一个Timer

/// @brief	主界面 设置按钮 弹起
///			初始化前期回调
void btn_main_set_clicked_pre_cb(void)
{
    if(global_param.init_cam)
    {
        HAL_DCMI_Stop(&hdcmi);	// 停止摄像头数据传输
        OV5640_Power(3);		// 摄像头省电模式
    }
	
	// 截图提示窗口提示隐藏
	lv_obj_add_flag(guider_ui.Main_label_snapshot_msg, LV_OBJ_FLAG_HIDDEN);
	
	// 暂停主界面数据刷新定时器
	lv_timer_pause(global_param.lvgl_timer_refresh[0]);
}

当切换页面进入 完成切换阶段 时开启第二个Timer

/// @brief	主界面 设置按钮 弹起
///			初始化后期回调
void btn_main_set_clicked_post_cb(void)
{
	// 开启设置界面数据刷新定时器
	lv_timer_resume(global_param.lvgl_timer_refresh[1]);
}

此时完成界面切换,同时也可以正常刷新第二页的数据。

文章原始发布于https://blog.awolon.fun/archives/lvgl-page-switch-clean-memory-cause-memery-problem.html

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lvgl实现界面切换的原理是通过创建多个页面(或者说lvgl里的screen)并实现它们之间的互相切换。这样可以将不同的功能和接口分散到不同的页面上,使界面更加优雅,并且适应嵌入式硬件的小屏幕。\[1\]\[2\] 为了方便加载界面内的各个控件,可以将需要与输入设备绑定的控件放到一个结构体中,以便于调用。这个结构体可以参考lvgl的官方示例代码,如lv_demo_keypad_encoder中的结构体定义。\[3\] 在实现页面切换时,可以使用事件处理函数(events_handler)来监听用户的输入或其他触发条件。根据不同的事件,可以通过lvgl提供的API来切换页面,例如使用lv_scr_load函数加载新的页面,或者使用lv_scr_act函数设置当前活动的页面。通过这些API的调用,可以实现页面之间的切换效果。 #### 引用[.reference_title] - *1* *3* [【esp32&lvgl】-2.6 #lvgl-多页面(screen)设定/切换](https://blog.csdn.net/weixin_43326110/article/details/126642410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【LVGL】学习笔记--(3)界面切换以及显示优化](https://blog.csdn.net/sinat_33408502/article/details/129242824)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值