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 immutable
        mPendingPollIntent = 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 outdated
        NtpTrustedTime.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 available
                if (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服务器;
测试过程:

  1. 关闭自动设置时间(auto_time)
  2. 更改当前系统时间为任意非准确时间
  3. 关闭网络/有SIM卡可以拔出来
  4. 重启系统, 清除NTP缓存数据, 否则, 系统会从缓存的NTP数据来更新当前系统时间
  5. 启动后时间是错误的, 打开WIFI, 打开自动设置时间, 正常情况下系统时间成功更新

适用性

  1. 国内的手机厂商大部分采用的是自定义的NTP服务端
  2. 某些厂商的设备(比如Oculus Quest 系列)在国内水土不服, 建议修改为国内的服务器
  3. 某些特定的行业需要自主的NTP服务器.

参考

网络时间检测
在这里插入图片描述

### 配置 Android 14 AOSP 的多个 NTP 地址Android 14 AOSP 中,可以通过修改 `config_ntpServer` 和其他相关文件来支持多个 NTP 服务地址。以下是具体方法: #### 修改 `config.xml` 文件 在 AOSP 源码中,NTP 服务器的配置位于以下路径: ```plaintext frameworks/base/core/res/res/values/config.xml ``` 找到 `<string>` 标签定义的 `config_ntpServer` 属性,并将其更改为逗号分隔的多个 NTP 服务器列表。例如: ```xml <string translatable="false" name="config_ntpServer">ntp1.example.com,ntp2.example.com,ntp3.example.com</string> ``` 此更改允许系统轮询这些 NTP 服务器以实现更高的可靠性和准确性。 #### 更新 Time Zone 数据库 如果需要进一步调整时区设置,可以参考 Android 提供的时间服务框架[^1]。通过更新系统的 IANA 时间数据库(tzdata),确保设备能够正确解析来自不同 NTP 服务器的时间戳。 #### 实现多级 Stratum 支持 为了提高时间同步精度并遵循 NTP 协议标准[^2],可以在本地网络环境中部署自己的 NTP 服务器作为 Stratum 1 或更高层的服务节点。这有助于减少对外部公共 NTP 服务器的依赖,同时提升内部设备间的时间一致性。 #### 替换默认 NTP 客户端逻辑 对于自定义需求较高的场景,还可以替换或扩展 Android 默认的 NTP 客户端实现。原生客户端源码位置如下: ```plaintext packages/modules/Permission/client/src/com/android/server/net/NtpTrustedTime.java ``` 在此基础上增加对多个备用服务器的支持以及错误重试机制等功能模块开发。 #### 示例代码片段 下面是一个简单的 Java 方法用于测试指定的一组 NTP 服务器是否可用: ```java import java.net.InetAddress; import java.util.Arrays; public class NtpTest { public static void main(String[] args) throws Exception { String[] servers = {"ntp1.example.com", "ntp2.example.com"}; Arrays.stream(servers).forEach(server -> { try { InetAddress address = InetAddress.getByName(server); System.out.println("Resolved: " + server + " to " + address.getHostAddress()); } catch (Exception e) { System.err.println("Failed resolving: " + server); } }); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值