编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

    • README
    • QML基础入门

    • QT核心库实践

    • Linux实践开发

      • README
      • Linux开发指引
      • 崩溃监听实践
      • Linux应用指令
      • 应用重启策略
      • 守护进程保活
      • 脚本进程保活
      • 应用启动的原理
      • LVGL设计原理
      • Qt应用保活方案设计
        • 01.应用调研背景
          • 1.1 遇到问题和场景
          • 1.2 需要解决的问题
          • 1.3 主要技术卡点
        • 02.方案探索和思考
          • 2.1 如何监听崩溃
          • 2.2 如何重启应用
          • 2.3 崩溃重启方案
          • 2.4 监控UI卡顿
          • 2.5 M3盛思达方案
          • 2.6 方案讨论结论
        • 03.保活监听重启方案
          • 3.1 整体架构设计
          • 3.2 守护进程设计
          • 3.3 监控机制设计
          • 3.4 通信机制设计
          • 3.5 守护进程的保活
          • 3.6 重启的策略
          • 3.7 注意事项说明
        • 04.UI卡顿重启方案
          • 4.1 背景和思考
          • 4.2 整体方案思路
          • 4.3 先要帧率监控
          • 4.4 事件循环分析
          • 4.5 渲染性能分析
          • 4.6 卡顿诊断判断
          • 4.7 可视化界面
          • 4.8 设计卡顿阀值重启
        • 05.自测模拟操作
          • 5.1 如何模拟崩溃
          • 5.2 如何模拟卡顿
  • Apps
  • Linux应用开发
  • Linux实践开发
杨充
2025-09-10
目录

Qt应用保活方案设计

# Qt应用保活方案设计

# 目录介绍

  • 01.应用调研背景
    • 1.1 遇到问题和场景
    • 1.2 需要解决的问题
    • 1.3 主要技术卡点
  • 02.方案探索和思考
    • 2.1 如何监听崩溃
    • 2.2 如何重启应用
    • 2.3 崩溃重启方案
    • 2.4 监控UI卡顿
    • 2.5 M3盛思达方案
    • 2.6 方案讨论结论
  • 03.保活监听重启方案
    • 3.1 整体架构设计
    • 3.2 守护进程设计
    • 3.3 监控机制设计
    • 3.4 通信机制设计
    • 3.5 守护进程的保活
    • 3.6 重启的策略
    • 3.7 注意事项说明
  • 04.UI卡顿重启方案
    • 4.1 背景和思考
    • 4.2 整体方案思路
    • 4.3 先要帧率监控
    • 4.4 事件循环分析
    • 4.5 渲染性能分析
    • 4.6 卡顿诊断判断
    • 4.7 可视化界面
    • 4.8 设计卡顿阀值重启
  • 05.自测模拟操作
    • 5.1 如何模拟崩溃
    • 5.2 如何模拟卡顿

# 01.应用调研背景

# 1.1 遇到问题和场景

  1. M3应用崩溃之后(指页面UI卡住,时间不动,停止渲染),无法自动重启,造成前线设备无法恢复刷掌功能。
  2. M3应用UI卡顿之后(指页面UI卡住,无法点击,但进程还存活),无法点击触发事件,无法恢复刷掌功能。
  3. 一个需要长时间运行的QT应用,比如嵌入式设备上的应用,遇到UI无响应的问题,影响用户体验。

# 1.2 需要解决的问题

  1. 需要监听崩溃之后,应用自动重启;需要监听UI渲染停止之后(指UI绘制阻塞,或者卡顿达到某个阀值),应用自动重启。

# 1.3 主要技术卡点

  1. 如何监听到应用的确是崩溃了,比如常见的系统发出来的如未处理的异常、信号等。用什么方法捕获崩溃的信号。
  2. 应用重启,如何监听应用重启成功,而非重启失败。
  3. 关于应用崩溃监听重启,厂商这边是做成了一个系统应用还是如何实现,是否需要一些系统级别权限?
  4. UI卡顿,并不是崩溃,这种情况如何监控。注意卡顿中要去除掉帧卡,比如1s绘制60帧,这个一直卡顿如何监控?
  5. 你们做守护进程(后台进程,类似Android中Service服务)拉起应用,这个如果是一个独立应用,该放在那里,如何保证两个进程都在运行状态?
  6. M4 上对应用的保活是怎么实现的。Linux 自带的 watchdog,M3 上有什么不会重启系统,这里是重启应用,还是重启系统?

# 02.方案探索和思考

# 2.1 如何监听崩溃

Qt应用程序中,监听崩溃(如未处理的异常、信号等)通常需要捕获操作系统发出的信号。

  1. 在类Unix系统(如Linux、macOS)上,常见的导致程序崩溃的信号有SIGSEGV(段错误)、SIGABRT(程序调用abort())等。
  2. 在Windows上,Qt提供了类似的机制来捕获结构化异常(Structured Exception Handling, SEH)。

Qt本身并没有直接提供全局异常捕获的机制,但我们可以使用信号处理函数(Unix-like系统)和结构化异常处理(Windows)来捕获崩溃。

  1. 第一种方法:可以使用信号处理函数来捕获常见的崩溃信号。由于Qt的事件循环和信号槽机制,直接使用信号处理函数可能会干扰Qt的内部信号处理。因此,我们需要谨慎处理。
  2. 第二种方法:使用Qt的qInstallMessageHandler来捕获Qt的消息(包括qDebug、qWarning等),但这并不能捕获崩溃信号。
  3. 第三种方法:为了捕获崩溃并生成堆栈跟踪等信息,我们可以使用第三方库,如Google Breakpad或Crashpad。

# 2.2 如何重启应用

方案1:使用QProcess启动新实例并退出当前实例

这是最常用的方法,原理是启动一个新的应用进程,然后退出当前进程。使用QProcess::startDetached()启动一个新的进程(独立于当前进程)。

优点:简单直接,跨平台,新进程独立,资源释放干净。 缺点:如果应用程序启动慢,会有短暂的时间没有应用界面。如果应用程序有单实例限制(如使用QLockFile),需要先释放锁。

方案2:延迟重启

为了避免资源冲突(如端口占用),可以延迟一段时间再启动新进程。

优点:给系统足够的时间释放资源(如网络端口、文件锁等)。避免新进程启动时资源冲突。 缺点:重启有延迟,用户可能需要等待。

方案3:通过外部脚本/工具重启

在某些复杂场景(如需要管理员权限重启),可以写一个外部脚本(批处理/shell)来重启应用。

优点:可以处理更复杂的重启逻辑,适合需要提升权限的场景(如以管理员身份重启)。缺点:依赖外部脚本,部署复杂,需要为不同系统编写不同脚本。

方案4:热重载(不重启整个应用)

如果重启的目的是为了重新加载某些资源(如语言、皮肤),则不需要重启整个应用。

优点:无需重启应用,用户体验流畅。快速,只更新需要的部分。缺点:实现复杂,需要手动管理状态和资源。不适用于所有场景(如更改全局设置可能需要完全重启)。

结论:

  1. 简单场景:使用方案1(直接重启)或方案2(延迟重启)。
  2. 复杂场景(如需要权限提升):使用方案3(外部脚本)。
  3. 局部更新:优先考虑方案4(热重载)避免重启。

# 2.3 崩溃重启方案

方案1:使用看门狗进程(Watchdog Process),设计思路:

  1. 双进程设计:主进程(应用程序)和看门狗进程。看门狗进程负责启动主进程并监控其状态。
  2. 监控机制:主进程定期向看门狗发送心跳信号(例如,通过本地套接字、共享内存或信号)。如果看门狗在指定时间内未收到心跳,则判定主进程崩溃或无响应。
  3. 重启机制:一旦检测到主进程异常,看门狗进程终止主进程(如果它还在运行但无响应)并重新启动它。

优点:即使主进程完全崩溃(如段错误),看门狗进程仍然可以重启它。缺点:需要维护两个进程,增加了复杂性。

方案2:使用Qt的未捕获异常处理(仅适用于部分异常),设计思路:

  1. 异常捕获:通过设置全局的异常处理函数(例如,在Windows下使用SetUnhandledExceptionFilter,在Linux下使用信号处理)来捕获未处理的异常。
  2. 日志与重启:在异常处理函数中记录崩溃信息,然后重启应用程序。注意,在异常处理函数中直接重启可能不稳定,因为程序可能处于不确定状态。
  3. 安全重启:异常处理函数可以通知一个外部监控进程(如方案1中的看门狗)来执行重启,或者自身通过调用一个外部重启程序来启动新实例并退出当前进程。

优点:可以捕获崩溃并记录详细信息。缺点:不能处理所有类型的崩溃(如堆栈溢出可能导致无法执行处理函数),且直接重启可能不可靠。

方案3:使用系统服务或守护进程,设计思路:

  1. 将应用程序作为系统服务(如Windows服务或Linux的systemd服务)运行,并利用服务管理器的自动重启功能。
  2. 例如,在Linux下,可以在systemd服务文件中配置Restart=on-failure和RestartSec等参数。

优点:利用系统机制,无需额外编写看门狗代码。缺点:平台相关,且可能无法灵活处理所有类型的崩溃(例如,服务管理器可能只检测进程退出,而不检测无响应)。

方案4:内部重启机制(适用于可控的异常),设计思路:

  1. 在应用程序内部,通过捕获异常(如C++的try/catch)来避免崩溃,然后尝试重启应用程序的核心部分(例如,重新初始化关键模块)。
  2. 对于多线程应用,可以在每个线程的顶层捕获异常,然后通知主线程重启。

优点:实现相对简单,不需要多进程。缺点:无法处理系统级错误,且可能无法完全恢复应用程序状态。

最后分析,综合建议:

对于高可靠性要求的应用,推荐使用方案1(看门狗进程),因为它能处理更广泛的崩溃情况,包括进程完全崩溃。同时,可以结合方案2(异常处理)来记录崩溃信息。

注意事项

  1. 避免递归崩溃:使用全局标志isCrashing防止在崩溃处理过程中再次触发崩溃处理。
  2. 资源释放:在崩溃处理函数中,应避免复杂的操作,因为此时程序状态可能已经损坏。只做最必要的操作(如重启)。
  3. 重启方式:使用QProcess::startDetached启动新进程,这样即使当前进程退出,新进程也能独立运行。

# 2.4 监控UI卡顿

Linux中什么是Ui卡顿

  1. 在Qt应用中监控UI卡顿通常涉及到检测主线程(也称为GUI线程)的响应性。
  2. 主线程负责处理所有用户界面事件和绘制操作。如果主线程被长时间阻塞,用户界面就会变得不响应,出现卡顿现象。

以下是一些监控UI卡顿的方法

  1. 使用QTimer检测事件循环的响应时间。可以在主线程中设置一个定时器,定时器每隔一段时间(例如100ms)触发一次。如果定时器超时,说明事件循环没有及时处理定时器事件,可能存在卡顿。
  2. 使用QElapsedTimer测量事件处理时间。在事件处理函数(如QObject::eventFilter)中,使用QElapsedTimer记录每个事件的处理时间。如果某个事件处理时间过长,就可以记录下来。
  3. 使用自定义事件循环监视器。可以创建一个辅助线程来监视主线程的事件循环。辅助线程定期向主线程发送一个自定义事件,并等待主线程处理该事件。如果辅助线程等待的时间超过阈值,则认为主线程卡顿。

针对 Qt 应用 UI 卡顿甚至无响应的问题

在原进程应用中,记录每个事件的处理时间,如果是持续卡顿超过阀值【比如卡顿20分钟】,则调用重启应用。

# 2.5 M3盛思达方案

M3盛思达方案:首先实现app启动+保活

  1. 第一步:将本文件放到项目中;
  2. 第二步:开机运行脚本启动app。在刷掌应用启动main的时候,调用writeWatchdogScript这个函数即可
  3. 第三步:如果推出刷掌应用。然后QCoreApplication::exit(0);
  4. 注意事项:有哪些?保证脚本写入OK

盛思达提供的脚本文件,代码如下所示:

#include <QString>
#include <QFile>
#include <QProcess>
#include <QCryptographicHash>
#include <QTextStream>
#include <QDebug>
#include <QFileInfo>
#include <QDir>

//app看门狗
#define WATCHDOG_PALMAPP_SH     "/data/palmApp/watchdog_palmapp.sh"

class ScriptUtils {
public:
    static bool writeWatchdogScript(bool overrideWrite) {
        QFileInfo fi(WATCHDOG_PALMAPP_SH);
        if (!overrideWrite && (fi.exists() && fi.isFile())) {
            LOG_WARN("Watchdog script already exists: " + QString::fromStdString(WATCHDOG_PALMAPP_SH) + ", Skip.");
            return true;
        }

        QFile scriptFile(WATCHDOG_PALMAPP_SH);
        QFileInfo fileInfo(scriptFile);
        QDir dir(fileInfo.absolutePath());
        if (!dir.exists()) {
            if (!dir.mkpath(".")) {
                LOG_ERROR("Failed to create directory: " + dir.absolutePath());
                return false;
            }
        }

        if (!scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
            LOG_ERROR("Failed to create watchdog script: " + QString::fromStdString(WATCHDOG_PALMAPP_SH));
            return false;
        }

        QTextStream out(&scriptFile);

        out << "#!/bin/sh\n";
        out << "LOGFILE=/data/palmLog/palmapp_watchdog.log\n";
        out << "RESTART_DELAY=5\n";
        out << "echo $$ > /tmp/palmapp_watchdog.pid\n";

        out << "# 清理日志,保留最近7天的记录\n";
        out << "mkdir -p /data/palmLog\n";
        out << "if [ -f \"$LOGFILE\" ]; then\n";
        out << "    TMPLOG=\"/tmp/tmp_watchdog_log\"\n";
        out << "    TODAY=$(date +%s)\n";
        out << "    SEVEN_DAYS_AGO=$((TODAY - 7 * 24 * 60 * 60))\n";
        out << "    echo \"$(date) [watchdog] Filter log older than: $(date -d @$SEVEN_DAYS_AGO)\"\n";
        out << "    awk -v limit=$SEVEN_DAYS_AGO '\n";
        out << "    {\n";
        out << "        timestamp = $1 \" \" $2 \" \" $3 \" \" $4 \" \" $6;\n";
        out << "        cmd = \"date -d \\\"\" timestamp \"\\\" +%s\";\n";
        out << "        cmd | getline t;\n";
        out << "        close(cmd);\n";
        out << "        if (t >= limit) print $0;\n";
        out << "    }' \"$LOGFILE\" > \"$TMPLOG\" && mv \"$TMPLOG\" \"$LOGFILE\";\n";
        out << "fi\n";

        out << "while true; do\n";
        out << "    # 60S连续启动3次,判定为异常\n";
        out << "    NOW=$(date +%s)\n";
        out << "    echo $NOW >> /tmp/palmapp_restart_times.log\n";
        out << "    tail -n 3 /tmp/palmapp_restart_times.log > /tmp/tmp_restart.log\n";
        out << "    mv /tmp/tmp_restart.log /tmp/palmapp_restart_times.log\n";
        out << "    FIRST=$(head -n 1 /tmp/palmapp_restart_times.log)\n";
        out << "    [ -z \"$FIRST\" ] && FIRST=$NOW\n";
        out << "    DIFF=$((NOW - FIRST))\n";

        out << "    if [ $(wc -l < /tmp/palmapp_restart_times.log) -ge 3 ] && [ $DIFF -lt 60 ]; then\n";
        out << "        echo \"$(date) [watchdog] Rebooted 3 times within 60 seconds. Abnormal restart detected.\" >> \"$LOGFILE\"\n";
        out << "    fi\n";

        out << "    echo \"$(date) [watchdog] Launching palmapp...\" >> \"$LOGFILE\"\n";
        out << "    if [ -x /data/palmApp/bin/palmapp ]; then\n";
        out << "        export LD_LIBRARY_PATH=/data/palmApp/lib/angstrong\n";
        out << "        /data/palmApp/bin/palmapp -platform linuxfb\n";
        out << "    elif [ -x /usr/bin/palmapp ]; then\n";
        out << "        export LD_LIBRARY_PATH=/usr/lib/angstrong\n";
        out << "        /usr/bin/palmapp -platform linuxfb\n";
        out << "    else\n";
        out << "        echo \"$(date) [watchdog] palmapp not found\" >> \"$LOGFILE\"\n";
        out << "    fi\n";

        out << "    EXIT_CODE=$?\n";
        out << "    echo \"$(date) [watchdog] palmapp exited with code $EXIT_CODE\" >> \"$LOGFILE\"\n";

        out << "    if [ \"$EXIT_CODE\" -eq 0 ]; then\n";
        out << "        echo \"$(date) [watchdog] Normal exit detected, stopping watchdog.\" >> \"$LOGFILE\"\n";
        out << "        break\n";
        out << "    fi\n";

        out << "    echo \"$(date) [watchdog] Abnormal exit detected, restarting in $RESTART_DELAY seconds...\" >> \"$LOGFILE\"\n";
        out << "    sleep $RESTART_DELAY\n";
        out << "done\n";

        out << "rm -f /tmp/palmapp_watchdog.pid\n";
        out << "rm -f /tmp/palmapp_restart_times.log\n";

        scriptFile.close();

        //设置脚本可执行
        if (QProcess::execute("chmod +x " + QString::fromStdString(WATCHDOG_PALMAPP_SH)) != 0) {
            LOG_ERROR("Failed to set executable permission on watchdog script");
            return false;
        }

        LOG_INFO("Watchdog script created successfully");
        return true;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

这段代码通过生成一个看门狗脚本,实现了对 palmapp 应用程序的监控和管理。它的核心功能包括应用程序的自动重启、日志记录和异常检测,能够有效提高系统的稳定性和可靠性。通过 QFile 和 QTextStream,代码实现了脚本内容的动态生成和写入,并通过 QProcess 设置脚本的可执行权限。

盛思达开门狗脚本验证方式,如下所示:

  1. 手动kill应用后,自测效果:应用可以自动重启
  2. 调研,在qml中,调用Qt.quit(),用盛思达脚本,自测效果:可以自动重启
  3. 调研,在qml中,设置obj是null,然后obj.invalidMethod(); ,触发未捕获的异常,自测效果:qml异常不会崩溃
  4. 调研,在c++中, int* ptr = nullptr; *ptr = 42; 访问空指针,触发崩溃。自测效果:可以崩溃重启
  5. 调研,在c++中,std::abort(); 或者使用 std::exit(1);触发崩溃。自测效果:可以崩溃重启
  6. 调研,在c++中,QCoreApplication::exit(reason); reason为0是让app退出。自测效果:可以崩溃重启
  7. 调研,在c++中,QCoreApplication::exit(reason); 非0是让app重启。自测效果:可以崩溃重启

有点不足是:watchdog_palmapp.sh 会覆盖我们自己的脚本。如果push新文件,要手动杀死应用后,再通过脚本启动应用。否则会启动多次应用!

在终端运行脚本,直接ctrl+c停止,watchdog就退出了

# 2.6 方案讨论结论

# 03.保活监听重启方案

# 3.1 整体架构设计

我将设计一个完整的保活系统,包含以下核心组件:

  1. 主应用程序 - 需要被监控的应用
  2. 守护进程 - 独立进程,负责监控主应用状态,并在主应用崩溃时重启它。
  3. 心跳检测机制 - 主应用定期向守护进程发送心跳
  4. 崩溃检测与自动重启 - 守护进程检测主应用崩溃并重启
  5. 通信机制 - 使用QLocalSocket进行进程间通信
  6. 日志系统 - 记录监控和重启事件
  7. 系统托盘界面 - 提供用户控制界面

详细实现方案具体的功能要包括以下这些内容:

1.守护进程核心功能

  1. 启动时检查主应用状态
  2. 监听主应用心跳信号
  3. 心跳超时检测(5秒)
  4. 崩溃检测与自动重启
  5. 日志记录所有关键事件
  6. 系统托盘控制界面

2.主应用核心功能

  1. 启动时连接守护进程
  2. 定期发送心跳信号(每2秒)
  3. 崩溃模拟功能(用于测试)
  4. 正常关闭通知守护进程

# 3.2 守护进程设计

  1. 守护进程会定期检查主应用是否在运行。
  2. 如果主应用崩溃,守护进程将重新启动它。
  3. 守护进程自身需要高可靠性,避免自身崩溃。

# 3.3 监控机制设计

使用心跳机制:主应用定期向守护进程发送心跳信号。

守护进程如果在指定时间内没有收到心跳,则认为主应用崩溃,执行重启。

# 3.4 通信机制设计

使用本地套接字(Local Socket)或共享内存进行进程间通信(IPC)。

这里选择使用QLocalSocket和QLocalServer进行通信,因为Qt提供了这些类,方便跨平台。

# 3.5 守护进程的保活

守护进程需要保证自身在后台运行,并且不会被系统杀死(在用户允许的情况下)。

对于不同的操作系统(如Windows、Linux、macOS),守护进程的实现方式有所不同,但Qt可以帮我们屏蔽大部分差异。

守护进程可以没有界面,或者提供一个简单的系统托盘图标,允许用户查看状态或退出。守护进程需要记录监控日志,包括主应用的心跳状态、重启事件等。

# 3.6 重启的策略

当检测到主应用崩溃时,守护进程记录日志,并重新启动主应用。

可以设置最大重启次数,避免无限重启导致系统资源耗尽。

# 3.7 注意事项说明

  1. 守护进程的启动:在实际部署时,守护进程可能需要设置为开机自启动(通过系统服务或启动项)。
  2. 权限问题:在某些系统上,可能需要管理员权限才能进行进程监控。
  3. 资源占用:守护进程应尽量轻量,避免占用过多系统资源。
  4. 多平台支持:上述代码在Windows、Linux和macOS上应该都能运行,但需要测试。
  5. 日志轮转:长时间运行的守护进程需要日志轮转,避免日志文件过大。

# 04.UI卡顿重启方案

# 4.1 背景和思考

开发一个需要长时间运行的QT应用,比如嵌入式设备上的应用,遇到UI无响应的问题,影响用户体验。

深层需求可能不只是解决卡顿,而是确保系统能自动恢复,减少人工干预,提升可靠性。

需要设计一个监控进程和UI进程分离的方案,用IPC通信,这样即使UI卡死也能被检测到并重启。同时要处理进程间通信和状态同步,确保重启后UI能恢复之前的状态。

# 4.2 整体方案思路

方案思路大概如下所示:

  1. 监控主线程(UI线程)的事件循环处理时间。如果事件处理时间过长,则意味着卡顿。
  2. 在QML引擎中,我们可以通过重写QQuickWindow的beforeSynchronizing和afterSynchronizing信号,以及beforeRendering和afterRendering信号来监控渲染时间。
  3. 另外,我们还可以通过一个定时器,定期检查主线程是否被阻塞,如果定时器超时,则说明有卡顿。

我们可以安装一个事件过滤器到主线程的事件循环中,记录每个事件处理的时间。如果某个事件处理时间超过阈值(如50ms),则认为发生了卡顿。 步骤:

  1. 步骤1:创建一个事件过滤器类,用于记录每个事件的处理时间
  2. 步骤2:在QML引擎初始化后,将该事件过滤器安装到主线程的事件循环中
  3. 步骤3:设置一个阈值,当事件处理时间超过阈值时,记录卡顿信息(包括时间、事件类型等)
  4. 步骤4:为了更详细地监控QML,我们还可以监控QML引擎的信号,如QQuickWindow的帧渲染信号
  5. 步骤5:将卡顿信息输出到日志文件,或者通过某种方式上报

# 4.3 先要帧率监控

设计一个帧率分析器(FrameAnalyzer),用于监控和统计应用程序的帧率(FPS,Frames Per Second)以及检测掉帧情况。

设计关键技术点和思路:

  1. 在每一帧渲染完成后触发,实时计算当前帧率和平均帧率。当前帧率计算fps = 1000.0 / elapsed,其中 elapsed 是帧间时间(毫秒)。
  2. 使用 QElapsedTimer 来精确测量帧间时间间隔。
  3. 通过信号(emit)通知外部帧率更新和掉帧事件,便于与其他模块交互。

# 4.4 事件循环分析

设计事件循环监视器(EventLoopWatcher),用于监控 Qt 应用程序的事件循环(QEventLoop)的性能,特别是检测长时间阻塞的事件处理。

设计关键技术点和思路:

  1. 通过定时器(QTimer)定期检查事件循环的性能,确保事件处理不会长时间阻塞主线程。

# 4.5 渲染性能分析

实现了一个渲染性能分析器(RenderProfiler),用于监控 Qt Quick 应用程序的渲染性能,特别是同步(Synchronizing)和渲染(Rendering)阶段的耗时。

设计关键技术点和思路:

  1. 实时监控,通过连接到 QQuickWindow 的信号,实时监控渲染流程中的关键阶段(同步和渲染)。如果同步或渲染阶段的耗时超过阈值,则提出告警通知。
  2. 数据统计,记录同步和渲染阶段的耗时,并计算总耗时、平均耗时以及各阶段的占比。提供统计数据,便于分析渲染性能。

# 4.6 卡顿诊断判断

不是所有UI卡顿都直接重启,那什么情况下卡顿重启?这就必须对卡顿进行诊断。整合帧率、事件循环阻塞等综合数据,形成数据诊断

# 4.7 可视化界面

# 4.8 设计卡顿阀值重启

# 05.自测模拟操作

# 5.1 如何模拟崩溃

  1. 方法 1:调用 Qt.quit() 或 Qt.exit()。通过调用 Qt.quit() 或 Qt.exit() 可以强制退出应用程序,模拟崩溃的效果。测试结果:
Button {
    onClicked: {
        console.log("Simulating crash...");
        Qt.quit(); // 或者使用 Qt.exit(1);
    }
}
1
2
3
4
5
6
  1. 触发未捕获的异常。在 QML 中触发未捕获的异常,可以模拟应用程序崩溃的效果。测试结果:
Button {
    onClicked: {
        var obj = null;
        obj.invalidMethod(); // 触发未捕获的异常
    }
}
1
2
3
4
5
6
  1. 方法 3:调用 C++ 代码触发崩溃。在 QML 中调用 C++ 代码,通过 C++ 触发崩溃(如访问空指针、除以零等)。测试结果:
Q_INVOKABLE void simulateCrash() {
    qDebug() << "Simulating crash in C++...";
    int* ptr = nullptr;
    *ptr = 42; // 访问空指针,触发崩溃
}
1
2
3
4
5
  1. 方法 4:调用系统函数触发崩溃。在 QML 中调用 C++ 代码,通过系统函数(如 abort() 或 exit())触发崩溃。测试结果:
Q_INVOKABLE void simulateCrash() {
    qDebug() << "Simulating crash in C++...";
    std::abort(); // 或者使用 std::exit(1);
}
1
2
3
4
  1. 方法 5:模拟内存泄漏。通过不断分配内存而不释放,模拟内存泄漏导致应用程序崩溃。
Q_INVOKABLE void simulateCrash() {
    std::vector<int*> ptrs;
    while (true) {
        ptrs.push_back(new int[1000000]); // 不断分配内存
    }
}
1
2
3
4
5
6

# 5.2 如何模拟卡顿

上次更新: 2026/06/10, 11:13:41
LVGL设计原理

← LVGL设计原理

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式