Android實現一個完美的倒計時功能

一. 已有倒計時方案存在的問題

在開發倒計時功能時往往我們會為瞭方便直接使用CountDownTimer或者使用Handler做延時來實現,當然CountDownTimer內部封裝也是使用的Handler。

如果隻是做次數很少的倒計時或者不需要精確的倒計時邏輯那倒沒關系,比如說我隻要倒計時10秒,或者我大概5分鐘請求某個接口

但是如果是需要做精確的倒計時操作,比如說手機發送驗證碼60秒,那使用現有的倒計時方案就會存在問題。可能有些朋友沒有註意到這一點,下面我們就來簡單分析一下現有倒計時的問題。

1. CountDownTimer

這個可能是用得最多的,因為方便嘛。但其實倒計時每一輪倒計時完之後都是存在誤差的,如果看過CountDownTimer的源碼你就會知道,他的內部是有做校準操作的。(源碼很簡單這裡就不分析瞭)

但是如果你認真的測試過CountDownTimer,你就會發現,即便它內部有做校準操作,他的沒一輪都是有偏差,隻是他最後一次倒計時完之後的總共時間和開始倒計時的時間相比沒偏差。

什麼意思呢,意思就是1秒,2.050秒,3.1秒……,這樣的每輪偏差,導致他會出現10.95秒,下一次12秒的情況,那它的回調中如果你直接做取整就會出現少一秒的情況,但實際是沒少的。

這隻是其中的一個問題,你可以不根據它的回調做展示,自己用一個整形累加做展示也能解決。但是他還有個問題,有概率直接出現跳秒,就是比如3秒,下次直接5秒,這是實際的跳秒,是少瞭一次回調的那種。

跳秒導致你如果直接使用它可能會大問題,你可能自測的時候沒發現,到時一上線應用在用戶那概率跳秒,那就蛋疼瞭。

2. Handler

不搞這麼多花裡胡哨的,直接使用Handler來實現,會有什麼問題。

因為直接使用handler來實現,沒有校準操作,每次循環會出現幾毫秒的誤差,雖然比CountDownTimer的十幾毫秒的誤差要好,但是在基數大的倒計時情況下誤差會累計,導致最終結果和現實時間差幾秒誤差,時間越久,誤差越大

3. Timer

直接使用Timer也一樣,隻不過他每輪的誤差更小,幾輪才有1毫秒的誤差,但是沒有校準還是會出現誤差累計,時間越久誤差越大。

二. 自己封裝倒計時

既然無法直接使用原生的,那我們就自己做一個。

我們基於Handler進行封裝,從上面可以看出主要為瞭解決兩個問題,時間校準和跳秒。自己寫一個CountDownTimer

public class CountDownTimer {

    private int mTimes;
    private int allTimes;
    private final long mCountDownInterval;
    private final Handler mHandler;
    private OnTimerCallBack mCallBack;
    private boolean isStart;
    private long startTime;

    public CountDownTimer(int times, long countDownInterval){
        this.mTimes = times;
        this.mCountDownInterval = countDownInterval;
        mHandler = new Handler();
    }

    public synchronized void start(OnTimerCallBack callBack){
        this.mCallBack = callBack;
        if (isStart || mCountDownInterval <= 0){
            return;
        }

        isStart = true;
        if (callBack != null){
            callBack.onStart();
        }
        startTime = SystemClock.elapsedRealtime();

        if (mTimes <= 0){
            finishCountDown();
            return;
        }
        allTimes = mTimes;

        mHandler.postDelayed(runnable, mCountDownInterval);
    }

    private final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            mTimes--;
            if (mTimes > 0){
                if (mCallBack != null){
                    mCallBack.onTick(mTimes);
                }

                long nowTime = SystemClock.elapsedRealtime();
                long delay = (nowTime - startTime) - (allTimes - mTimes) * mCountDownInterval;
                // 處理跳秒
                while (delay > mCountDownInterval){
                    mTimes --;
                    if (mCallBack != null){
                        mCallBack.onTick(mTimes);
                    }

                    delay -= mCountDownInterval;
                    if (mTimes <= 0){
                        finishCountDown();
                        return;
                    }
                }

                mHandler.postDelayed(this, 1000 - delay);
            }else {
                finishCountDown();
            }
        }
    };

    private void finishCountDown(){
        if (mCallBack != null){
            mCallBack.onFinish();
        }
        isStart = false;
    }

    public void cancel(){
        mHandler.removeCallbacksAndMessages(null);
        isStart = false;
    }

    public interface OnTimerCallBack{

        void onStart();

        void onTick(int times);

        void onFinish();

    }

}

思路就是在倒計時開始前獲取一次SystemClock.elapsedRealtime(),沒輪倒計時再獲取一次SystemClock.elapsedRealtime()相減得到誤差,根據delay校準。然後使用while循壞來處理跳秒的操作,與原生的CountDownTimer不同,這裡如果跳瞭多少秒,就會返回多少次回調。

總結

到此這篇關於利用Android實現一個完美的倒計時功能的文章就介紹到這瞭,更多相關Android倒計時功能內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: