【NTP】同步设备时间

文章详细阐述了Android系统如何通过NTP协议进行时间更新,主要依赖GPRS和WiFi网络。在NetworkTimeUpdateService中,系统注册广播接收器并监听网络变化,当有可用网络时,通过NtpServer获取时间。如果获取失败,系统会有重试机制。NtpTrustedTime和SntpClient类在其中起到关键作用,负责实际的时间同步操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NTP时间更新主要依赖于GPRS和wifi,即通过网络的方式去获取时间,在NetworkTimeUpdateService中调用onPollNetworkTime访问NtpServer获取网络时间,我们先来看看整体流程
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

/** Initialize the receivers and initiate the first NTP request */
public void systemRunning() {
    registerForAlarms();

    HandlerThread thread = new HandlerThread(TAG);
    thread.start();
    mHandler = new MyHandler(thread.getLooper());
    mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
    mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);

    mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler,
            EVENT_AUTO_TIME_ENABLED);
    mAutoTimeSettingObserver.observe();
}

registerForAlarms中监听"com.android.server.NetworkTimeUpdateService.action.POLL"广播,
//registerForConnectivityIntents监听网络状态改变的广播,
SettingsObserver里监听Settings.Global.AUTO_TIME值的改变

private void registerForAlarms() {
    mContext.registerReceiver(
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
                }
            }, new IntentFilter(ACTION_POLL));
}
private class NetworkTimeUpdateCallback extends NetworkCallback {
    @Override
    public void onAvailable(Network network) {
        Log.d(TAG, String.format("New default network %s; checking time.", network));
        mDefaultNetwork = network;
        // Running on mHandler so invoke directly.
        onPollNetworkTime(EVENT_NETWORK_CHANGED);
    }

    @Override
    public void onLost(Network network) {
        if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;
    }
}

    /** Handler to do the network accesses on */
    private class MyHandler extends Handler {

        MyHandler(Looper l) {
            super(l);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_AUTO_TIME_ENABLED:
                case EVENT_POLL_NETWORK_TIME:
                case EVENT_NETWORK_CHANGED:
                    onPollNetworkTime(msg.what);
                    break;
            }
        }
    }
    
    private void onPollNetworkTime(int event) {
        // If we don't have any default network, don't bother.
        if (mDefaultNetwork == null) return;
        mWakeLock.acquire();
        try {
            onPollNetworkTimeUnderWakeLock(event);
        } finally {
            mWakeLock.release();
        }
    }

   //mTime = NtpTrustedTime.getInstance(context);
   private void onPollNetworkTimeUnderWakeLock(int event) {
        // Force an NTP fix when outdated
        if (mTime.getCacheAge() >= mPollingIntervalMs) {
            if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
            mTime.forceRefresh();
        }

        if (mTime.getCacheAge() < mPollingIntervalMs) {
            // Obtained fresh fix; schedule next normal update
            resetAlarm(mPollingIntervalMs);
            if (isAutomaticTimeRequested()) {
                updateSystemClock(event);
            }

        } else {
            // No fresh fix; schedule retry
            mTryAgainCounter++;
            if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                resetAlarm(mPollingIntervalShorterMs);
            } else {
                // Try much later
                mTryAgainCounter = 0;
                resetAlarm(mPollingIntervalMs);
            }
        }
    }

这个方法中主要调用了TrustedTime实例的forceRefresh方法去获取时间,获取之后通过mTime.currentTimeMillis获得获取成功之后的时间ntp,最后调用 SystemClock.setCurrentTimeMillis(ntp)设置系统时间有几个参数需要特别说下

        //正常的轮询频率
        mPollingIntervalMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpPollingInterval);
        //重试轮询间隔,以防网络请求失败
        mPollingIntervalShorterMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpPollingIntervalShorter);
        //再次尝试次数
        mTryAgainTimesMax = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpRetry);
    <!-- Remote server that can provide NTP responses. -->
    <string translatable="false" name="config_ntpServer">timesync.xxxx.com</string>
    <!-- Normal polling frequency in milliseconds -->
    <integer name="config_ntpPollingInterval">3600000</integer>   
    <!-- Try-again polling interval in milliseconds, in case the network request failed -->
    <integer name="config_ntpPollingIntervalShorter">1000</integer>
    <!-- Number of times to try again with the shorter interval, before backing
         off until the normal polling interval. A value < 0 indicates infinite. -->
    <integer name="config_ntpRetry">-1</integer>
    <!-- If the time difference is greater than this threshold in milliseconds,
         then update the time. -->
    <integer name="config_ntpThreshold">5000</integer>
    <!-- Timeout to wait for NTP server response in milliseconds. -->
    <integer name="config_ntpTimeout">5000</integer>

//frameworks/base/core/java/android/util/NtpTrustedTime.java

    public long getCacheAge() {
        if (mHasCache) {
            return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
        } else {
            return Long.MAX_VALUE;
        }
    }
    
    public boolean forceRefresh() {
        // We can't do this at initialization time: ConnectivityService might not be running yet.
        synchronized (this) {
            if (mCM == null) {
                mCM = sContext.getSystemService(ConnectivityManager.class);
            }
        }

        final Network network = mCM == null ? null : mCM.getActiveNetwork();
        return forceRefresh(network);
    }

    public boolean forceRefresh(Network network) {
        if (TextUtils.isEmpty(mServer)) {
            // missing server, so no trusted time available
            return false;
        }

        // We can't do this at initialization time: ConnectivityService might not be running yet.
        synchronized (this) {
            if (mCM == null) {
                mCM = sContext.getSystemService(ConnectivityManager.class);
            }
        }

        final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network);
        if (ni == null || !ni.isConnected()) {
            if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
            return false;
        }


        if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
        final SntpClient client = new SntpClient();
        if (client.requestTime(mServer, (int) mTimeout, network)) {
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            return false;
        }
    }
    

上面方法中主要是通过SntpClient的requestTime根据传入的mServer获取时间,mServer是在调用NtpTrustedTime的getInstance中初始化的,具体如下

 public static synchronized NtpTrustedTime getInstance(Context context) {
        if (sSingleton == null) {
            final Resources res = context.getResources();
            final ContentResolver resolver = context.getContentResolver();

            final String defaultServer = res.getString(
                    com.android.internal.R.string.config_ntpServer);
            final long defaultTimeout = res.getInteger(
                    com.android.internal.R.integer.config_ntpTimeout);

            final String secureServer = Settings.Global.getString(
                    resolver, Settings.Global.NTP_SERVER);
            final long timeout = Settings.Global.getLong(
                    resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);

            final String server = secureServer != null ? secureServer : defaultServer;
            sSingleton = new NtpTrustedTime(server, timeout);
            sContext = context;
        }

        return sSingleton;
    }

SntpClient主要是提供访问Ntp server的一个类,在requestTime中主要通过DatagramSocket访问传入的server,来获取时间,具体实现如下:
/frameworks/base/core/java/android/net/SntpClient.java

    public boolean requestTime(String host, int timeout, Network network) {
        final Network networkForResolv = network.getPrivateDnsBypassingCopy();
        InetAddress address = null;
        try {
            address = networkForResolv.getByName(host);
        } catch (Exception e) {
            EventLogTags.writeNtpFailure(host, e.toString());
            if (DBG) Log.d(TAG, "request time failed: " + e);
            return false;
        }
        return requestTime(address, NTP_PORT, timeout, networkForResolv);
    }

    public boolean requestTime(InetAddress address, int port, int timeout, Network network) {
        DatagramSocket socket = null;
        final int oldTag = TrafficStats.getAndSetThreadStatsTag(
                TrafficStatsConstants.TAG_SYSTEM_NTP);
        try {
            socket = new DatagramSocket();
            network.bindSocket(socket);
            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;
            EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
            if (DBG) {
                Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
                        "clock offset: " + clockOffset + "ms");
            }//这个log

            // 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) {
            EventLogTags.writeNtpFailure(address.toString(), e.toString());
            if (DBG) Log.d(TAG, "request time failed: " + e);
            return false;
        } finally {
            if (socket != null) {
                socket.close();
            }
            TrafficStats.setThreadStatsTag(oldTag);
        }

        return true;
    }

其实Ntp说的明白点,就是通过网络去获取时间然后更新系统时间,具体流程上面也简单说了下,当遇到手机不能更新时间时,先要看看网络是否可用,网络可用的情况下,就得看看对应的NtpServer是否能够访问。曾经就遇到过移动的数据业务不能访问NtpServer,导致手机不能更新时间。一般可以通过ntp等关键字在log中搜索,一般是SocketException或unknown host的错误。也可以直接adb shell命令进去手机,然后利用ping NtpServer 查看当前服务器是否可访问。在这里需要说的一点是,NtpServer一般都是对应的网址,访问网络时会根据当前的运营商网络,找到对应的IP地址,再去访问,有可能存在同一个NtpServer联通网络可以访问而移动网络不行的情况。

NTP通过访问NtpServer获取网络时间,最后都是通过调用SystemClock.setCurrentTimeMillis更新手机时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值