1. 程式人生 > >Android自帶的CountDownTimer倒計時器有時會跳過最後一次onTick()的分析以及解決方案

Android自帶的CountDownTimer倒計時器有時會跳過最後一次onTick()的分析以及解決方案

今天在使用CountDownTimer時發現,最後一次onTick()沒有被執行,翻了一下原始碼:

// handles counting down
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            synchronized (CountDownTimer.this) {
                if (mCancelled) {
                    return;
                }

                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

                if (millisLeft <= 0) {
                    onFinish();
                } else if (millisLeft < mCountdownInterval) {
                    // no tick, just delay until done
                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
                } else {
                    long lastTickStart = SystemClock.elapsedRealtime();
                    onTick(millisLeft);

                    // take into account user's onTick taking time to execute
                    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();

                    // special case: user's onTick took more than interval to
                    // complete, skip to next interval
                    while (delay < 0) delay += mCountdownInterval;

                    sendMessageDelayed(obtainMessage(MSG), delay);
                }
            }
        }
    };
handleMessage()中,對millisLeft(距離倒計時結束的時間)進行了判斷,當距結束的時間小於mCountdownInterval(我們設定的間隔時間)時,就不會執行onTick()而直接延遲到最後去執行onFinish()方法。

是什麼原因導致millisLeft會小於mCountdownInterval呢?我設定的明明都是整數呀。

我們一般計算倒計時剩餘時間是由總倒計時時間減去我們設定的間隔時間得到的,但CountDownTimer是用倒計時結束時刻減去手機當前時刻計算出來的:

mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

用這種方式計算出來的剩餘時間當然會有誤差啦,每次獲取手機當前時刻都會稍微延遲一兩毫秒,所以每次一次執行時都會累積一兩毫秒,這就導致到最後millisLeft小於mCountdownInterval

如果只是用於倒計時功能,對onTick()的執行沒有需求,那大可直接使用;如果有需求,可以對程式碼稍微進行一點改動:

// handles counting down
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            synchronized (FVCountDownTimer.this) {
                if (mCancelled) {
                    return;
                }

                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

                if (millisLeft <= 0) {
                    onFinish();
                } else {// 修改了最後一次提醒時會不執行onTick()的問題
                    long lastTickStart = SystemClock.elapsedRealtime();
                    onTick(millisLeft);

                    if (millisLeft < mCountdownInterval) {
                        sendMessageDelayed(obtainMessage(MSG), millisLeft);
                    } else {
                        // take into account user's onTick taking time to execute
                        long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();

                        // special case: user's onTick took more than interval to
                        // complete, skip to next interval
                        while (delay < 0)
                            delay += mCountdownInterval;

                        sendMessageDelayed(obtainMessage(MSG), delay);
                    }
                }
            }
        }
    };
這樣,最後一次就會執行了。