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 遇到问题和场景
- M3应用崩溃之后(指页面UI卡住,时间不动,停止渲染),无法自动重启,造成前线设备无法恢复刷掌功能。
- M3应用UI卡顿之后(指页面UI卡住,无法点击,但进程还存活),无法点击触发事件,无法恢复刷掌功能。
- 一个需要长时间运行的QT应用,比如嵌入式设备上的应用,遇到UI无响应的问题,影响用户体验。
# 1.2 需要解决的问题
- 需要监听崩溃之后,应用自动重启;需要监听UI渲染停止之后(指UI绘制阻塞,或者卡顿达到某个阀值),应用自动重启。
# 1.3 主要技术卡点
- 如何监听到应用的确是崩溃了,比如常见的系统发出来的如未处理的异常、信号等。用什么方法捕获崩溃的信号。
- 应用重启,如何监听应用重启成功,而非重启失败。
- 关于应用崩溃监听重启,厂商这边是做成了一个系统应用还是如何实现,是否需要一些系统级别权限?
- UI卡顿,并不是崩溃,这种情况如何监控。注意卡顿中要去除掉帧卡,比如1s绘制60帧,这个一直卡顿如何监控?
- 你们做守护进程(后台进程,类似Android中Service服务)拉起应用,这个如果是一个独立应用,该放在那里,如何保证两个进程都在运行状态?
- M4 上对应用的保活是怎么实现的。Linux 自带的 watchdog,M3 上有什么不会重启系统,这里是重启应用,还是重启系统?
# 02.方案探索和思考
# 2.1 如何监听崩溃
Qt应用程序中,监听崩溃(如未处理的异常、信号等)通常需要捕获操作系统发出的信号。
- 在类Unix系统(如Linux、macOS)上,常见的导致程序崩溃的信号有SIGSEGV(段错误)、SIGABRT(程序调用abort())等。
- 在Windows上,Qt提供了类似的机制来捕获结构化异常(Structured Exception Handling, SEH)。
Qt本身并没有直接提供全局异常捕获的机制,但我们可以使用信号处理函数(Unix-like系统)和结构化异常处理(Windows)来捕获崩溃。
- 第一种方法:可以使用信号处理函数来捕获常见的崩溃信号。由于Qt的事件循环和信号槽机制,直接使用信号处理函数可能会干扰Qt的内部信号处理。因此,我们需要谨慎处理。
- 第二种方法:使用Qt的qInstallMessageHandler来捕获Qt的消息(包括qDebug、qWarning等),但这并不能捕获崩溃信号。
- 第三种方法:为了捕获崩溃并生成堆栈跟踪等信息,我们可以使用第三方库,如Google Breakpad或Crashpad。
# 2.2 如何重启应用
方案1:使用QProcess启动新实例并退出当前实例
这是最常用的方法,原理是启动一个新的应用进程,然后退出当前进程。使用QProcess::startDetached()启动一个新的进程(独立于当前进程)。
优点:简单直接,跨平台,新进程独立,资源释放干净。 缺点:如果应用程序启动慢,会有短暂的时间没有应用界面。如果应用程序有单实例限制(如使用QLockFile),需要先释放锁。
方案2:延迟重启
为了避免资源冲突(如端口占用),可以延迟一段时间再启动新进程。
优点:给系统足够的时间释放资源(如网络端口、文件锁等)。避免新进程启动时资源冲突。 缺点:重启有延迟,用户可能需要等待。
方案3:通过外部脚本/工具重启
在某些复杂场景(如需要管理员权限重启),可以写一个外部脚本(批处理/shell)来重启应用。
优点:可以处理更复杂的重启逻辑,适合需要提升权限的场景(如以管理员身份重启)。缺点:依赖外部脚本,部署复杂,需要为不同系统编写不同脚本。
方案4:热重载(不重启整个应用)
如果重启的目的是为了重新加载某些资源(如语言、皮肤),则不需要重启整个应用。
优点:无需重启应用,用户体验流畅。快速,只更新需要的部分。缺点:实现复杂,需要手动管理状态和资源。不适用于所有场景(如更改全局设置可能需要完全重启)。
结论:
- 简单场景:使用方案1(直接重启)或方案2(延迟重启)。
- 复杂场景(如需要权限提升):使用方案3(外部脚本)。
- 局部更新:优先考虑方案4(热重载)避免重启。
# 2.3 崩溃重启方案
方案1:使用看门狗进程(Watchdog Process),设计思路:
- 双进程设计:主进程(应用程序)和看门狗进程。看门狗进程负责启动主进程并监控其状态。
- 监控机制:主进程定期向看门狗发送心跳信号(例如,通过本地套接字、共享内存或信号)。如果看门狗在指定时间内未收到心跳,则判定主进程崩溃或无响应。
- 重启机制:一旦检测到主进程异常,看门狗进程终止主进程(如果它还在运行但无响应)并重新启动它。
优点:即使主进程完全崩溃(如段错误),看门狗进程仍然可以重启它。缺点:需要维护两个进程,增加了复杂性。
方案2:使用Qt的未捕获异常处理(仅适用于部分异常),设计思路:
- 异常捕获:通过设置全局的异常处理函数(例如,在Windows下使用SetUnhandledExceptionFilter,在Linux下使用信号处理)来捕获未处理的异常。
- 日志与重启:在异常处理函数中记录崩溃信息,然后重启应用程序。注意,在异常处理函数中直接重启可能不稳定,因为程序可能处于不确定状态。
- 安全重启:异常处理函数可以通知一个外部监控进程(如方案1中的看门狗)来执行重启,或者自身通过调用一个外部重启程序来启动新实例并退出当前进程。
优点:可以捕获崩溃并记录详细信息。缺点:不能处理所有类型的崩溃(如堆栈溢出可能导致无法执行处理函数),且直接重启可能不可靠。
方案3:使用系统服务或守护进程,设计思路:
- 将应用程序作为系统服务(如Windows服务或Linux的systemd服务)运行,并利用服务管理器的自动重启功能。
- 例如,在Linux下,可以在systemd服务文件中配置Restart=on-failure和RestartSec等参数。
优点:利用系统机制,无需额外编写看门狗代码。缺点:平台相关,且可能无法灵活处理所有类型的崩溃(例如,服务管理器可能只检测进程退出,而不检测无响应)。
方案4:内部重启机制(适用于可控的异常),设计思路:
- 在应用程序内部,通过捕获异常(如C++的try/catch)来避免崩溃,然后尝试重启应用程序的核心部分(例如,重新初始化关键模块)。
- 对于多线程应用,可以在每个线程的顶层捕获异常,然后通知主线程重启。
优点:实现相对简单,不需要多进程。缺点:无法处理系统级错误,且可能无法完全恢复应用程序状态。
最后分析,综合建议:
对于高可靠性要求的应用,推荐使用方案1(看门狗进程),因为它能处理更广泛的崩溃情况,包括进程完全崩溃。同时,可以结合方案2(异常处理)来记录崩溃信息。
注意事项
- 避免递归崩溃:使用全局标志isCrashing防止在崩溃处理过程中再次触发崩溃处理。
- 资源释放:在崩溃处理函数中,应避免复杂的操作,因为此时程序状态可能已经损坏。只做最必要的操作(如重启)。
- 重启方式:使用QProcess::startDetached启动新进程,这样即使当前进程退出,新进程也能独立运行。
# 2.4 监控UI卡顿
Linux中什么是Ui卡顿
- 在Qt应用中监控UI卡顿通常涉及到检测主线程(也称为GUI线程)的响应性。
- 主线程负责处理所有用户界面事件和绘制操作。如果主线程被长时间阻塞,用户界面就会变得不响应,出现卡顿现象。
以下是一些监控UI卡顿的方法
- 使用QTimer检测事件循环的响应时间。可以在主线程中设置一个定时器,定时器每隔一段时间(例如100ms)触发一次。如果定时器超时,说明事件循环没有及时处理定时器事件,可能存在卡顿。
- 使用QElapsedTimer测量事件处理时间。在事件处理函数(如QObject::eventFilter)中,使用QElapsedTimer记录每个事件的处理时间。如果某个事件处理时间过长,就可以记录下来。
- 使用自定义事件循环监视器。可以创建一个辅助线程来监视主线程的事件循环。辅助线程定期向主线程发送一个自定义事件,并等待主线程处理该事件。如果辅助线程等待的时间超过阈值,则认为主线程卡顿。
针对 Qt 应用 UI 卡顿甚至无响应的问题
在原进程应用中,记录每个事件的处理时间,如果是持续卡顿超过阀值【比如卡顿20分钟】,则调用重启应用。
# 2.5 M3盛思达方案
M3盛思达方案:首先实现app启动+保活
- 第一步:将本文件放到项目中;
- 第二步:开机运行脚本启动app。在刷掌应用启动
main的时候,调用writeWatchdogScript这个函数即可 - 第三步:如果推出刷掌应用。然后
QCoreApplication::exit(0); - 注意事项:有哪些?保证脚本写入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;
}
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 设置脚本的可执行权限。
盛思达开门狗脚本验证方式,如下所示:
- 手动kill应用后,自测效果:应用可以自动重启
- 调研,在qml中,调用Qt.quit(),用盛思达脚本,自测效果:可以自动重启
- 调研,在qml中,设置obj是null,然后obj.invalidMethod(); ,触发未捕获的异常,自测效果:qml异常不会崩溃
- 调研,在c++中, int* ptr = nullptr; *ptr = 42; 访问空指针,触发崩溃。自测效果:可以崩溃重启
- 调研,在c++中,std::abort(); 或者使用 std::exit(1);触发崩溃。自测效果:可以崩溃重启
- 调研,在c++中,QCoreApplication::exit(reason); reason为0是让app退出。自测效果:可以崩溃重启
- 调研,在c++中,QCoreApplication::exit(reason); 非0是让app重启。自测效果:可以崩溃重启
有点不足是:watchdog_palmapp.sh 会覆盖我们自己的脚本。如果push新文件,要手动杀死应用后,再通过脚本启动应用。否则会启动多次应用!
在终端运行脚本,直接ctrl+c停止,watchdog就退出了
# 2.6 方案讨论结论
# 03.保活监听重启方案
# 3.1 整体架构设计
我将设计一个完整的保活系统,包含以下核心组件:
- 主应用程序 - 需要被监控的应用
- 守护进程 - 独立进程,负责监控主应用状态,并在主应用崩溃时重启它。
- 心跳检测机制 - 主应用定期向守护进程发送心跳
- 崩溃检测与自动重启 - 守护进程检测主应用崩溃并重启
- 通信机制 - 使用QLocalSocket进行进程间通信
- 日志系统 - 记录监控和重启事件
- 系统托盘界面 - 提供用户控制界面
详细实现方案具体的功能要包括以下这些内容:
1.守护进程核心功能
- 启动时检查主应用状态
- 监听主应用心跳信号
- 心跳超时检测(5秒)
- 崩溃检测与自动重启
- 日志记录所有关键事件
- 系统托盘控制界面
2.主应用核心功能
- 启动时连接守护进程
- 定期发送心跳信号(每2秒)
- 崩溃模拟功能(用于测试)
- 正常关闭通知守护进程
# 3.2 守护进程设计
- 守护进程会定期检查主应用是否在运行。
- 如果主应用崩溃,守护进程将重新启动它。
- 守护进程自身需要高可靠性,避免自身崩溃。
# 3.3 监控机制设计
使用心跳机制:主应用定期向守护进程发送心跳信号。
守护进程如果在指定时间内没有收到心跳,则认为主应用崩溃,执行重启。
# 3.4 通信机制设计
使用本地套接字(Local Socket)或共享内存进行进程间通信(IPC)。
这里选择使用QLocalSocket和QLocalServer进行通信,因为Qt提供了这些类,方便跨平台。
# 3.5 守护进程的保活
守护进程需要保证自身在后台运行,并且不会被系统杀死(在用户允许的情况下)。
对于不同的操作系统(如Windows、Linux、macOS),守护进程的实现方式有所不同,但Qt可以帮我们屏蔽大部分差异。
守护进程可以没有界面,或者提供一个简单的系统托盘图标,允许用户查看状态或退出。守护进程需要记录监控日志,包括主应用的心跳状态、重启事件等。
# 3.6 重启的策略
当检测到主应用崩溃时,守护进程记录日志,并重新启动主应用。
可以设置最大重启次数,避免无限重启导致系统资源耗尽。
# 3.7 注意事项说明
- 守护进程的启动:在实际部署时,守护进程可能需要设置为开机自启动(通过系统服务或启动项)。
- 权限问题:在某些系统上,可能需要管理员权限才能进行进程监控。
- 资源占用:守护进程应尽量轻量,避免占用过多系统资源。
- 多平台支持:上述代码在Windows、Linux和macOS上应该都能运行,但需要测试。
- 日志轮转:长时间运行的守护进程需要日志轮转,避免日志文件过大。
# 04.UI卡顿重启方案
# 4.1 背景和思考
开发一个需要长时间运行的QT应用,比如嵌入式设备上的应用,遇到UI无响应的问题,影响用户体验。
深层需求可能不只是解决卡顿,而是确保系统能自动恢复,减少人工干预,提升可靠性。
需要设计一个监控进程和UI进程分离的方案,用IPC通信,这样即使UI卡死也能被检测到并重启。同时要处理进程间通信和状态同步,确保重启后UI能恢复之前的状态。
# 4.2 整体方案思路
方案思路大概如下所示:
- 监控主线程(UI线程)的事件循环处理时间。如果事件处理时间过长,则意味着卡顿。
- 在QML引擎中,我们可以通过重写QQuickWindow的beforeSynchronizing和afterSynchronizing信号,以及beforeRendering和afterRendering信号来监控渲染时间。
- 另外,我们还可以通过一个定时器,定期检查主线程是否被阻塞,如果定时器超时,则说明有卡顿。
我们可以安装一个事件过滤器到主线程的事件循环中,记录每个事件处理的时间。如果某个事件处理时间超过阈值(如50ms),则认为发生了卡顿。 步骤:
- 步骤1:创建一个事件过滤器类,用于记录每个事件的处理时间
- 步骤2:在QML引擎初始化后,将该事件过滤器安装到主线程的事件循环中
- 步骤3:设置一个阈值,当事件处理时间超过阈值时,记录卡顿信息(包括时间、事件类型等)
- 步骤4:为了更详细地监控QML,我们还可以监控QML引擎的信号,如QQuickWindow的帧渲染信号
- 步骤5:将卡顿信息输出到日志文件,或者通过某种方式上报
# 4.3 先要帧率监控
设计一个帧率分析器(FrameAnalyzer),用于监控和统计应用程序的帧率(FPS,Frames Per Second)以及检测掉帧情况。
设计关键技术点和思路:
- 在每一帧渲染完成后触发,实时计算当前帧率和平均帧率。当前帧率计算fps = 1000.0 / elapsed,其中 elapsed 是帧间时间(毫秒)。
- 使用 QElapsedTimer 来精确测量帧间时间间隔。
- 通过信号(emit)通知外部帧率更新和掉帧事件,便于与其他模块交互。
# 4.4 事件循环分析
设计事件循环监视器(EventLoopWatcher),用于监控 Qt 应用程序的事件循环(QEventLoop)的性能,特别是检测长时间阻塞的事件处理。
设计关键技术点和思路:
- 通过定时器(QTimer)定期检查事件循环的性能,确保事件处理不会长时间阻塞主线程。
# 4.5 渲染性能分析
实现了一个渲染性能分析器(RenderProfiler),用于监控 Qt Quick 应用程序的渲染性能,特别是同步(Synchronizing)和渲染(Rendering)阶段的耗时。
设计关键技术点和思路:
- 实时监控,通过连接到 QQuickWindow 的信号,实时监控渲染流程中的关键阶段(同步和渲染)。如果同步或渲染阶段的耗时超过阈值,则提出告警通知。
- 数据统计,记录同步和渲染阶段的耗时,并计算总耗时、平均耗时以及各阶段的占比。提供统计数据,便于分析渲染性能。
# 4.6 卡顿诊断判断
不是所有UI卡顿都直接重启,那什么情况下卡顿重启?这就必须对卡顿进行诊断。整合帧率、事件循环阻塞等综合数据,形成数据诊断
# 4.7 可视化界面
# 4.8 设计卡顿阀值重启
# 05.自测模拟操作
# 5.1 如何模拟崩溃
- 方法 1:调用 Qt.quit() 或 Qt.exit()。通过调用 Qt.quit() 或 Qt.exit() 可以强制退出应用程序,模拟崩溃的效果。测试结果:
Button {
onClicked: {
console.log("Simulating crash...");
Qt.quit(); // 或者使用 Qt.exit(1);
}
}
2
3
4
5
6
- 触发未捕获的异常。在 QML 中触发未捕获的异常,可以模拟应用程序崩溃的效果。测试结果:
Button {
onClicked: {
var obj = null;
obj.invalidMethod(); // 触发未捕获的异常
}
}
2
3
4
5
6
- 方法 3:调用 C++ 代码触发崩溃。在 QML 中调用 C++ 代码,通过 C++ 触发崩溃(如访问空指针、除以零等)。测试结果:
Q_INVOKABLE void simulateCrash() {
qDebug() << "Simulating crash in C++...";
int* ptr = nullptr;
*ptr = 42; // 访问空指针,触发崩溃
}
2
3
4
5
- 方法 4:调用系统函数触发崩溃。在 QML 中调用 C++ 代码,通过系统函数(如 abort() 或 exit())触发崩溃。测试结果:
Q_INVOKABLE void simulateCrash() {
qDebug() << "Simulating crash in C++...";
std::abort(); // 或者使用 std::exit(1);
}
2
3
4
- 方法 5:模拟内存泄漏。通过不断分配内存而不释放,模拟内存泄漏导致应用程序崩溃。
Q_INVOKABLE void simulateCrash() {
std::vector<int*> ptrs;
while (true) {
ptrs.push_back(new int[1000000]); // 不断分配内存
}
}
2
3
4
5
6