Android11 使用NTP同步时间
Android11 使用NTP同步時間
文章目錄
- Android11 使用NTP同步時間
- 一、前言
- 二、使用阿里云代碼獲取最新時間
- 三、Android 系統NTP server分析
- 1、分析ntp網址的獲取
- 2、定義默認ntp網址的文件
- 3、監聽時間同步開關并進行時間設置管理Service
- (1)自動同步事件監聽
- (2)網絡變化監聽
- (3)handler處理
- (4)onPollNetworkTime 方法
- (5)根據需求獲取到最新時間后的具體操作
- 四、之前寫的一個同步時間Demo
- 五、總結一下 Android ntp時間同步
- 六、NTP相關問題解決思路
- 1、設備聯網后點擊"同步時間",但是時間未同步
- 2、同一個軟件在不同的設備上的能同步時間,有的設備不能同步時間
- 共勉:曾經錯過的東西,不一定能追回到最初,但是能盡力挽回,也不枉初心。
一、前言
前段時間遇到同樣的版本和相關的產線的產品,存在有的設備可以同步時間,有的設備無法同步時間問題。
存在問題的設備不僅無法同步時間,而且無法使用瀏覽器搜索百度,但是可以ping同百度,
報錯:system.err: Caused by: java.security.cert.CertPathValidatorException: timestamp check failed;
意外的是修改到當天的日期時間后是可以正常同步時間和正常瀏覽網頁。
按照網上的做法:跳過SSL/TLS證書驗證后,是可以正常同步時間的。
具體原因可能是:原生默認服務器 國內網絡可能無法訪問,可以用下阿里云的公網NTP獲取時間。
跳過證書驗證畢竟不是安全的行為,能直接訪問是最好的。
在有問題的設備試了下,確實是阿里云的NTP網址是可以獲取時間的,www.baidu.com是不行的。
并且測試了使用百度網址,修改到最新時間前3個月內和后面8個月內是可以獲取到最新時間的。估計是百度服務器校驗相關。
阿里云NTP網址(公網支持國內國外):
https://help.aliyun.com/document_detail/92704.html
如果需要研究系統NTP直接查看第三點的內容即可。
二、使用阿里云代碼獲取最新時間
代碼參考:
https://blog.csdn.net/huang_cai_yuan/article/details/51689907
MainActivity.java
調用startCalibrateTime方法,創建一個線程獲取時間,并且進行時間設置。
private final int NTP_TIME_OUT_MILLISECOND = 3000;private final int NTP_TIMES = 20;private boolean isStopCalibrate = false;/*** ntp服務器地址集*/private String[] ntpServerHost = new String[]{"ntp.aliyun.com","ntp1.aliyun.com","223.6.6.6","www.alidns.com"};/*** 開始校準時間*/public void startCalibrateTime() {new Thread() {@Overridepublic void run() {int curTryTime = 0;while (!isStopCalibrate) {for (int i = 0; i < ntpServerHost.length; i++) {long time = getTimeFromNtpServer(ntpServerHost[i]);SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd.HHmmss");String datetime = df.format(time);LogUtil.debug("i = " + i + ",time = " + time + ",datetime = " + datetime);if (time != -1) {int tryCount = 3;while (tryCount > 0) {tryCount--;boolean isSetTimeSuccessful = setSysDateAndTime(MainActivity.this, time);curTryTime++;if (isSetTimeSuccessful) {tryCount = 0;isStopCalibrate = true;LogUtil.inform("set time successful");} else {LogUtil.inform("set time failure");}}break;}}if (curTryTime >= NTP_TIMES) {isStopCalibrate = true;break;}}}}.start();}/*** 從ntp服務器中獲取時間** @param ntpHost ntp服務器域名地址* @return 如果失敗返回-1,否則返回當前的毫秒數*/private long getTimeFromNtpServer(String ntpHost) {LogUtil.inform("get time from " + ntpHost);NtpClient client = new NtpClient();boolean isSuccessful = client.requestTime(ntpHost, NTP_TIME_OUT_MILLISECOND);if (isSuccessful) {return client.getNtpTime();}return -1;}//設置系統日期public boolean setSysDateAndTime(Context context, long time) {if (time / 1000 < Integer.MAX_VALUE) {((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(time);}long timeLong = System.currentTimeMillis();LogUtil.debug("time = " + time + ",timeLong = " + timeLong);if (time - timeLong < 2000) {return true;} else {return false;}}網絡請求對象NtpClient :
package com.liwenzhi.synctimedemo;import android.os.SystemClock;import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress;/*** ntp 網絡請求數據對象*/ public class NtpClient {private static final int REFERENCE_TIME_OFFSET = 16;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;// system time computed from NTP server responseprivate long mNtpTime;// value of SystemClock.elapsedRealtime() corresponding to mNtpTimeprivate long mNtpTimeReference;// round trip time in millisecondsprivate long mRoundTripTime;/*** Sends an SNTP request to the given host and processes the response.** @param host host name of the server.* @param timeout network timeout in milliseconds.* @return true if the transaction was successful.*/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);buffer[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);long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;mNtpTime = responseTime + clockOffset;mNtpTimeReference = responseTicks;mRoundTripTime = roundTripTime;} catch (Exception e) {LogUtil.error("", e);return false;} finally {if (socket != null) {socket.close();}}return true;}public long getNtpTime() {return mNtpTime;}public long getNtpTimeReference() {return mNtpTimeReference;}public long getRoundTripTime() {return mRoundTripTime;}/*** 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);} }其他參考:
android設置系統時區和時間
https://www.csdn.net/tags/OtTaMgzsMjM2MS1ibG9n.html
Android App 設置系統時間,語言和時區、系統重啟
https://blog.csdn.net/jdfkldjlkjdl/article/details/121020649
時區:
https://www.cnblogs.com/minimeta/p/16555015.html
ntp的配置:
https://blog.csdn.net/LoongEmbedded/article/details/111185359
后面研究發現,NtpClient的代碼,在系統Framework中是的。
所以說系統是會自動同步時間的。但是為啥點擊自動同步不能自動更新時間呢?下面是簡單分析。
三、Android 系統NTP server分析
下面是簡單的研究分析和涉及文件:
1、獲取ntpServer網址類 frameworks\base\core\java\android\util\NtpTrustedTime.java 2、ntp默認網址配置 frameworks\base\core\res\res\values\config.xml 3、監聽時間同步開關并進行時間設置管理 frameworks\base\services\core\java\com\android\server\NetworkTimeUpdateService.java1、分析ntp網址的獲取
NtpTrustedTime.java
//獲取最新時間方法public boolean forceRefresh() {synchronized (this) {NtpConnectionInfo connectionInfo = getNtpConnectionInfo();//。。。網絡等判斷空情況final SntpClient client = new SntpClient();final String serverName = connectionInfo.getServer();final int timeoutMillis = connectionInfo.getTimeoutMillis();//(1)使用SntpClient 對象請求ntp網絡獲取時間if (client.requestTime(serverName, timeoutMillis, network)) {long ntpCertainty = client.getRoundTripTime() / 2;//(2)獲取網絡時間mTimeResult = new TimeResult(client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);return true;} else {return false;}}}//獲取ntp網址的方法private NtpConnectionInfo getNtpConnectionInfo() {final ContentResolver resolver = mContext.getContentResolver();final Resources res = mContext.getResources();//(1)從config配置中獲取ntpServer網址配置final String defaultServer = res.getString(com.android.internal.R.string.config_ntpServer);//(2)從config配置中獲取ntpTimeout網址連接超時時間長短配置final int defaultTimeoutMillis = res.getInteger(com.android.internal.R.integer.config_ntpTimeout);//(3)從Settings變量中獲取NTP_SERVER網址配置final String secureServer = Settings.Global.getString(resolver, Settings.Global.NTP_SERVER);//(4)從Settings變量中獲取ntpTimeout網址連接超時時間長短配置final int timeoutMillis = Settings.Global.getInt(resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);//(5)最后判斷如果Settings中未配置,則從config默認配置中獲取final String server = secureServer != null ? secureServer : defaultServer;return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);}所以ntp網址的獲取的次序就是:
優先判斷是否已在Settings.Global.NTP_SERVER變量中設置,如果沒有就從config中獲取。
2、定義默認ntp網址的文件
config.xml
//(1)默認ntp網址,可以換成國家ntp網址:us.pool.ntp.org,獲取阿里云公網:ntp.aliyun.com<!-- Remote server that can provide NTP responses. old ntp:time.android.com --><string translatable="false" name="config_ntpServer">us.pool.ntp.org</string>//(2)ntp網址連接超時時間<!-- Try-again polling interval in milliseconds, in case the network request failed --><integer name="config_ntpPollingIntervalShorter">60000</integer>嘗試ping time.android.com 確實是ping不通的。
使用公網ntp網絡us.pool.ntp.org/阿里云ntp網絡ntp.aliyun.com是可以ping通的。
所以這就是問題的根本原因。
所以解決這個問題只需要:
3、監聽時間同步開關并進行時間設置管理Service
下面是 NetworkTimeUpdateService.java 的代碼簡單分析
(1)自動同步事件監聽
//下面這段代碼可以看到這里private static class AutoTimeSettingObserver extends ContentObserver {void observe() {ContentResolver resolver = mContext.getContentResolver();resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),false, this);}@Overridepublic void onChange(boolean selfChange) {if (isAutomaticTimeEnabled()) {mHandler.obtainMessage(mMsg).sendToTarget();}}/*** Checks if the user prefers to automatically set the time.*/private boolean isAutomaticTimeEnabled() {ContentResolver resolver = mContext.getContentResolver();return Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) != 0;}}從上面代碼可以看出Service中是有對Settings.Global.AUTO_TIME屬性進行監聽的,
并且監聽到打開狀態會做相應的操作,比如從網絡同步時間。
(2)網絡變化監聽
private class NetworkTimeUpdateCallback extends NetworkCallback {@Overridepublic void onAvailable(Network network) {Log.d(TAG, String.format("New default network %s; checking time.", network));mDefaultNetwork = network;// Running on mHandler so invoke directly.onPollNetworkTime(EVENT_NETWORK_CHANGED);}@Overridepublic void onLost(Network network) {if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;}}這里可以看到是判斷網絡變化為可用onAvailable時會進行對應處理。
具體監聽都是在Service創建的時候監聽,這里不進行展開描述。
(3)handler處理
private static final int EVENT_AUTO_TIME_ENABLED = 1; //監聽設置同步時間private static final int EVENT_POLL_NETWORK_TIME = 2; //系統啟動首次初始化private static final int EVENT_NETWORK_CHANGED = 3; //監聽網絡改變private class MyHandler extends Handler {MyHandler(Looper l) {super(l);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case EVENT_AUTO_TIME_ENABLED:case EVENT_POLL_NETWORK_TIME:case EVENT_NETWORK_CHANGED:onPollNetworkTime(msg.what);break;}}}其實不管是什么消息,這里都是進行調用了更新時間的方法onPollNetworkTime。
(4)onPollNetworkTime 方法
下面簡單跟一下 onPollNetworkTime 方法流程
private void onPollNetworkTime(int event) {// If we don't have any default network, don't bother.if (mDefaultNetwork == null) return;mWakeLock.acquire();try {//關鍵流程(1)onPollNetworkTimeUnderWakeLock(event);} finally {mWakeLock.release();}}//關鍵流程 <1> 方法private void onPollNetworkTimeUnderWakeLock(int event) {// Force an NTP fix when outdatedNtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) {if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");mTime.forceRefresh();//關鍵流程 <2> 獲取到網絡時間cachedNtpResult = mTime.getCachedTimeResult();}if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) {// Obtained fresh fix; schedule next normal update//關鍵流程<3>真正就行時間同步resetAlarm(mPollingIntervalMs);//同步鬧鐘時間,不知道會不會同步系統數據!//自己添加的同步時間和同步RTCif (isAutomaticTimeRequested()) {updateSystemClock(event); }}。。。//有多次重連機制,不進行展開描述!}而關鍵流程<2>又回到了最開始的時候的NtpTrustedTime.java ntp獲取網絡時間的邏輯。
所以上面就是從各個狀態到獲取網絡時間的完整流程。
(5)根據需求獲取到最新時間后的具體操作
private void updateSystemClock(int event) {//判斷是否勾選了"同步時間",觸發的時間更新//如果不是"同步時間"觸發的,判斷時間差距大的就同步一下,否則不用管(其實沒啥用)final boolean forceUpdate = (event == EVENT_AUTO_TIME_ENABLED);if (!forceUpdate) {if (getNitzAge() < mPollingIntervalMs) {if (DBG) Log.d(TAG, "Ignoring NTP update due to recent NITZ");return;}final long skew = Math.abs(mTime.currentTimeMillis() - System.currentTimeMillis());if (skew < mTimeErrorThresholdMs) {if (DBG) Log.d(TAG, "Ignoring NTP update due to low skew");return;}}SystemClock.setCurrentTimeMillis(mTime.currentTimeMillis());//關鍵流程 同步系統時間//這是自己加的代碼方法,一般系統需要同步設置RTC時間,可以在這里進行同步updateRtcTime();}執行了SystemClock.setCurrentTimeMillis 方法后,后面各應用獲取時間就是設置的時間了。
系統有啥獲取時間的方法:
//1、Calendar獲取時間值Calendar calendar = Calendar.getInstance();int year = calendar.get(Calendar.YEAR);int month = calendar.get(Calendar.MONTH);int day = calendar.get(Calendar.DAY_OF_MONTH);int hour = calendar.get(Calendar.HOUR_OF_DAY);int minute = calendar.get(Calendar.MINUTE);int sec = calendar.get(Calendar.SECOND);//2、System.currentTimeMillis()獲取系統時間搓RTC是啥?RTC是硬件芯片記錄時間的程序。
RTC 官方解釋:
Real-time clock,中文名稱:實時時鐘,是指可以像時鐘一様輸出實際時間的電子設備,
一般會是集成電路,因此也稱為時鐘芯片。
網上 NetworkTimeUpdateService 相關分析:
https://www.cnblogs.com/zhangming-blog/articles/6215312.html
四、之前寫的一個同步時間Demo
之前以為要ntp時間同步自己實現,后面發現系統已經存在,所以這個demo沒有太多的使用價值。
還是有一定的分析和研究價值的demo代碼,供大家參考。
界面效果:
這里是在模擬器上運行,所以第一點ping 百度網站沒返回數據,真機聯網是會返回數據的。
包含內容:
1、判斷網絡是否可用 2、ping www.baidu.com網絡并返回網絡情況 3、使用最原始的URLConnection方式情況網絡獲取百度網址的時間 4、參考系統NTP源碼使用DatagramSocket獲取網絡時間 5、查看獲取網絡時間需要的時間demo代碼:
https://download.csdn.net/download/wenzhi20102321/86806264
五、總結一下 Android ntp時間同步
Android 系統是有ntp相關服務 NetworkTimeUpdateService。
但是 NetworkTimeUpdateService 并不是單純的Service,
而是一個繼承了extends Binder的BinderService,具體怎么使用不展開描述。
這個服務在SystemServer起來的時候,
伴隨OtherService那些服務一起起來,
并且后續在相關時機會做一些同步時間。
其實NetworkTimeUpdateService的代碼并不是很完善的,
比如同步SystemClock和硬件RTC時間都是要自己添加代碼實現的。
如果不想在Framework的Service中操作這些時間同步,
在自己的應用中創建Service也是可以進行同步時間的操作,
需要的廣播,狀態監聽等可以參考NetworkTimeUpdateService的實現。
六、NTP相關問題解決思路
1、設備聯網后點擊"同步時間",但是時間未同步
(1)判斷設備是否聯網成功,直接ping網址或者使用里面瀏覽器瀏覽網頁查看 (2)判斷ntp網址是否有效 手動設置一個能正常ping通的ntp網址 settings put global ntp_server us.pool.ntp.org //設置ntp網址 settings get global ntp_server //查詢ntp網址,默認為null 需要重啟后ntpService才會使用新網址請求時間。2、同一個軟件在不同的設備上的能同步時間,有的設備不能同步時間
這個問題之前還報了原廠,后面發現是未使用ntp網址的導致,
之前在應用中使用百度網址獲取網上時間,
需要把系統時間修改到最近時間才能請求網絡獲取到到數據,這個估計和百度服務器校驗相關
能夠成功是有時間范圍要求的,往前早三個月內,和網后10個月內,都是可以獲取到最新時間的,
有效時間范圍外會有CA證書校驗失敗的錯誤:
System.err: Caused by: java.security.cert.CertPathValidatorException: timestamp check failed
無法獲取到最新時間。
所以使用ntp網址更新時間時沒有這個問題的。
共勉:曾經錯過的東西,不一定能追回到最初,但是能盡力挽回,也不枉初心。
總結
以上是生活随笔為你收集整理的Android11 使用NTP同步时间的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最近很火的养猫小程序—365赚钱宝小程序
- 下一篇: Android 播放器 mov,Andr