Android 手機的 NTP 時間同步(網絡時間同步)主要依賴網絡,但系統時間來源還包括其他方式,整體時間校準機制是多種來源的結合。具體可分為以下幾類:
1. 網絡 NTP 同步(最主要方式)
這是 Android 設備獲取時間的核心方式,通過訪問 NTP 服務器實現:
- 原理:設備連接 Wi-Fi 或移動數據(蜂窩網絡)時,會定期向預設的 NTP 服務器(如
time.android.com
、廠商自定義服務器等)發送請求,獲取標準時間并校準本地時間。 - 特點:依賴網絡連接,精度通常在毫秒到秒級,是日常使用中最主要的時間來源。
2. GPS/位置服務(輔助時間來源)
GPS 不僅提供位置信息,也會同步時間:
- 原理:GPS 衛星內置高精度原子鐘,設備接收 GPS 信號時,會同時獲取衛星的精確時間(UTC 時間),并結合時區信息轉換為本地時間。
- 特點:
- 精度極高(毫秒級),不受網絡限制,戶外定位時自動同步。
- 通常作為網絡同步的補充,尤其在網絡不穩定或無網絡時提供時間參考。
- 部分設備在開啟“位置服務”后,會優先使用 GPS 時間校準系統時間。
3. 移動網絡(蜂窩網絡)時間
部分運營商的移動網絡(如 4G/5G)會通過信令傳遞時間信息:
- 原理:設備接入蜂窩網絡時,可能從基站獲取時間(類似 NTP 的簡化機制),尤其在早期 2G/3G 網絡中更常見。
- 特點:精度較低(通常秒級),依賴運營商網絡配置,現代設備更多以 NTP 同步為主。
4. 本地保存的時間(離線臨時使用)
設備斷電或重啟時,會依賴內置的 RTC(實時時鐘)芯片維持基本時間:
- 原理:RTC 芯片由設備內置電池供電,即使主電源關閉也能運行,保存最近同步的時間。
- 特點:精度低(可能每天偏差幾秒到幾分鐘),僅作為離線時的臨時時間來源,聯網后會立即通過 NTP 或 GPS 校準。
總的來說, Android 手機的時間來源是多方式協同的:
- 主要來源:網絡 NTP 同步(最常用,依賴網絡)。
- 輔助來源:GPS 時間(高精度,依賴定位信號)、蜂窩網絡時間(運營商提供)。
- fallback 機制:本地 RTC 時鐘(離線時臨時使用)。
系統會根據網絡狀態、定位信號強度等自動選擇最優時間來源,確保時間準確性。例如:聯網時優先用 NTP,戶外定位時結合 GPS 校準,離線時依賴 RTC 并在聯網后修正偏差。
NTP 服務:
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
public NetworkTimeUpdateService(Context context) {mContext = context;mTime = NtpTrustedTime.getInstance(context);mAlarmManager = mContext.getSystemService(AlarmManager.class);mTimeDetector = mContext.getSystemService(TimeDetector.class);mCM = mContext.getSystemService(ConnectivityManager.class);Intent pollIntent = new Intent(ACTION_POLL, null);// Broadcast alarms sent by system are immutablemPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent,PendingIntent.FLAG_IMMUTABLE);mPollingIntervalMs = mContext.getResources().getInteger(com.android.internal.R.integer.config_ntpPollingInterval);mPollingIntervalShorterMs = mContext.getResources().getInteger(com.android.internal.R.integer.config_ntpPollingIntervalShorter);mTryAgainTimesMax = mContext.getResources().getInteger(com.android.internal.R.integer.config_ntpRetry);mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);}private void onPollNetworkTimeUnderWakeLock(int event) {long currentElapsedRealtimeMillis = SystemClock.elapsedRealtime();// Force an NTP fix when outdatedNtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();if (cachedNtpResult == null || cachedNtpResult.getAgeMillis(currentElapsedRealtimeMillis)>= mPollingIntervalMs) {if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");boolean isSuccessful = mTime.forceRefresh();if (isSuccessful) {mTryAgainCounter = 0;} else {String logMsg = "forceRefresh() returned false: cachedNtpResult=" + cachedNtpResult+ ", currentElapsedRealtimeMillis=" + currentElapsedRealtimeMillis;if (DBG) {Log.d(TAG, logMsg);}mLocalLog.log(logMsg);}cachedNtpResult = mTime.getCachedTimeResult();}//....}
frameworks/base/core/java/android/util/NtpTrustedTime.java
@GuardedBy("this")private NtpConnectionInfo getNtpConnectionInfo() {final ContentResolver resolver = mContext.getContentResolver();final Resources res = mContext.getResources();final String hostname;if (mHostnameForTests != null) {hostname = mHostnameForTests;} else {String serverGlobalSetting =Settings.Global.getString(resolver, Settings.Global.NTP_SERVER);if (serverGlobalSetting != null) {hostname = serverGlobalSetting;} else {hostname = res.getString(com.android.internal.R.string.config_ntpServer);}}final Integer port;if (mPortForTests != null) {port = mPortForTests;} else {port = SntpClient.STANDARD_NTP_PORT;}final int timeoutMillis;if (mTimeoutForTests != null) {timeoutMillis = (int) mTimeoutForTests.toMillis();} else {int defaultTimeoutMillis =res.getInteger(com.android.internal.R.integer.config_ntpTimeout);timeoutMillis = Settings.Global.getInt(resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);}return TextUtils.isEmpty(hostname) ? null :new NtpConnectionInfo(hostname, port, timeoutMillis);}@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)public boolean forceRefresh() {synchronized (this) {NtpConnectionInfo connectionInfo = getNtpConnectionInfo();if (connectionInfo == null) {// missing server config, so no NTP time availableif (LOGD) Log.d(TAG, "forceRefresh: invalid server config");return false;}ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();if (connectivityManager == null) {if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");return false;}final Network network = connectivityManager.getActiveNetwork();final NetworkInfo ni = connectivityManager.getNetworkInfo(network);// This connectivity check is to avoid performing a DNS lookup for the time server on a// unconnected network. There are races to obtain time in Android when connectivity// changes, which means that forceRefresh() can be called by various components before// the network is actually available. This led in the past to DNS lookup failures being// cached (~2 seconds) thereby preventing the device successfully making an NTP request// when connectivity had actually been established.// A side effect of check is that tests that run a fake NTP server on the device itself// will only be able to use it if the active network is connected, even though loopback// addresses are actually reachable.if (ni == null || !ni.isConnected()) {if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");return false;}if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");final SntpClient client = new SntpClient();final String serverName = connectionInfo.getServer();final int port = connectionInfo.getPort();final int timeoutMillis = connectionInfo.getTimeoutMillis();if (client.requestTime(serverName, port, timeoutMillis, network)) {long ntpCertainty = client.getRoundTripTime() / 2;mTimeResult = new TimeResult(client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);return true;} else {return false;}}}
系統默認采用android的NTP服務器:
frameworks/base/core/res/res/values/config.xml
<string translatable="false" name="config_ntpServer">2.android.pool.ntp.org</string>
frameworks/base/core/java/android/provider/Settings.java
/** Preferred NTP server. {@hide} */public static final String NTP_SERVER = "ntp_server";
通常, 系統沒有提供可視界面供用戶設置NTP服務器的接口. 可以通過adb
命令來設置:
# 設置NTP服務器
adb shell settings put global "ntp_server" "ntp.aliyun.com"# 關閉 和 打開 自動時間同步
adb shell settings put global "auto_time" 0
adb shell settings put global "auto_time" 1
實際測試發現, AOSP中默認的NTP服務器地址, 經常是訪問不上的, 所以, 可以考慮更換為aliyun的ntp服務器;
測試過程:
- 關閉自動設置時間(auto_time)
- 更改當前系統時間為任意非準確時間
- 關閉網絡/有SIM卡可以拔出來
- 重啟系統, 清除NTP緩存數據, 否則, 系統會從緩存的NTP數據來更新當前系統時間
- 啟動后時間是錯誤的, 打開WIFI, 打開自動設置時間, 正常情況下系統時間成功更新
適用性
- 國內的手機廠商大部分采用的是自定義的NTP服務端
- 某些廠商的設備(比如Oculus Quest 系列)在國內水土不服, 建議修改為國內的服務器
- 某些特定的行業需要自主的NTP服務器.
參考
網絡時間檢測