某些情况下,你的App的运行依赖于手机的系统时间, 或者对系统时间的准确性有要求.然而, 用户是可以任意修改系统时间的,当手机端的系统时间不准确时, 会影响你app的部分功能, 或者你希望在某种情况下检查手机系统时间的准确性. 我最近做的某项目, 依赖于系统时间的准确性, 希望通过某种方法来校验本地时间是否准确.此文分享我使用的办法.
一、思路介绍
原理:通过获取服务端的时间, 来跟手机系统时间进行对比, 来检查本地时间的准确性.
问题进一步转化为:如何方便和相对准确的, 随时获取服务端时间?
(默认服务端时间是准确的, 或者你选择时间准确的服务器来获取时间)
二、实现方法
2.1 如何获取服务器时间?
向被认为时间准确的可信服务器发送请求, 取response中的Date字段作为服务端当前的时间.
Date = (
"Thu, 01 Jun 2023 07:10:40 GMT"
);
2.2 请求如何选取?
可以专门发送一个请求来获取服务端时间, 也可以利用现有app中的任意请求;我选择后者.
从现有app请求中选择一个符合你要求的请求;可以通过domain,path等来过滤请求, 还可以过滤响应时间等.
iOS10.0及以后版本,监听下面delegate方法, 获取metrics, 里面记录了请求操作的各个阶段时间.可以用来估算服务器之于本地相对准确的时间.具体可以根据自己对时间精度的要求进行计算.(metrics也包含response数据, 可以获取response的Date字段)
/*
* Sent when complete statistics information has been collected for the task.
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
2.3 如何便利且高效的实时获取服务端时间?
首先, 根据2.2中的方法记录相较本地时间误差最小的服务端时间serverTime, 同时记录本地iOS开机时间systemTime; (开机时间[NSProcessInfo processInfo].systemUptime)
其次, 计算任意时间的服务端时间的方法:
比如当前系统开机时间currentSystemTime, 则当前服务端时间为:
currentServerTime = serverTime + (currentSystemTime-systemTime)
此处做个修正
苹果API "[NSProcessInfo processInfo].systemUptime"在统计开机时长时存在漏洞,
当手机锁屏, 且手机电源线断开时, 此API不会统计息屏时间.(深度休眠不会被记录到开机时长)
如果所在的应用程序开启了保活功能, 比如导航, 定位等功能, 会对休眠有影响(此时比较难发现此问题), 但是时间拉长到1小时以上, 还是会发现此API统计的开机时长变短;
可以替换为内核方法可解决此问题:
方法1:
struct timeval boottime;
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
size_t size = sizeof(boottime);
struct timeval now;
struct timezone tz;
gettimeofday(&now, &tz);
double uptime = -1;
if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
{
uptime = now.tv_sec - boottime.tv_sec;
uptime += (double)(now.tv_usec - boottime.tv_usec) / 1000000.0;
}
return uptime;
方法2:
if (@available(iOS 10.0, *)) {
struct timespec timespec;
int status = clock_gettime(CLOCK_MONOTONIC_RAW, ×pec);
if (!status) {
assert(!status);
}
return timespec.tv_sec + (double)timespec.tv_nsec / NSEC_PER_SEC;
} else {
// timespec->tv_sec = 0;
// timespec->tv_nsec = 0;
}
2.4 如何减少代码侵入
<1>在网络delegate中发通知,
<2>在单独的时间检查模块比如systemTimeChecker封装类处理时间校验逻辑.
<3>添加监听这个通知, 然后根据条件筛选适合的app内现有的网络请求, 捕获系统时间并记录开机时间. (可以在+load类方法里添加通知)
<4>捕获服务端时间后, 移除通知;
然后根据上述方法计算任意时间点的服务器时间, 对外只需要提供"检查系统时间是否被修改"的一个接口即可.
实现代码最简洁,且,无任何侵入.