把倒計時做到極致
一、前言
倒計時這種,每秒更新 UI 的需求,應(yīng)該算是比較常見的了。最常見的場景,就是驗證碼發(fā)送超時重試的邏輯,這個邏輯中需要一個倒計時的邏輯去每秒修改 UI ,讓倒計時做到用戶可感知。
那么倒計時的邏輯,需要如何做到***?
一個倒計時,最少要有兩個要求:準、穩(wěn)。
準就是說,一個 2 分鐘的倒計時,就應(yīng)該執(zhí)行兩分鐘,穩(wěn)的意思就是說,每次同步 UI 的更新,都是差不多間隔 1s。
二、實現(xiàn)思路
1、每次延遲 1s 通知 UI 更新
倒計時說白了就是一個間隔固定時間去做一件固定任務(wù),這樣的功能,最簡單的就是使用 Handler.postDelayed() 去間隔執(zhí)行。
那么我們寫一個 CountdownUtils 的類,先看看它的結(jié)構(gòu)。
可以看到,它是基于一個 Handler 來做延遲的。這個邏輯非常的簡單,直接上代碼了。
使用起來也非常的簡單,傳遞進去 2 分鐘的時長。
- new CountdownUtils(120).start()
看看 Log 輸出的結(jié)果,
從 Log 上看,確實是完成了一個倒計時的功能,一秒一秒一直到 0 ,但是這里為了觀察準不準,對倒計時執(zhí)行的完整時間做了一個間隔記錄,看到問題了嗎?一個 120s 的倒計時,卻執(zhí)行了124s 左右。
這個問題實際上是因為 Handler.postDelayed() 的間隔時長,并不是準確的間隔指定的時長,具體什么時候執(zhí)行,實際上是看線程的調(diào)度的。這種總時長差異的問題,換了 Timer 什么的去實現(xiàn)也是無法解決的。
這個問題,在一些驗證碼倒計時的場景下,沒有參照事件點,每個倒計時,誤差幾十毫秒,基本上是用戶無感知的。但是有一些情況下,例如視頻播放的倒計時,這種有參照的情況下,幾分鐘的倒計時,誤差幾秒鐘,就是非常明顯的 Bug 了。
這就是不穩(wěn),那么,如何把倒計時做的穩(wěn)呢?
2、 使用 CountDownTimer
實現(xiàn)一個倒計時, Android 其實是提供了對應(yīng)的支持類的,那就是 CounDownTimer ,它處于 android.os 包下的,完全可以實現(xiàn)一個倒計時的邏輯。
我們先看看它是如何使用的。
CountDownTimer 的使用非常的簡單,在 onTick() 中監(jiān)聽倒計時的變化,結(jié)束的時候會去調(diào)用 onFinish()。
繼續(xù)運行一下看看 Log 的輸出情況。
這個總時長,誤差已經(jīng)是毫秒級的了,看樣子比我們自己實現(xiàn)的好很多。
再仔細看看,onTick() 方法回調(diào)的參數(shù),是一個 毫秒 為單位的數(shù)值,而這個數(shù)值,其實是有誤差的,但是這個其實也不影響,只需要對其進行四舍五入的運算,就可以得到正確的倒計時秒數(shù)。
例如:2830 就是 3s,1828 就是 2s。
但是再仔細看看,就能發(fā)現(xiàn)問題,如果使用這種方式來處理倒計時的話,你會發(fā)現(xiàn),拿不到 1s 的狀態(tài),會直接 3s - 2s - finish,這個問題,從 Log 上也可以反應(yīng)出來。
這就很尷尬了,有沒有參照物,都是一個 Bug,只能先看看 CountDownTimer 的源碼了,它是如何保證總時長的準確的。
從 CountDownTimer 的結(jié)構(gòu)可以看出,它實際上也是使用 mHandler 來做的延遲,繼續(xù)看最重要的 Handler 的實現(xiàn)代碼。
在 handleMessage() 中,用到了一個 SystemClock.elapsedRealtime() ,它實際上獲取到的是一個 系統(tǒng) 啟動之后,到現(xiàn)在的一個絕對時間,包含系統(tǒng)休眠的間隔。
但是,它并不是關(guān)鍵,關(guān)鍵在于,CountDownTimer 會使用這個時間,每次計算出一個相對 1s 間隔的差值,也就是說,每次都去糾正這個誤差值,來保證最終的總時長誤差是毫秒級(其實就是***一次 postDelayed() 的誤差)。
既然找到了 CountDownTimer 保證時間準確行的關(guān)鍵點,那么我們可以改寫***個 Demo 的代碼,來解決沒有 1s 狀態(tài)的問題。
3、 動態(tài)計算 delay 值
沒什么好說的,就是計算此次間隔耗時,然后比 1s 多出來的毫秒值,從下一個 1s 中減去,來糾正間隔時長。
既然實現(xiàn)了之后,我們再看看輸出的 Log。
可以看到,interval 每一次都在動態(tài)的調(diào)整,每一秒的狀態(tài)都會更新出去,并且總時長也保證誤差在毫秒級的,基本上***解決了倒計時的問題了。
三、小結(jié)
一個倒計時,簡簡單單使用 Handler.postDelayed() 也是無法保證準和穩(wěn)的。細節(jié)決定成敗,一個倒計時也是可以做到***的。
【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】