iOS C++中如何获取应用挂起时停止计时的稳定/单调时钟
iOS C++ 实现应用挂起即停止的单调计时方案
方案1:基于进程CPU时钟的纯C++实现(无侵入)
该方案使用POSIX标准的CLOCK_PROCESS_CPUTIME_ID时钟,它统计当前进程所有线程实际占用CPU的总时间,应用被系统挂起时进程停止调度,时钟计数会自动停止,完全匹配需求,无需额外对接iOS系统逻辑。
实现代码
#include <time.h> #include <stdint.h> // 获取当前进程CPU时间,单位为纳秒 uint64_t get_process_running_time_ns() { struct timespec ts; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); return static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + static_cast<uint64_t>(ts.tv_nsec); } // 耗时统计示例 void measure_target_operation() { uint64_t start_time = get_process_running_time_ns(); // 此处放置需要测算耗时的业务逻辑 uint64_t end_time = get_process_running_time_ns(); uint64_t cost_ns = end_time - start_time; // 按需转换单位:毫秒 = cost_ns / 1e6,秒 = cost_ns / 1e9 }
适用场景说明
- 优点:纯C++跨POSIX平台实现,天然单调稳定,不受系统时间修改、应用挂起影响,无需额外适配
- 缺点:仅统计进程实际占用CPU的时间,操作中的IO等待、线程休眠时间不会被计入。如果需要统计操作从触发到完成的全链路真实耗时(仅排除挂起时间),请使用方案2。
方案2:基于生命周期监听的挂起时长扣除实现
该方案以系统单调时钟为基础,通过监听应用前后台切换事件统计总挂起时长,最终从总耗时中扣除挂起时长,实现仅统计应用活跃状态的时间。
实现逻辑
- 选择
CLOCK_MONOTONIC作为基础计时时钟(不受系统时间修改影响,仅整机重启才会重置) - 监听
UIApplicationDidEnterBackground通知,记录应用进入后台的时间点 - 监听
UIApplicationWillEnterForeground通知,计算本次挂起的时长并累加到全局总挂起时长 - 最终耗时 = 操作结束时钟时间 - 操作开始时钟时间 - 操作执行期间的总挂起时长
核心代码(Objective-C实现,保存为.mm文件即可在C工程中调用)
#include <time.h> #include <stdint.h> #import <UIKit/UIKit.h> static uint64_t g_total_suspend_ns = 0; static uint64_t g_enter_background_time_ns = 0; // 初始化生命周期监听,在应用启动时调用一次即可 void init_suspend_aware_timer() { [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); g_enter_background_time_ns = static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + static_cast<uint64_t>(ts.tv_nsec); }]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { if (g_enter_background_time_ns == 0) return; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t current = static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + static_cast<uint64_t>(ts.tv_nsec); g_total_suspend_ns += (current - g_enter_background_time_ns); g_enter_background_time_ns = 0; }]; } // 获取扣除挂起时间的单调时钟值,单位纳秒 uint64_t get_suspend_aware_time_ns() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t current = static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + static_cast<uint64_t>(ts.tv_nsec); return current - g_total_suspend_ns; } // 耗时统计示例 void measure_target_operation() { uint64_t start_time = get_suspend_aware_time_ns(); // 此处放置需要测算耗时的业务逻辑 uint64_t end_time = get_suspend_aware_time_ns(); uint64_t cost_ns = end_time - start_time; }
适用场景说明
- 优点:统计的是应用活跃状态下的真实流逝时间,包含IO等待、线程休眠等非CPU占用时间,符合绝大多数业务耗时统计的需求
- 注意:需要在应用启动时调用一次
init_suspend_aware_timer初始化监听逻辑,若应用开启了后台任务权限,后台任务运行期间的时间会被正常计入,仅应用被系统彻底挂起后才会停止计时。
内容的提问来源于stack exchange,提问作者randomThought
相关产品推荐
相关产品推荐

