做android开发有一段时间了,之前一直做服务类的产品,最近公司接手sip话机的项目,里面涉及到修改系统设置的问题,今天就把我在项目中有关NTP服务器同步android系统时间遇到的种种问题,在这做一个总结,希望给后面的人一些帮助,本人也是首次接触这样的项目,总结的如有不完善的地方,希望大神指点出来。。。。
有很多,网可以搜一下
cn.pool.ntp.org
s1a.time.edu.cn 北京邮电大学
s1b.time.edu.cn 清华大学
s1c.time.edu.cn 北京大学
s1d.time.edu.cn 东南大学
s1e.time.edu.cn 清华大学
s2a.time.edu.cn 清华大学
s2b.time.edu.cn 清华大学
中国[China] cn.ntp.org.cn
美国[America] us.ntp.org.cn
韩国[korea] kr.ntp.org.cn
新加坡[Singapore] sgp.ntp.org.cn
由于项目要求,在项目中同步系统时间是每隔多久同步一次系统时间,通过TimerTask来执行任务。通过handler来发送处理消息。相关代码:
/**
*校准时间
* 从ntp服务器中获取时间
* @param ntpHost
* ntp服务器域名地址
* @return 如果失败返回-1,否则返回当前的毫秒数
*/
public static void startCalibrateTime(final String mhostAddress,
final int cycleTime) {
MyLog.d(tag, "startCalibrateTime()");
if (mCycleTimer != null) {
mCycleTask.cancel();
mCycleTimer.cancel();
mCycleTimer.purge();
mCycleTask = null;
mCycleTimer = null;
}
mCycleTask = new TimerTask() {
@Override
public void run() {
MyLog.d(tag, "run()");
long time = getTimeFromNtpServer(mhostAddress);//从获取ntp服务器上获取时间
if (time == -1) {
MyLog.e(tag, "async time failed.");
} else {
SystemClock.setCurrentTimeMillis(time);//设置系统时间
}
if (isTurnToSuccess) {
isTurnToSuccess = false;
mHandler.sendEmptyMessage(MSG_NTP_SEARCH_OK);
}
}
@Override
public boolean cancel() {
MyLog.d(tag, "cancel()");
return super.cancel();
}
};
mCycleTimer = new NgnTimer();
mCycleTimer.schedule(mCycleTask, 0, cycleTime);
MyLog.d(tag, "start ntp timer time:" + cycleTime / 1000);
}
handler处理消息,在不同的消息出处理不同的操作,针对项目要求,我在项目中保存了ntp服务器地址和每次隔多久同步一次的时间,可以自行定义。
涉及到的变量:
private static String mNtpServer = “pool.ntp.org”;
/**
* NTP获取时间失败时,每隔30s周期性重新获取,直至成功,成功后恢复正常计时
*/
private final static int CYCLE_TIME_ERROR = 30000;
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(android.os.Message msg) {
if (msg.what == MSG_NTP_SEARCH_FAILED) {
String mhostAddress = GlobalConfigUtils
.get(ConfTag.DATETIME_SNTP_SERVER);
if (TextUtils.isEmpty(mhostAddress)) {
mhostAddress = mNtpServer;
}
startCalibrateTime(mhostAddress, CYCLE_TIME_ERROR);
} else if (msg.what == MSG_NTP_SEARCH_OK) {
String mhostAddress = GlobalConfigUtils
.get(ConfTag.DATETIME_SNTP_SERVER);//项目中保存的ntp服务器地址
if (TextUtils.isEmpty(mhostAddress)) {
mhostAddress = mNtpServer;
}
String timeStr = GlobalConfigUtils .get(ConfTag.DATETIME_NTP_RESYNC_TIME);//项目中设置的系统默认隔多久同步时间
int time = 168 * 60 * 60 * 1000;
if (!TextUtils.isEmpty(timeStr)
&& TextUtils.isDigitsOnly(timeStr)) {
time = Integer.parseInt(timeStr)*3600*1000;
}
startCalibrateTime(mhostAddress, time);
}
};
};
停止校准时间
public static void stopCalibrateTimer() {
if (mCycleTimer != null) {
mCycleTask.cancel();
mCycleTimer.cancel();
mCycleTimer.purge();
mCycleTask = null;
mCycleTimer = null;
}
}
最重要的部分就是从ntp服务器上获取时间,这块很重要。。重要原因就是NTP工作原理简单分析一下:
下面是NTP工作原理图,图片是从我看的资料上截取过来勿喷。
Device A 与 Device B 通过网络相连,都有自己独立的系统时钟,需要通过ntp实现两个系统时钟的时间同步
1.Device B作为服务器,Device A作为客户端,需要网络使本地时钟与服务器时钟同步,假设同步之前,Device A的时间是10:00:00am,Device B的时间是11:00:00am
2.NTP报文的Device A 和Device B 之间单向传输时间是1秒,
3.Device处理报文时间是1秒
Device A 与Devide B 工作流程如下
获取ntp时间:
public static long getTimeFromNtpServer(String hostAddress) {
MyLog.d(tag, "getTimeFromNtpServer()");
if (TextUtils.isEmpty(hostAddress)) {
MyLog.e(tag, "Ntp host is null.");
return -1;
}
if (mNtpClient == null) {
mNtpClient = new SntpClient();
}
boolean isSuccessful = mNtpClient.requestTime(hostAddress, 20000);
MyLog.d(tag, "requestTime:" + isSuccessful);
if (isSuccessful) {
long now = mNtpClient.getNtpTime();//now就是获取的时间
if (isInErrorCycle) {
if(!isTurnToSuccess){
isTurnToSuccess = true;
}
isInErrorCycle = false;
}
return now;
} else {
if (!isInErrorCycle) {
isInErrorCycle = true;
isTurnToSuccess = false;
mHandler.sendEmptyMessage(MSG_NTP_SEARCH_FAILED);
}
}
return -1;
}
接下来就是封装好的获取时间方法,获取原理如上,接下来就是原理的代码,可以配合原理来理解
public static class SntpClient {
private static final String TAG = "SntpClient";
private static final int REFERENCE_TIME_OFFSET = 16;
private static final int ORIGINATE_TIME_OFFSET = 24;
private static final int RECEIVE_TIME_OFFSET = 32;
private static final int TRANSMIT_TIME_OFFSET = 40;
private static final int NTP_PACKET_SIZE = 48;
private static final int NTP_PORT = 123;
private static final int NTP_MODE_CLIENT = 3;
private static final int NTP_VERSION = 3;
// Number of seconds between Jan 1, 1900 and Jan 1, 1970
// 70 years plus 17 leap days
private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
// system time computed from NTP server response
private long mNtpTime;
// value of SystemClock.elapsedRealtime() corresponding to mNtpTime
private long mNtpTimeReference;
// round trip time in milliseconds
private long mRoundTripTime;
/**
* Sends an SNTP request to the given host and processes the response.
*
* @param host
* host name of the server.
* @param timeout
* network timeout in milliseconds.
* @return true if the transaction was successful.
*/
public boolean requestTime(String host, int timeout) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
InetAddress address = InetAddress.getByName(host);
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer,
buffer.length, address, NTP_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
long requestTime = System.currentTimeMillis();
MyLog.d(TAG, "RequestTime:"+new Date(requestTime));
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);
long responseTicks = SystemClock.elapsedRealtime();
long responseTime = requestTime
+ (responseTicks - requestTicks);
// extract the results
long originateTime = readTimeStamp(buffer,
ORIGINATE_TIME_OFFSET);
long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
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 - requestTime) + (transmitTime - System.currentTimeMillis())) / 2;
// if (false) Log.d(TAG, "round trip: " + roundTripTime +
// " ms");
// if (false) Log.d(TAG, "clock offset: " + clockOffset +
// " ms");
// save our results - use the times on this side of the network
// latency
// (response rather than request time)
mNtpTime = System.currentTimeMillis() + clockOffset;
// mNtpTime = transmitTime;
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
if (false)
Log.d(TAG, "request time failed:" + e);
e.printStackTrace();
return false;
} finally {
if (socket != null) {
socket.close();
}
}
return true;
}
/**
* Returns the time computed from the NTP transaction.
*
* @return time value computed from NTP server response.
*/
public long getNtpTime() {
return mNtpTime;
}
/**
* Returns the reference clock value (value of
* SystemClock.elapsedRealtime()) corresponding to the NTP time.
*
* @return reference clock corresponding to the NTP time.
*/
public long getNtpTimeReference() {
return mNtpTimeReference;
}
/**
* Returns the round trip time of the NTP transaction
*
* @return round trip time in milliseconds.
*/
public long getRoundTripTime() {
return mRoundTripTime;
}
/**
* Reads an unsigned 32 bit big endian number from the given offset in
* the buffer.
*/
private long read32(byte[] buffer, int offset) {
byte b0 = buffer[offset];
byte b1 = buffer[offset + 1];
byte b2 = buffer[offset + 2];
byte b3 = buffer[offset + 3];
// convert signed bytes to unsigned values
int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);
return ((long) i0 << 24) + ((long) i1 << 16) + ((long) i2 << 8)
+ (long) i3;
}
/**
* Reads the NTP time stamp at the given offset in the buffer and
* returns it as a system time (milliseconds since January 1, 1970).
*/
private long readTimeStamp(byte[] buffer, int offset) {
long seconds = read32(buffer, offset);
long fraction = read32(buffer, offset + 4);
return ((seconds - OFFSET_1900_TO_1970) * 1000)
+ ((fraction * 1000L) / 0x100000000L);
}
/**
* Writes system time (milliseconds since January 1, 1970) as an NTP
* time stamp at the given offset in the buffer.
*/
private void writeTimeStamp(byte[] buffer, int offset, long time) {
long seconds = time / 1000L;
long milliseconds = time - seconds * 1000L;
seconds += OFFSET_1900_TO_1970;
// write seconds in big endian format
buffer[offset++] = (byte) (seconds >> 24);
buffer[offset++] = (byte) (seconds >> 16);
buffer[offset++] = (byte) (seconds >> 8);
buffer[offset++] = (byte) (seconds >> 0);
long fraction = milliseconds * 0x100000000L / 1000L;
// write fraction in big endian format
buffer[offset++] = (byte) (fraction >> 24);
buffer[offset++] = (byte) (fraction >> 16);
buffer[offset++] = (byte) (fraction >> 8);
// low order bits should be random data
buffer[offset++] = (byte) (Math.random() * 255.0);
}
}
以上就是从ntp服务器上同步系统时间,项目比较完整,有疑问大家提出来,互相学习,到此结束。