文章目录
- 第一种改法
-
- 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域名
<!-- 主 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(可能是几分钟)才会再次尝试

修改代码
- 思路
把所有ntp服务器放在一个可变的列表中,每次轮流查询这个列表,当有一个服务器同步时间成功则返回成功并把它放到列表最前面,这样下次再同步时间时就会优化查询这个成功过的服务器。
另外把SERVERLIST中内置的ntp域名替换成可以ping通的,比如:

- 代码
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();
}
}
- 预期行为
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文章
Android8 Framework实现Ntp服务器多域名轮询同步时间
Android11~13 Framework实现Ntp服务器多域名轮询同步时间
作者:帅得不敢出门 谢绝转载。
