Android 设置/修改系统NTP服务地址
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服务器.
参考
网络时间检测