Android8 Framework实现Ntp服务器多域名轮询同步时间

文章目录

  • 第一种改法
    • SntpClient
    • [修改 frameworks/base/core/res/res/values/config.xml](#修改 frameworks/base/core/res/res/values/config.xml)
    • [修改 NtpTrustedTime.java](#修改 NtpTrustedTime.java)
      • [添加成员变量(存储主备 NTP 域名)](#添加成员变量(存储主备 NTP 域名))
      • 修改构造函数
      • [修改 requestTime() 方法(增加多域名重试逻辑)](#修改 requestTime() 方法(增加多域名重试逻辑))
  • 第二种改法
  • [Android Framework文章](#Android Framework文章)

在 Android AOSP(Android Open Source Project)中,默认使用 NetworkTimeUpdateService 来通过 NTP(Network Time Protocol)同步系统时间。其核心实现依赖于 SntpClient、NtpTrustedTime、NetworkTimeUpdateService这三个类。

第一种改法

在Android8.1上修改实测,NTP同步的代码高版本的差异不多,修改方法都是类似的,都是在SntpClient、NtpTrustedTime、NetworkTimeUpdateService这三源码中修改就可。

SntpClient

我们需要增强SntpClient,使其能够按顺序尝试列表中的服务器。

修改位置:frameworks/base/core/java/android/net/SntpClient.java

添加如下函数

java 复制代码
public boolean requestTime(String[] servers, int timeout, @NonNull Network network) {
        // 遍历所有NTP服务器
        for (String server : servers) {
            try {
                Log.d(TAG, "Trying NTP server: " + server);
                if (requestTime(server, timeout, network)) { // 调用原有的单个服务器请求方法
                    Log.d(TAG, "Successfully synced with NTP server: " + server);
                    return true; // 只要有一个成功就返回
                }
            } catch (Exception e) {
                Log.e(TAG, "Request failed for server: " + server, e);
                // 这个服务器失败了,记录日志并继续尝试下一个
            }
        }
        // 所有服务器都尝试失败
        Log.e(TAG, "All NTP servers failed to respond.");
        return false;
    }

    // 保留原有的requestTime方法,用于单个服务器请求
    public boolean requestTime(String host, int timeout, @NonNull Network network) {
        // ... 原有的实现逻辑,建立Socket连接、发送NTP包、解析响应等
    }

新的requestTime方法接受一个服务器数组作为参数。它会遍历这个数组,对每个服务器调用原有的单个服务器请求方法,直到有一个成功为止。

修改 frameworks/base/core/res/res/values/config.xml

添加以下配置项(主域名、备用域名(逗号分隔)、最大重试次数):

列几个国内的ntp域名

腾讯:time1.cloud.tencent.com

阿里:ntp.aliyun.com

复制代码
<!-- 主 NTP 域名(默认 time.android.com,可替换为自定义域名) -->
<string name="config_ntpServer" translatable="false">time.android.com</string>

<!-- 备用 NTP 域名(逗号分隔,可添加多个,如 time1.google.com、cn.pool.ntp.org 等) -->
<string name="config_backupNtpServers" translatable="false">time1.google.com,time2.google.com,cn.pool.ntp.org</string>

<!-- NTP 最大重试次数(主域名+备用域名总次数,建议 3-5 次) -->
<integer name="config_ntpMaxRetries">3</integer>

如果 config.xml 中已有 config_ntpServer,直接修改或补充

部分 AOSP 版本可能已存在 config_ntpServer,只需保留并添加 config_backupNtpServers 和 config_ntpMaxRetries 即可。

修改 NtpTrustedTime.java

NtpTrustedTime.java会调用SntpClient进行时间同步,

复制代码
frameworks/base/core/java/android/util/NtpTrustedTime.java

原生代码如下:

核心就是这几行:

java 复制代码
        final SntpClient client = new SntpClient();
        if (client.requestTime(mServer, (int) mTimeout)) {
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            return false;
        }

添加成员变量(存储主备 NTP 域名)

在 NtpTrustedTime 类中添加以下变量,用于存储主域名、备用域名列表:

java 复制代码
private final String mPrimaryNtpServer; // 主 NTP 域名
private final List<String> mBackupNtpServers; // 备用 NTP 域名列表
private final int mMaxRetries; // 最大重试次数(主域名+备用域名总次数)

修改构造函数

java 复制代码
// 原构造函数(保留,兼容旧逻辑)
public static NtpTrustedTime getInstance(Context context) {
    synchronized (sLock) {
        if (sSingleton == null) {
            final Resources res = context.getResources();
            final ContentResolver resolver = context.getContentResolver();
            
            // 从系统资源或 Settings 中读取主备域名(推荐在资源文件中配置)
            String primaryServer = res.getString(com.android.internal.R.string.config_ntpServer);
            String backupServersStr = res.getString(com.android.internal.R.string.config_backupNtpServers);
            List<String> backupServers = new ArrayList<>();
            if (!TextUtils.isEmpty(backupServersStr)) {
                String[] servers = backupServersStr.split(",");
                backupServers.addAll(Arrays.asList(servers));
            }
            
            // 默认重试次数(主1次 + 备用2次 = 3次,可配置)
            int maxRetries = res.getInteger(com.android.internal.R.integer.config_ntpMaxRetries);
            
            sSingleton = new NtpTrustedTime(primaryServer, backupServers, maxRetries);
        }
        return sSingleton;
    }
}

// 新构造函数(接收主备域名和重试次数)
private NtpTrustedTime(String primaryServer, List<String> backupServers, int maxRetries) {
    mPrimaryNtpServer = primaryServer;
    mBackupNtpServers = backupServers != null ? backupServers : new ArrayList<>();
    mMaxRetries = maxRetries;
    // 其他原有初始化逻辑....
}

修改 requestTime() 方法(增加多域名重试逻辑)

android8中是没有requestTime函数的,我们新增这样一个函数:

java 复制代码
public boolean requestTime() {
    // 合并主域名和备用域名为一个列表(主域名在前,优先尝试)
    List<String> allServers = new ArrayList<>();
    if (!TextUtils.isEmpty(mPrimaryNtpServer)) {
        allServers.add(mPrimaryNtpServer);
    }
    allServers.addAll(mBackupNtpServers);
    
    // 去重(避免配置重复域名)
    Set<String> uniqueServers = new LinkedHashSet<>(allServers);
    allServers = new ArrayList<>(uniqueServers);
    
    // 遍历所有域名,尝试获取时间
    for (int i = 0; i < Math.min(allServers.size(), mMaxRetries); i++) {
        String currentServer = allServers.get(i);
        if (TextUtils.isEmpty(currentServer)) {
            continue;
        }
        
        try {
            // 原有 NTP 请求逻辑(替换原有的固定域名,使用当前循环的域名)
            Log.d(TAG, "Requesting time from server: " + currentServer);
            NtpClient client = new NtpClient();
            client.setServer(currentServer);
            client.setTimeout(mNtpTimeout);
            client.requestTime();
            
            // 请求成功,更新时间和状态
            mTime = client.getNtpTime();
            mElapsedRealtime = client.getNtpTimeReference();
            mCertainty = client.getRoundTripTime() / 2;
            Log.d(TAG, "Successfully got time from " + currentServer + ": " + new Date(mTime));
            return true; // 成功,直接返回
        } catch (Exception e) {
            Log.e(TAG, "Failed to request time from " + currentServer, e);
            // 失败,继续尝试下一个域名
            continue;
        }
    }
    
    // 所有域名尝试失败
    Log.e(TAG, "All NTP servers failed after " + mMaxRetries + " retries");
    return false;
}

然后修改原有forceRefresh代码

java 复制代码
diff --git a/frameworks/base/core/java/android/util/NtpTrustedTime.java b/frameworks/base/core/java/android/util/NtpTrustedTime.java
index f6fd28d604..2b758b287a 100644
--- a/frameworks/base/core/java/android/util/NtpTrustedTime.java
+++ b/frameworks/base/core/java/android/util/NtpTrustedTime.java
@@ -104,8 +104,9 @@ public class NtpTrustedTime implements TrustedTime {


         if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
-        final SntpClient client = new SntpClient();
-        if (client.requestTime(mServer, (int) mTimeout)) {
+        //final SntpClient client = new SntpClient();
+        //if (client.requestTime(mServer, (int) mTimeout)) {
+        if (requestTime()) {
             mHasCache = true;
             mCachedNtpTime = client.getNtpTime();
             mCachedNtpElapsedRealtime = client.getNtpTimeReference();

第二种改法

上面是一种改法,还有另外一种改法是修改

NetworkTimeUpdateService.java类,其本身是支持多NTP域名的,只是有点问题,需要修改下。

原代码

位置:

复制代码
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

原始部分代码

java 复制代码
    //M: For multiple NTP server retry
    private ArrayList<String> mNtpServers = new ArrayList<String>();
    private String mDefaultServer;
    private static final String[] SERVERLIST =  new String[]{
                                            "hshh.org",
                                             "time.apple.com",
                                             "time-a.nist.gov"
                                             };
                                             
                                             ......
                                             
private void onPollNetworkTimeUnderWakeLock(int event) {
.....
int index = mTryAgainCounter % mNtpServers.size();
if (DBG) Log.d(TAG, "mTryAgainCounter = " + mTryAgainCounter
    + ";mNtpServers.size() = " + mNtpServers.size()
    + ";index = " + index + ";mNtpServers = " + mNtpServers.get(index));
if (mTime instanceof NtpTrustedTime)
{
    ((NtpTrustedTime) mTime).setServer(mNtpServers.get(index));
    mTime.forceRefresh();
    ((NtpTrustedTime) mTime).setServer(mDefaultServer);
}
}                                             

问题所在

❌ 每次只尝试一个服务器:通过 mTryAgainCounter % mNtpServers.size() 计算索引,每次同步只尝试列表中的一个服务器

❌ 没有失败重试机制:如果当前选择的服务器失败,不会立即尝试下一个,而是等待下一次轮询

❌ 延迟太高:失败后要等待 mPollingIntervalShorterMs(可能是几分钟)才会再次尝试

修改代码

  1. 思路
    把所有ntp服务器放在一个可变的列表中,每次轮流查询这个列表,当有一个服务器同步时间成功则返回成功并把它放到列表最前面,这样下次再同步时间时就会优化查询这个成功过的服务器。
    另外把SERVERLIST中内置的ntp域名替换成可以ping通的,比如:
  2. 代码
java 复制代码
diff --git a/frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java b/frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
index 9047561996..b9518ff602 100644
--- a/frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -58,6 +58,8 @@ import android.os.SystemProperties;

 //M: For multiple NTP server retry
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;


@@ -127,10 +129,15 @@ public class NetworkTimeUpdateService extends Binder {
     private ArrayList<String> mNtpServers = new ArrayList<String>();
     private String mDefaultServer;
     private static final String[] SERVERLIST =  new String[]{
+                                            "time1.cloud.tencent.com",
                                             "hshh.org",
                                              "time.apple.com",
                                              "time.windows.com"
                                              };
+    // 记录服务器成功时间,用于优化服务器顺序
+    private HashMap<String, Long> mServerSuccessTime = new HashMap<>();
+    private static final long SERVER_PREFERENCE_DURATION = 24 * 60 * 60 * 1000; // 24小时

     public NetworkTimeUpdateService(Context context) {
         mContext = context;
@@ -153,12 +160,18 @@ public class NetworkTimeUpdateService extends Binder {
                 PowerManager.PARTIAL_WAKE_LOCK, TAG);
         //M: For multiple NTP server retry
         mDefaultServer = ((NtpTrustedTime) mTime).getServer();
+
+        // 初始化服务器列表,默认服务器放在最前面
+        mNtpServers.clear();
         mNtpServers.add(mDefaultServer);
-        for (String str : SERVERLIST)
-        {
-           mNtpServers.add(str);
+        for (String server : SERVERLIST) {
+            if (!server.equals(mDefaultServer) && !mNtpServers.contains(server)) {
+                mNtpServers.add(server);
+            }
         }
         mTryAgainCounter = 0;
+
+        if (DBG) Log.d(TAG, "Initialized NTP servers: " + mNtpServers.toString());
     }

     /** Initialize the receivers and initiate the first NTP request */
@@ -241,6 +254,49 @@ public class NetworkTimeUpdateService extends Binder {
         }
     }

+    /**
+     * 优化服务器列表,将成功的服务器移到最前面
+     */
+    private void optimizeServerList(String successfulServer) {
+        if (successfulServer == null || !mNtpServers.contains(successfulServer)) {
+            return;
+        }
+
+        // 记录成功时间
+        mServerSuccessTime.put(successfulServer, System.currentTimeMillis());
+
+        // 如果成功的服务器已经在最前面,不需要调整
+        if (successfulServer.equals(mNtpServers.get(0))) {
+            if (DBG) Log.d(TAG, "Successful server " + successfulServer + " is already first");
+            return;
+        }
+
+        // 将成功的服务器移到最前面
+        mNtpServers.remove(successfulServer);
+        mNtpServers.add(0, successfulServer);
+
+        if (DBG) Log.d(TAG, "Optimized server list, new order: " + mNtpServers.toString());
+    }
+
+    /**
+     * 获取优化后的服务器列表(考虑历史成功记录)
+     */
+    private List<String> getOptimizedServerList() {
+        // 检查是否有最近成功的非首位服务器
+        long currentTime = System.currentTimeMillis();
+        for (int i = 1; i < mNtpServers.size(); i++) {
+            String server = mNtpServers.get(i);
+            Long lastSuccess = mServerSuccessTime.get(server);
+            if (lastSuccess != null &&
+                (currentTime - lastSuccess) < SERVER_PREFERENCE_DURATION) {
+                // 这个服务器在24小时内成功过,可以考虑临时提升优先级
+                if (DBG) Log.d(TAG, "Server " + server + " had recent success, considering promotion");
+            }
+        }
+
+        return new ArrayList<>(mNtpServers); // 返回当前优化后的列表
+    }
+
     private void onPollNetworkTimeUnderWakeLock(int event) {
         final long refTime = SystemClock.elapsedRealtime();
         // If NITZ time was received less than mPollingIntervalMs time ago,
@@ -259,28 +315,60 @@ public class NetworkTimeUpdateService extends Binder {

             // force refresh NTP cache when outdated
             if (mTime.getCacheAge() >= mPollingIntervalMs) {
-                //M: For multiple NTP server retry
-                //mTime.forceRefresh();
-                int index = mTryAgainCounter % mNtpServers.size();
-                if (DBG) Log.d(TAG, "mTryAgainCounter = " + mTryAgainCounter
-                    + ";mNtpServers.size() = " + mNtpServers.size()
-                    + ";index = " + index + ";mNtpServers = " + mNtpServers.get(index));
-                if (mTime instanceof NtpTrustedTime)
-                {
-                    ((NtpTrustedTime) mTime).setServer(mNtpServers.get(index));
-                    mTime.forceRefresh();
+                //M: For multiple NTP server retry with optimization
+                boolean ntpSuccess = false;
+                String successfulServer = null;
+
+                // 获取优化后的服务器列表
+                List<String> serversToTry = getOptimizedServerList();
+
+                if (DBG) Log.d(TAG, "Trying NTP servers in order: " + serversToTry);
+
+                // 遍历所有NTP服务器,直到有一个成功
+                for (String server : serversToTry) {
+                    if (DBG) Log.d(TAG, "Attempting NTP sync with server: " + server);
+
+                    if (mTime instanceof NtpTrustedTime) {
+                        ((NtpTrustedTime) mTime).setServer(server);
+                        if (mTime.forceRefresh()) {
+                            ntpSuccess = true;
+                            successfulServer = server;
+                            if (DBG) Log.d(TAG, "Successfully synced with NTP server: " + server);
+
+                            // 优化服务器列表,将成功的服务器放到最前面
+                            optimizeServerList(successfulServer);
+                            break; // 成功就退出循环
+                        } else {
+                            if (DBG) Log.w(TAG, "Failed to sync with NTP server: " + server);
+                        }
+                    }
+                }
+
+                // 恢复默认服务器配置
+                if (mTime instanceof NtpTrustedTime) {
                     ((NtpTrustedTime) mTime).setServer(mDefaultServer);
                 }
-                else
-                {
-                    mTime.forceRefresh();
+
+                if (ntpSuccess) {
+                    mTryAgainCounter = 0;
+                } else {
+                    // 所有服务器都失败的处理
+                    mTryAgainCounter++;
+                    if (DBG) Log.e(TAG, "All " + serversToTry.size() + " NTP servers failed");
+
+                    if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
+                        resetAlarm(mPollingIntervalShorterMs);
+                    } else {
+                        mTryAgainCounter = 0;
+                        resetAlarm(mPollingIntervalMs);
+                    }
+                    return;
                 }
             }

             // only update when NTP time is fresh
             if (mTime.getCacheAge() < mPollingIntervalMs) {
                 final long ntp = mTime.currentTimeMillis();
-                mTryAgainCounter = 0;
                 // If the clock is more than N seconds off or this is the first time it's been
                 // fetched since boot, set the current time.
                 if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
@@ -425,5 +513,18 @@ public class NetworkTimeUpdateService extends Binder {
         pw.print("LastNtpFetchTime: ");
         TimeUtils.formatDuration(mLastNtpFetchTime, pw);
         pw.println();
+
+        // 添加NTP服务器状态信息
+        pw.println("NTP Servers (optimized order):");
+        for (int i = 0; i < mNtpServers.size(); i++) {
+            String server = mNtpServers.get(i);
+            Long lastSuccess = mServerSuccessTime.get(server);
+            pw.print("  " + i + ": " + server);
+            if (lastSuccess != null) {
+                pw.print(" [last success: " + TimeUtils.logTimeOfDay(lastSuccess) + "]");
+            }
+            pw.println();
+        }
                 }
-                else
-                {
-                    mTime.forceRefresh();
+
+                if (ntpSuccess) {
+                    mTryAgainCounter = 0;
+                } else {
+                    // 所有服务器都失败的处理
+                    mTryAgainCounter++;
+                    if (DBG) Log.e(TAG, "All " + serversToTry.size() + " NTP servers failed");
+
+                    if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
+                        resetAlarm(mPollingIntervalShorterMs);
+                    } else {
+                        mTryAgainCounter = 0;
+                        resetAlarm(mPollingIntervalMs);
+                    }
+                    return;
                 }
             }

             // only update when NTP time is fresh
             if (mTime.getCacheAge() < mPollingIntervalMs) {
                 final long ntp = mTime.currentTimeMillis();
-                mTryAgainCounter = 0;
                 // If the clock is more than N seconds off or this is the first time it's been
                 // fetched since boot, set the current time.
                 if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
@@ -425,5 +513,18 @@ public class NetworkTimeUpdateService extends Binder {
         pw.print("LastNtpFetchTime: ");
         TimeUtils.formatDuration(mLastNtpFetchTime, pw);
         pw.println();
+
+        // 添加NTP服务器状态信息
+        pw.println("NTP Servers (optimized order):");
+        for (int i = 0; i < mNtpServers.size(); i++) {
+            String server = mNtpServers.get(i);
+            Long lastSuccess = mServerSuccessTime.get(server);
+            pw.print("  " + i + ": " + server);
+            if (lastSuccess != null) {
+                pw.print(" [last success: " + TimeUtils.logTimeOfDay(lastSuccess) + "]");
+            }
+            pw.println();
+        }
+        pw.println();
     }
 }
  1. 预期行为
text 复制代码
第一次同步: [google, apple, nist]
→ google失败 → apple成功 → 列表变为: [apple, google, nist]

第二次同步: [apple, google, nist]  
→ apple成功 → 列表保持: [apple, google, nist]

第三次同步: [apple, google, nist]
→ apple失败 → google成功 → 列表变为: [google, apple, nist]

以下是开机后,第一次联网时ntp同步的打印:

实测代码有效果。

Android Framework文章

Android Framework专栏

Android8 Framework实现Ntp服务器多域名轮询同步时间
Android11~13 Framework实现Ntp服务器多域名轮询同步时间

作者:帅得不敢出门 谢绝转载。

相关推荐
走在路上的菜鸟43 分钟前
Android学Dart学习笔记第十一节 错误处理
android·笔记·学习·flutter
阿杰同学1 小时前
Java NIO 面试题及答案整理,最新面试题
java·开发语言·nio
视觉装置在笑7131 小时前
awk 基础知识和进阶用法
linux·运维·服务器·正则表达式
无线图像传输研究探索1 小时前
国标28181平台与TCP对讲:从“不支持”到“实现路径”的完整解析(5G单兵图传、布控球)
运维·服务器·网络·5g·无人机·单兵图传·无人机图传
haiyu_y1 小时前
Day 29 异常处理
python
古城小栈1 小时前
Python 3.14:重塑开发体验的五大技术突破与实践指南
开发语言·python
没有bug.的程序员1 小时前
GC日志解析:从日志看全流程
java·网络·jvm·spring·日志·gc
WZTTMoon1 小时前
开发中反复查的 Spring Boot 注解,一次性整理到位
java·spring boot·后端
一点一木1 小时前
🚀 2025 年 11 月 GitHub 十大热门项目排行榜 🔥
前端·人工智能·github