一、简介
Android设备有两种同步时间的方式,一种,人为手动调节;一种,使用网络提供的时间
注:
源码来自Android 7.1
二、详细介绍
注:本文重点介绍网络同步相关的业务
1.app端
通过设置Settings.Global.AUTO_TIME,即
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME,
isChecked ? 1 : 0);
1 代表开启
0 代表关闭
2.系统服务端–NetworkTimeUpdateService
1)adb 快捷查询方式
adb shell dumpsys network_time_update_service
了解几个关键变量
PollingIntervalMs //拉取网络的间隔时间。默认24小时
PollingIntervalShorterMs //拉取网络失败后,再次拉取的间隔时间。默认1分钟
mTryAgainTimesMax //获取网络失败后,需要再拉取的次数。默认3次
mTimeErrorThresholdMs //纠正时间差。默认误差5秒
mTryAgainCounter //拉取网络失败的重复次数。默认3次
mLastNtpFetchTime //时间同步成功的时间记录。默认-1
了解真正的实现方法
SystemClock.setCurrentTimeMillis(ntp);
注:
调用此方法实现系统时间同步
具体细节:
1)
android.os.SystemClock
public static boolean setCurrentTimeMillis(long millis) {
IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
IAlarmManager mgr = IAlarmManager.Stub.asInterface(b);
if (mgr == null) {
return false;
}
try {
return mgr.setTime(millis);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to set RTC", e);
} catch (SecurityException e) {
Slog.e(TAG, "Unable to set RTC", e);
}
return false;
}
调用AlarmManagerService设置时间:
AlarmManager --> AlarmManagerService
2)
AlarmManagerService中注册服务的地方在onStart方法:
publishBinderService(Context.ALARM_SERVICE, mService);
mService为AlarmManagerService的内部变量
private final IBinder mService = new IAlarmManager.Stub() {
···
@Override
public boolean setTime(long millis) {
getContext().enforceCallingOrSelfPermission(
"android.permission.SET_TIME",
"setTime");
if (mNativeData == 0) {
Slog.w(TAG, "Not setting time since no alarm driver is available.");
return false;
}
synchronized (mLock) {
return setKernelTime(mNativeData, millis) == 0;
}
}
····
}
a.设置时间,第一需要权限:android.permission.SET_TIME
<permission android:name="android.permission.SET_TIME"
android:protectionLevel="signature|privileged" />
//系统签名或者集成在priv-app目录,才能获取此权限
b.setKernelTime 即调用jni设置到kernel
private native int setKernelTime(long nativeData, long millis);
3)
frameworks/base/services/core/jni/
com_android_server_AlarmManagerService.cpp
static jint android_server_AlarmManagerService_setKernelTime(JNIEnv*, jobject, jlong nativeData, jlong millis)
{
AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
struct timeval tv;
int ret;
if (millis <= 0 || millis / 1000LL >= INT_MAX) {
return -1;
}
tv.tv_sec = (time_t) (millis / 1000LL);
tv.tv_usec = (suseconds_t) ((millis % 1000LL) * 1000LL);
ALOGD("Setting time of day to sec=%d
", (int) tv.tv_sec);
ret = impl->setTime(&tv);
if(ret < 0) {
ALOGW("Unable to set rtc to %ld: %s
", tv.tv_sec, strerror(errno));
ret = -1;
}
return ret;
}
a.这里的impl 即为AlarmIplAlarmDriver
b.setTime为关键函数
int AlarmImplAlarmDriver::setTime(struct timeval *tv)
{
struct timespec ts;
int res;
ts.tv_sec = tv->tv_sec;
ts.tv_nsec = tv->tv_usec * 1000;
res = ioctl(fds[0], ANDROID_ALARM_SET_RTC, &ts);
if (res < 0)
ALOGV("ANDROID_ALARM_SET_RTC ioctl failed: %s
", strerror(errno));
return res;
}
2)业务实现细节
a.启动网络时间同步服务
SystemServer
networkTimeUpdater = new NetworkTimeUpdateService(context);
ServiceManager.addService("network_time_update_service", networkTimeUpdater);
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
b.NetworkTimeUpdateService的构造方法
public NetworkTimeUpdateService(Context context) {
//初始化了关键变量
}
c.NetworkTimeUpdateService.systemRunning
public void systemRunning() {
//注册了几个监听事件,广播
//TelephonyIntents.ACTION_NETWORK_SET_TIME
//TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE
//ACTION_POLL
//ConnectivityManager.CONNECTIVITY_ACTION
//启动获取网络的消息列表
//mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
}
d.关键方法分析
onPollNetworkTime-->onPollNetworkTimeUnderWakeLock
注:
前提是自动同步时间打开
private void onPollNetworkTimeUnderWakeLock(int event) {
final long refTime = SystemClock.elapsedRealtime();
// If NITZ time was received less than mPollingIntervalMs time ago,
// no need to sync to NTP.
if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
resetAlarm(mPollingIntervalMs);
return;
}
final long currentTime = System.currentTimeMillis();
if (DBG) Log.d(TAG, "System time = " + currentTime);
// Get the NTP time
if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
|| event == EVENT_AUTO_TIME_CHANGED) {
if (DBG) Log.d(TAG, "Before Ntp fetch");
// force refresh NTP cache when outdated
if (mTime.getCacheAge() >= mPollingIntervalMs) {
mTime.forceRefresh();
}
// 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
|| mLastNtpFetchTime == NOT_SET) {
// Set the system time
if (DBG && mLastNtpFetchTime == NOT_SET
&& Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
Log.d(TAG, "For initial setup, rtc = " + currentTime);
}
if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
// Make sure we don t overflow, since it s going to be converted to an int
if (ntp / 1000 < Integer.MAX_VALUE) {
SystemClock.setCurrentTimeMillis(ntp);
}
} else {
if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
}
mLastNtpFetchTime = SystemClock.elapsedRealtime();
} else {
// Try again shortly
mTryAgainCounter++;
if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
resetAlarm(mPollingIntervalShorterMs);
} else {
// Try much later
mTryAgainCounter = 0;
resetAlarm(mPollingIntervalMs);
}
return;
}
}
resetAlarm(mPollingIntervalMs);
}
//触发调节为对应的广播,例如网络变化的广播android.net.conn.CONNECTIVITY_CHANGE
//默认,如果3次失败(每次1分钟),依旧无法获取网络时间。则等待24小时再重新再试
知识补充:
知识点一、
DatagramSocket
DatagramPacket
DatagramSocket socket = null;
这种socket通信SNTP协议 也不错
public boolean requestTime(InetAddress address, int port, int timeout) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, port);
// set mode = 3 (client) and version = 3
// mode is in low 3 bits of first byte
// version is in bits 3-5 of first byte
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
// get current time and write it to the request packet
final long requestTime = System.currentTimeMillis();
final long requestTicks = SystemClock.elapsedRealtime();
writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
socket.send(request);
// read the response
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
final long responseTicks = SystemClock.elapsedRealtime();
final long responseTime = requestTime + (responseTicks - requestTicks);
// extract the results
final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
final byte mode = (byte) (buffer[0] & 0x7);
final int stratum = (int) (buffer[1] & 0xff);
final long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
final long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
final long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
/* do sanity check according to RFC */
// TODO: validate originateTime == requestTime.
checkValidServerReply(leap, mode, stratum, transmitTime);
long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
// receiveTime = originateTime + transit + skew
// responseTime = transmitTime + transit - skew
// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
// = ((originateTime + transit + skew - originateTime) +
// (transmitTime - (transmitTime + transit - skew)))/2
// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
if (DBG) {
Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
"clock offset: " + clockOffset + "ms");
}
// save our results - use the times on this side of the network latency
// (response rather than request time)
mNtpTime = responseTime + clockOffset;
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
if (socket != null) {
socket.close();
}
}
return true;
}
知识点二
System.currentTimeMillis
SystemClock.elapseRealtime
开机的时间从哪里获取,怎么出来?
区别:System.currentTimeMillis()获取的是系统的时间,可以被修改。
SystemClock.elapsedRealtime()获取的是系统开机到目前的时间,不能被修改。
使用:System.currentTimeMillis()用于和日期相关的地方,列如日志。
SystemClock.elapsedRealtime()用于某个事件经历的时间,列如两次点击的时间间隔。
其他
SystemClock.uptimeMillis() 从开机到目前的毫秒数(手机睡眠的时间不包括在内)
参考学习
config_ntpServer
https://www.jianshu.com/p/ca10702401ba
https://blog.csdn.net/P20914065/article/details/83820537 https://www.jianshu.com/p/3f2f8bf52b3d
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END
暂无评论内容