背景:前段时间要做一个用户行为统计分析的SDK,其中要做到一个功能是,比如用户更改了手机的时间以及年份日期等,那么此时获取的System.currentTimeMillis()就不准确,本人也是通过研究全球各大第三方顶尖统计开源项目+结合优化得出以下方案,无论用户如何更改手机本地的时间或者日期都可以获取当前用户产生的精确时间(已验证)
使用此功能需要了解以下api:

//什么是开机时间?返回自启动以来的毫秒数,包括睡眠时间
long elapsedRealtime = SystemClock.elapsedRealtime();
//什么是ntp网络时间? 是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议。直接copy复制以下NTPClient代码即可。

解决方案:NTP网络协议时间+开机时间(返回自启动以来的毫秒数,包括睡眠时间)

步骤一:新建一个类可以命名为NTPClient.java或其他自定义名称(已验证,可直接copy使用)

public class NTPClient {private static final int ORIGINATE_TIME_OFFSET = 24;private static final int RECEIVE_TIME_OFFSET = 32;private static final int TRANSMIT_TIME_OFFSET = 40;private static final int NTP_PACKET_SIZE = 48;private static final int NTP_PORT = 123;private static final int NTP_MODE_CLIENT = 3;private static final int NTP_VERSION = 3;// Number of seconds between Jan 1, 1900 and Jan 1, 1970// 70 years plus 17 leap daysprivate static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;private long mOffSet;public long getOffset() {return mOffSet;}public boolean requestTime(String host, int timeout) {DatagramSocket socket = null;try {socket = new DatagramSocket();socket.setSoTimeout(timeout);InetAddress address = InetAddress.getByName(host);byte[] buffer = new byte[NTP_PACKET_SIZE];DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);// set mode = 3 (client) and version = 3// mode is in low 3 bits of first byte// version is in bits 3-5 of first bytebuffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);// get current time and write it to the request packetlong requestTime = System.currentTimeMillis();long requestTicks = SystemClock.elapsedRealtime();writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);socket.send(request);// read the responseDatagramPacket response = new DatagramPacket(buffer, buffer.length);socket.receive(response);long responseTicks = SystemClock.elapsedRealtime();long responseTime = requestTime + (responseTicks - requestTicks);// extract the resultslong originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);// receiveTime = originateTime + transit + skew// responseTime = transmitTime + transit - skew// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2// = ((originateTime + transit + skew - originateTime) +// (transmitTime - (transmitTime + transit - skew)))/2// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2// = (transit + skew - transit + skew)/2// = (2 * skew)/2 = skewlong clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;// if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");// if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");mOffSet = clockOffset;} catch (Exception e) {return false;} finally {if (socket != null) {socket.close();}}return true;}/*** Reads an unsigned 32 bit big endian number from the given offset in the buffer.*/private long read32(byte[] buffer, int offset) {byte b0 = buffer[offset];byte b1 = buffer[offset + 1];byte b2 = buffer[offset + 2];byte b3 = buffer[offset + 3];// convert signed bytes to unsigned valuesint i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);return ((long) i0 << 24) + ((long) i1 << 16) + ((long) i2 << 8) + (long) i3;}/*** Reads the NTP time stamp at the given offset in the buffer and returns * it as a system time (milliseconds since January 1, 1970).*/private long readTimeStamp(byte[] buffer, int offset) {long seconds = read32(buffer, offset);long fraction = read32(buffer, offset + 4);return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);}/*** Writes system time (milliseconds since January 1, 1970) as an NTP time stamp * at the given offset in the buffer.*/private void writeTimeStamp(byte[] buffer, int offset, long time) {long seconds = time / 1000L;long milliseconds = time - seconds * 1000L;seconds += OFFSET_1900_TO_1970;// write seconds in big endian formatbuffer[offset++] = (byte) (seconds >> 24);buffer[offset++] = (byte) (seconds >> 16);buffer[offset++] = (byte) (seconds >> 8);buffer[offset++] = (byte) (seconds >> 0);long fraction = milliseconds * 0x100000000L / 1000L;// write fraction in big endian formatbuffer[offset++] = (byte) (fraction >> 24);buffer[offset++] = (byte) (fraction >> 16);buffer[offset++] = (byte) (fraction >> 8);// low order bits should be random databuffer[offset++] = (byte) (Math.random() * 255.0);}
}

步骤二:新建一个工具类TimeUtils ,获取当前校准过后的时间(已验证,可直接copy使用)

public class TimeUtils {private String[] ntpServerPool = {"ntp1.aliyun.com", "ntp.ntsc.ac.cn", "time.google.com", "time.windows.com", "time.nist.gov", "time.apple.com","time1.cloud.tencent.com", "time2.apple.com", "time2.google.com", "3.asia.pool.ntp.org"};//校准时间起始开机时间private long startRealTime;private static TimeUtils instance;//校准后 开始时间private long startTime;//是否校准public boolean isCalibrated = false;public static TimeUtils getInstance() {if (null == instance) {instance = new TimeUtils();}return instance;}/*** 返回自启动以来的毫秒数,包括睡眠时间** @return*/public long getElapsedRealtime() {return SystemClock.elapsedRealtime();}/*** 获取时间** @return*/public Date getDate() {long elapsedRealtime = SystemClock.elapsedRealtime();return startRealTime == 0 ? new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime() + elapsedRealtime): new Date(elapsedRealtime - this.startRealTime + startTime);}/*** 使用ntp网络时间与本地时间进行校准*/public void checkTime() {//异步调用获取ntp协议网络时间new Thread(new Runnable() {@Overridepublic void run() {NTPClient ntpclient = new NTPClient();for (String ntpHost : ntpServerPool) {if (ntpclient.requestTime(ntpHost, 30000)) {long currTime = ntpclient.getNtpTime() + SystemClock.elapsedRealtime() - ntpclient.getNtpTimeReference();long nowTime = currTime / 1000; //当前获取到的准确ntp时间戳(秒单位)startRealTime = SystemClock.elapsedRealtime();startTime = System.currentTimeMillis() + ntpclient.getTimeOffset();isCalibrated = true; //为true代表当前已经拉到网络ntpbreak;}}}}).start();}
}

步骤三:接口调用实现(已验证,可直接copy使用)

 //1、在你初始化或者在你需要初始化获取ntp网络时间的地方调用一次即可,以下举例子:public void init(Context context){TimeUtils.getInstance().checkTime();
}
//2、在你想要获取准确时间处调用以下代码,获取当前已校准的用户时间(实时)
long cunrrent_time = TimeUtils.getInstance().getDate().getTime();//单位为毫秒,如需到秒需除以1000

完结:总之,经过自动化以及各种环境测试,无论用户怎么更改本地时间或者日期更改多往前往后此方案都可以解决。

时间校准(全网最全最准确方案)完美无解相关推荐

  1. 局域网内文件传输速度_跨平台传输文件方案大汇总(中篇)——可能全网最全的传输方案了...

    继续上篇 第二类:利用已安装的常用软件进行跨平台传输文件, 相信大部分人电脑或者手机都会安装聊天软件类:如QQ.微信.钉钉等 还有就是常用的网盘类软件:百度网盘.天翼云盘等 也有不少人使用奶牛快传,类 ...

  2. IntelliJ IDEA 乱码:全网最全 4 种方法完美解决 IntelliJ IDEA 控制台中文乱码问题

    文章目录 前言 一.修改当前 Web 项目 Tomcat Server 的虚拟机输出选项 二.修改 IntelliJ IDEA 全局编码.项目编码.属性文件编码 三.IntelliJ IDEA 中自定 ...

  3. 分辨mqtt在线与离线_最全视频下载方案,100%下载所有在线视频!

    前言 之前跟大家分享过一款视频下载王软件,得到了不少朋友的好评. MeetUp:这款小巧且万能的视频下载.录频.格式转换工具,用了都觉得香!​zhuanlan.zhihu.com 但是也有些朋友反馈有 ...

  4. 【免费下载】全网最全5G资料包(报告、白皮书、方案、政策等1300余份,持续更新)...

    大家好,我是文文(微信:sscbg2020),今天给大家盘点一下今年大热的5G相关的行业报告. 移动通信从面向个人通信的1G.2G.3G.4G以十年一代的速度发展到现在,面向产业互联网和智慧城市应用的 ...

  5. 整理全网最全大屏,可视化大屏,可视化方案,可视化参考,报表,大屏设计,大屏资源,大屏学习,高保真大屏

    整理全网最全大屏设计资源,包括各类智慧大屏,axure高保真大屏原型,大屏设计参考思路,大屏设计可视化图片,如何学习设计大屏等. Gitee仓库地址:https://gitee.com/AiShiYu ...

  6. 全网最全安全加固指南

    干货 | 全网最全安全加固指南 安全加固相关概念阐述 安全加固定义 安全加固和优化是实现信息系统安全的关键环节.通过安全加固,将在信息系统的网络层.主机层.软件层.应用层等层次建立符合安全需求的安全状 ...

  7. 硬核!全网最全Nginx配置指令,建议收藏~

    硬核!全网最全Nginx配置指令,建议收藏~ 1.前言 1.1.Nginx配置文件各个主配置块说明 1.2.Nginx配置符号参考 1.3.本文出现的一些词汇介绍 2.正文 2.1.位于全局块的配置指 ...

  8. 全网最全的ChatGPT提示词

    全网最全最好用的ChatGPT调教指南(prompt) 什么是prompt 提示词(Prompt)是一种向人工智能系统(如ChatGPT等)提供的输入,用于引导和激发AI生成特定的回应或内容.在许多情 ...

  9. 可能是全网最全,JAVA日志框架适配/冲突解决方案,可以早点下班了

    点击关注公众号,Java干货及时送达 你是否遇到过配置了日志,但打印不出来的情况? 你是否遇到过配置了logback,启动时却提示log4j错误的情况?像下面这样: log4j:WARN No app ...

  10. 可能是全网最全的 Java 日志框架适配、冲突解决方案

    作者:空无 juejin.cn/post/6945220055399399455 前言 你是否遇到过配置了日志,但打印不出来的情况? 你是否遇到过配置了logback,启动时却提示log4j错误的情况 ...

最新文章

  1. O(N)的时间复杂度找出a[N]中那个重复的数字
  2. 已解决:Connecting to raw.githubusercontent.com |185.199.109.133|:443... Unable to establish SSL connect
  3. 使用脚本编写 Vim 编辑器,第 4 部分: 字典
  4. Linux下rz命令和sz命令使用方法
  5. ZYNQ7000程序编译成功但烧写报错(使用Vitis2020.2)
  6. Mysql分析排序和锁阅读总结
  7. c++ lambda 重载_您会后悔对Lambdas应用重载!
  8. [原创]将本地代码共享到github的操作步骤
  9. Sinevibes Plugins Bundle for Mac(Sinevibes合成器合集)
  10. 中国省市县地区代码数据库文件
  11. 中国天气网城市编码获取地址
  12. Fantastic-Matplotlib 第一回
  13. link2sd或者app2sd前的分区——SD卡分区教程 link2sd教程 app2sd教程
  14. 论文阅读:GMAN: A Graph Multi-Attention Network for Traffic Prediction
  15. tomcat(非安装版) 服务不能启动但是startup 却可以启动的问题
  16. MDM Apple Configurator使用
  17. Ext4 Project Quota磁盘配额使用介绍
  18. luogu P3398 仓鼠找sugar
  19. 10分钟获得小分子三级结构(.pdb)
  20. java url 设置超时_URLConnection的连接、超时、关闭用法总结

热门文章

  1. Emulex光纤卡lpfc配置文件的修改
  2. 基于Java框架开发OA企业在线办公系统项目教程-附源码-毕业设计
  3. python 邮件抄送是什么意思_python 获取邮件中的发件人From、收件人To、抄送人Cc...
  4. 百度翻译使用经验(Python版)
  5. 贴吧云签到php源码,Tieba-Cloud-Sign: 百度贴吧云签到,在服务器上配置好就无需进行任何操作便可以实现贴吧的全自动签到。配合插件使用还可实现云灌水、点赞、封禁、删帖、审查等功能...
  6. Less入门以及一些前端面试题
  7. eclipse 导入项目后,在工程图标上出现红叉,但是工程中的文件并没有提示错误的解决方法
  8. 2.2析取范式与合取范式
  9. 高数——两个重要极限
  10. Kindle——电子书格式转换(二)