Android 网络时间同步-7.1

一、简介
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
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容