Xamarin iOS如何获取不受休眠与系统日期影响的设备运行时间?
在Xamarin iOS中获取不受影响的系统运行时长
首先,你之前的代码返回常量的原因很明确:kern.boottime返回的并不是运行时长,而是设备启动时的UTC时间戳(以及对应的硬件时钟值),所以你拿到的是一个固定的启动时间点,自然是常量。
接下来针对你的需求,我们分两种场景来解决:
场景1:需要系统实际运行时长(休眠时暂停计数),不受系统日期变更影响
这种场景可以利用mach_absolute_time硬件时钟,结合kern.boottime返回的启动时硬件时钟值来计算,完全和系统时间无关:
- 首先定义正确的
kern_boottime结构体(因为它包含两个字段:启动UTC时间和启动时的mach_absolute_time值):
[StructLayout(LayoutKind.Sequential)] private struct KernBootTime { public TimeVal tv; public ulong bootAbsoluteTime; } [StructLayout(LayoutKind.Sequential)] public struct TimeVal { public long tv_sec; public long tv_usec; } [StructLayout(LayoutKind.Sequential)] private struct MachTimebaseInfo { public uint numer; public uint denom; }
- 然后编写方法获取启动时的硬件时钟值,再结合当前硬件时钟计算时长:
[DllImport("libSystem.dylib")] private static extern int sysctlbyname(string name, IntPtr info, ref uint infoLen, IntPtr newInfo, uint newInfoLen); [DllImport("libSystem.dylib")] private static extern ulong mach_absolute_time(); [DllImport("libSystem.dylib")] private static extern int mach_timebase_info(ref MachTimebaseInfo info); public static TimeSpan GetSystemUptimeExcludingSleep() { // 获取kern.boottime结构体的长度 uint infoLen = (uint)Marshal.SizeOf<KernBootTime>(); IntPtr bootTimePtr = Marshal.AllocHGlobal((int)infoLen); try { int result = sysctlbyname("kern.boottime", bootTimePtr, ref infoLen, IntPtr.Zero, 0); if (result != 0) throw new SystemException("Failed to get boot time"); KernBootTime bootTime = Marshal.PtrToStructure<KernBootTime>(bootTimePtr); // 获取当前硬件时钟值 ulong currentAbsoluteTime = mach_absolute_time(); // 计算差值 ulong elapsedAbsolute = currentAbsoluteTime - bootTime.bootAbsoluteTime; // 转换硬件时钟周期为秒数 MachTimebaseInfo timebaseInfo = new MachTimebaseInfo(); mach_timebase_info(ref timebaseInfo); // 计算秒数:(elapsedAbsolute * numer) / denom 得到纳秒数 double elapsedNanoSeconds = (double)elapsedAbsolute * timebaseInfo.numer / timebaseInfo.denom; return TimeSpan.FromNanoseconds(elapsedNanoSeconds); } finally { Marshal.FreeHGlobal(bootTimePtr); } }
这个方法返回的是设备启动后实际运行的时长,设备休眠时会暂停计数,但完全不受系统日期修改的影响。
场景2:需要从重启到现在的实际时间流逝(包含休眠时长),但无法完全避免系统日期变更影响
如果你的需求是不管设备是否休眠,都要计算从重启到现在的实际时间长度,那只能依赖系统的实时时钟——因为只有设备的RTC时钟在休眠时会继续走,但RTC时间是可以被用户修改的,所以无法完全避免系统日期变更的影响。不过我们可以尽量优化:
public static TimeSpan GetTotalElapsedSinceRebootIncludingSleep() { uint infoLen = (uint)Marshal.SizeOf<KernBootTime>(); IntPtr bootTimePtr = Marshal.AllocHGlobal((int)infoLen); try { int result = sysctlbyname("kern.boottime", bootTimePtr, ref infoLen, IntPtr.Zero, 0); if (result != 0) throw new SystemException("Failed to get boot time"); KernBootTime bootTime = Marshal.PtrToStructure<KernBootTime>(bootTimePtr); DateTime bootUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(bootTime.tv.tv_sec).AddMicroseconds(bootTime.tv.tv_usec); // 当前UTC时间减去启动UTC时间 return DateTime.UtcNow - bootUtc; } finally { Marshal.FreeHGlobal(bootTimePtr); } }
这个方法会包含休眠时长,但如果用户修改了系统的UTC时间,结果就会不准确——这是无法避免的,因为iOS没有提供一个既不受系统时间修改影响、又能在休眠时继续计数的时钟API。
总结
- 如果你需要不受系统日期影响的时长,只能选择场景1的方法,但它会在设备休眠时暂停计数;
- 如果你需要包含休眠时长的真实流逝时间,只能选择场景2的方法,但它会受系统日期变更的影响;
- iOS目前没有同时满足两个条件的公开API,你需要根据自己的实际需求做取舍。
内容的提问来源于stack exchange,提问作者Muthukumar
相关产品推荐
相关产品推荐

