当前位置: 首页 > news >正文

如何在LVGL之外的线程更新UI内容

前言

  作为一个刚开始学习LVGL和嵌入式开发的新手,学会绘制一个界面之后,遇到了一个问题:在LVGL线程之外的线程,更新UI内容时,会导致程序崩溃。

1、问题分析

首先,需要了解LVGL的基本工作原理。LVGL(Light and Versatile Graphics Library)是一个用于嵌入式系统的开源图形库,主要用于创建用户界面。它通常运行在单线程环境中,所有的UI操作(如创建控件、更新显示等)都在主线程中完成。这是因为LVGL本身并不是线程安全的,直接在其他线程中操作UI可能会导致竞态条件或数据不一致的问题。

接下来,需要理解为什么要在其他线程更新UI。在实际应用中,可能会有一些耗时操作,比如网络请求、数据处理或硬件交互,这些操作如果放在主线程中执行,会导致界面卡顿,影响用户体验。因此,将这些操作放在后台线程中执行,完成后再更新UI,是一种常见的做法。

现在的问题是,如何在非LVGL线程(即后台线程)中安全地更新UI。我的初步思路是,需要某种机制将后台线程中的数据或状态传递到主线程,由主线程负责实际的UI更新。这通常涉及到线程间的通信或同步机制,比如消息队列、信号量、互斥锁等。

需要了解LVGL的主循环机制。LVGL通常在一个主循环中处理任务,例如通过调用`lv_timer_handler()`来执行定时器任务和UI刷新。这个主循环通常运行在主线程中。因此,任何对UI的修改都需要在这个主线程中进行,以确保线程安全。

2、解决思路

那么,何将后台线程的更新请求传递到主线程呢?可能的解决方案包括:

2.1.使用消息队列

后台线程将更新UI的请求放入一个消息队列中,主线程在每次循环中检查队列并处理这些请求。

2.2 使用回调函数

在后台线程中完成处理后,通过某种方式触发主线程中的回调函数来更新UI。

2.3 使用互斥锁(Mutex)

在访问共享的UI资源时,使用互斥锁来确保同一时间只有一个线程操作UI。

3、步骤分析

接下来,需要考虑这些方法的可行性和具体实现细节。

3.1 使用消息队列的步骤

1. 创建一个线程安全的消息队列,用于存储UI更新请求。

2. 后台线程在需要更新UI时,将请求(例如,更新某个标签的文本)放入队列。

3. 主线程在每次执行`lv_timer_handler()`之前或之后,从队列中取出请求并执行相应的UI操作。

**注意事项**:

- 需要确保消息队列的线程安全性,避免数据竞争。

- 消息的结构需要包含足够的信息来描述如何更新UI(例如,控件指针、新值等)。

- 需要考虑内存管理,避免在传递指针时出现悬垂指针或内存泄漏。

3.2 使用互斥锁的步骤

1. 在访问LVGL的API时,使用互斥锁来保护关键区域。

2. 后台线程在更新UI前获取锁,执行操作后释放锁。

3. 主线程同样在操作UI时获取锁,确保互斥访问

**注意事项**:

- LVGL本身可能并不是线程安全的,即使使用互斥锁,某些内部状态可能仍然存在竞态条件。

- 频繁的锁操作可能会影响性能,尤其是在高频率更新时。

- 需要仔细设计锁的范围,避免死锁。

3.3 使用回调函数的步骤

1. 主线程注册一个回调函数,用于处理特定的UI更新。

2. 后台线程在需要更新UI时,触发这个回调函数。

3. 回调函数在主线程的上下文中执行,确保安全地操作UI。

**注意事项**:

- 需要确保回调函数在主线程中被正确调用,可能需要借助操作系统的事件或消息机制。

- 需要处理回调函数的参数传递和生命周期管理。

4、可行性分析

现在,我需要进一步验证这些方法的可行性,并考虑实际实现中的细节问题。

例如,使用消息队列的方法,在FreeRTOS中,可以使用`xQueueSend()`和`xQueueReceive()`函数来实现线程间的消息传递。消息队列中的每个消息可以包含控件指针、操作类型(如设置文本、改变颜色等)以及相应的参数。主线程在每次循环中处理队列中的所有消息,执行对应的UI操作。

另一个例子,使用互斥锁的方法,在FreeRTOS中,可以使用`xSemaphoreCreateMutex()`创建一个互斥锁。在后台线程中,操作UI前先获取锁,操作完成后释放锁。主线程在调用LVGL的API时也需要获取相同的锁。但需要注意,如果LVGL的主循环本身涉及到多个步骤,可能需要在整个处理过程中保持锁,这可能影响其他线程的响应性。

此外,我还需要考虑LVGL的官方建议最佳实践。根据LVGL的文档,官方推荐在主线程中处理所有的UI操作,避免在多线程中直接调用LVGL的API。因此,使用消息队列将UI更新请求传递到主线程,可能是更符合LVGL设计理念的做法

5、解决方案

5.1 创建线程安全的消息队列

  • 使用RTOS提供的消息队列(如FreeRTOS的QueueHandle_t

  • 定义消息结构体,包含UI更新所需的信息

  • typedef struct {lv_obj_t* target;    // 目标控件void* data;          // 数据指针void (*update_func)(lv_obj_t*, void*); // 更新函数
    } UI_Update_Message;QueueHandle_t ui_update_queue = xQueueCreate(10, sizeof(UI_Update_Message));

    5.2 实现消息处理函数

  • 在主线程的LVGL循环中处理消息
  • void process_ui_messages(void) {UI_Update_Message msg;while(xQueueReceive(ui_update_queue, &msg, 0) == pdTRUE) {if(msg.target && msg.update_func) {msg.update_func(msg.target, msg.data);}// 释放动态分配的数据(如果需要则不释放)if(msg.data) {free(msg.data);}}
    }// 在LVGL主循环中调用
    while(1) {lv_timer_handler();process_ui_messages();vTaskDelay(pdMS_TO_TICKS(5));
    }

    5.3 后台线程发送更新请求

  • // 通用更新函数模板
    void safe_ui_update(lv_obj_t* target, void (*func)(lv_obj_t*, void*),void* data, size_t data_size) {UI_Update_Message msg = {.target = target,.update_func = func,.data = NULL};if(data && data_size > 0) {msg.data = pvPortMalloc(data_size);if(msg.data) memcpy(msg.data, data, data_size);}xQueueSend(ui_update_queue, &msg, portMAX_DELAY);
    }// 具体实现示例:更新标签文本
    void update_label_text(lv_obj_t* label, const char* text) {safe_ui_update(label, (void (*)(lv_obj_t*, void*))lv_label_set_text, (void*)text, strlen(text)+1);
    }// 后台线程中调用
    void sensor_thread(void* arg) {while(1) {float temp = read_temperature();char buffer[20];snprintf(buffer, sizeof(buffer), "Temp: %.1f℃", temp);update_label_text(ui.temp_label, buffer);vTaskDelay(pdMS_TO_TICKS(1000));}
    }

    5.4 高级功能扩展 (待开发)

    • 优先级消息处理

    • 批量更新优化

    • 异步回调支持

    • 内存管理

    • 性能优化

      • 批量处理UI更新请求

      • 使用双缓冲技术

      • 限制更新频率(如最大60FPS)

    • 错误处理

      • 添加队列满时的处理策略

      • 实现超时机制

      • 添加内存分配失败的回退方案

        

6、总结

这种设计模式的优势:

  1. 完全解耦业务逻辑和UI渲染

  2. 确保LVGL在单线程环境下运行

实际项目中需要根据具体需求调整:

  • 对于高实时性系统:缩短队列处理周期

  • 对于内存受限系统:使用静态内存分配

  • 对于复杂UI:添加批量更新优化

  • 对于安全关键系统:添加校验和验证机制

总的来说,能够导致LVGL卡死现象,主要原因就是在LVGL的线程外更新了LVGL对象导致的,内容如有不对之处,欢迎各位指点修改!

ps:参考并复制粘贴了DEEPSEEK的回答。

相关文章:

  • 从纸质契约到智能契约:AI如何改写信任规则与商业效率?​——从智能合约到监管科技,一场颠覆传统商业逻辑的技术革命
  • Unreal 从入门到精通之SceneCaptureComponent2D实现UI层3D物体360°预览
  • 学习VS2022离线安装包的下载方法
  • STC-ISP烧录过程中一直显示“正在检测单片机”的解决办法
  • WebSphere Application Server(WAS)8.5.5教程第五讲
  • 解释加密中的加盐操作
  • 理解PostgreSQL查询执行计划(三)--复杂操作篇
  • C++17之std::launder函数
  • 【回溯法】0-1背包问题 C/C++(附代码)
  • nmcli connection reload
  • React集成百度【JSAPI Three】教程(002):设置不同的环境效果
  • OpenTelemetry 从入门到精通
  • 【MySQL】基础操作
  • 【Linux】进程控制(进程创建、进程终止、进程等待、进程替换)
  • Vue.js---立即执行的watch与回调执行时机
  • 扫描项目依赖漏洞
  • 网络学习-epoll(四)
  • 入职软件开发与实施工程师了后........
  • Ktransformers0.3框架的api访问接口程序
  • vue中excel文件 打包后不展示问题
  • 购房成本再降低!今年首次降息落地,30年期百万房贷月供将减少54元
  • “宝马女司机驾车拖行虐猫”引关注,海口警方介入调查
  • 上海合作组织减贫和可持续发展论坛开幕,沈跃跃宣读习近平主席贺信
  • 新疆巴音郭楞州和硕县发生4.6级地震,震源深度10千米
  • “打铁”热邂逅江南水乡,长三角首个国际级铁三赛事有何不同
  • 石家庄桥西区通报“中药液”添加安眠药问题:对医院立案调查