【從 0 開(kāi)始開(kāi)發(fā)一款直播 APP】7 倒計(jì)時(shí)器 CountDownTimer 源碼解析

本文為菜鳥(niǎo)窩作者蔣志碧的連載良漱∥枋“從 0 開(kāi)始開(kāi)發(fā)一款直播 APP ”系列來(lái)聊聊時(shí)下最火的直播 APP,如何完整的實(shí)現(xiàn)一個(gè)類(lèi)"騰訊直播"的商業(yè)化項(xiàng)目
視頻地址:http://www.cniao5.com/course/10121


倒計(jì)時(shí)功能常見(jiàn)應(yīng)用場(chǎng)景:手機(jī)發(fā)送驗(yàn)證碼

以前寫(xiě)定時(shí)器的常用做法:Handler + Timer + TimeTask母市,這個(gè)方法可以?huà)仐壚卜担琣ndroid 為我們封裝好了一個(gè) CountDownTimer 類(lèi)。

先來(lái)看看一個(gè) Demo患久,倒計(jì)時(shí)器的應(yīng)用椅寺。


一、倒計(jì)時(shí)器的使用

if (null != mLoginView){
   mLoginView.verifyCodeSuccess(60,60);
}

@Override
public void verifyCodeSuccess(int reaskDuration, int expireDuration) {
    showMsg("注冊(cè)短信下發(fā)蒋失,驗(yàn)證碼 " + expireDuration / 60 + " 分鐘內(nèi)有效返帕!");
    OtherUtils.startTimer(new WeakReference<TextView>(tvVerifyCode), "驗(yàn)證碼", reaskDuration, 1);
}

/**
  * 在按鈕上啟動(dòng)一個(gè)定時(shí)器
  * @param tvVerifyCode  驗(yàn)證碼控件
  * @param defaultString 按鈕上默認(rèn)的字符串
  * @param max           失效時(shí)間(單位:s)
  * @param interval      更新間隔(單位:s)
  */
public static void startTimer(final WeakReference<TextView> tvVerifyCode,
                                  final String defaultString,
                                  int max,
                                  int interval) {
    tvVerifyCode.get().setEnabled(false);
    // 由于CountDownTimer并不是準(zhǔn)確計(jì)時(shí),在onTick方法調(diào)用的時(shí)候篙挽,time會(huì)有1-10ms左右的誤差荆萤,這會(huì)導(dǎo)致最后一秒不會(huì)調(diào)用onTick()
    // 因此,設(shè)置間隔的時(shí)候嫉髓,默認(rèn)減去了10ms观腊,從而減去誤差。
    // 經(jīng)過(guò)以上的微調(diào)算行,最后一秒的顯示時(shí)間會(huì)由于10ms延遲的積累梧油,導(dǎo)致顯示時(shí)間比1s長(zhǎng)max*10ms的時(shí)間,其他時(shí)間的顯示正常,總時(shí)間正常
    new CountDownTimer(max * 1000, interval * 1000 - 10) {
        @Override
        public void onTick(long time) {
            // 第一次調(diào)用會(huì)有1-10ms的誤差州邢,因此需要+15ms儡陨,防止第一個(gè)數(shù)不顯示褪子,第二個(gè)數(shù)顯示2s
            if (null == tvVerifyCode.get())
                this.cancel();
            else
                tvVerifyCode.get().setText("" + ((time + 15) / 1000) + "s");
        }
        @Override
        public void onFinish() {
            if (null == tvVerifyCode.get()) {
                this.cancel();
                return;
            }
            tvVerifyCode.get().setEnabled(true);
            tvVerifyCode.get().setText(defaultString);
        }
    }.start();
}

從代碼中提煉出來(lái)的就這么幾步:

1、構(gòu)造函數(shù) CountDownTimer (long millisInFuture, long countDownInterval)
millisInFuture: 倒計(jì)時(shí)的總時(shí)間, 單位ms.
countDownInterval: 倒計(jì)時(shí)的間隔時(shí)間, 單位ms.

2骗村、重寫(xiě) onTick(long time) 和 onFinish() 方法

CountDownTimer 的使用很簡(jiǎn)單,只會(huì)使用的程序員不是好程序猿嫌褪,當(dāng)然要知道實(shí)現(xiàn)原理啦。

二胚股、CountDownTimer 官方示例


看到官方示例以及英文解釋?zhuān)裁匆馑迹?/p>

先看代碼笼痛,代碼中創(chuàng)建了一個(gè) CountDownTimer 匿名內(nèi)部類(lèi),實(shí)現(xiàn)了兩個(gè)方法 onTick(long millisUntilFinished) 和 onFinish()琅拌。onTick() 方法每隔 1 秒回調(diào)一次缨伊,顯示時(shí)間,onFinish() 方法在倒計(jì)時(shí)結(jié)束時(shí)候調(diào)用进宝。

上面的英文釋義:直到今后一段時(shí)間刻坊,定義一個(gè)倒計(jì)時(shí),定期間隔通知党晋,在本文字段中展示一個(gè) 30 秒倒計(jì)時(shí)示例谭胚。

下面的英文釋義:對(duì) onTick(long) 的調(diào)用與本對(duì)象同步,為了唯一調(diào)用 onTick(long)未玻,以便在上一次調(diào)用完成之前不會(huì)發(fā)生對(duì) onTick(long) 的再一次調(diào)用灾而。這有效僅在當(dāng)執(zhí)行 onTick(long) 時(shí),執(zhí)行時(shí)間和倒計(jì)時(shí)時(shí)間間隔相比的時(shí)候有意義扳剿。

翻譯不到位绰疤,習(xí)慣看英文的還是看英文吧。

簡(jiǎn)言之舞终,onTick(long) 這個(gè)方法是同步的,避免在上一次沒(méi)調(diào)用完的時(shí)候重復(fù)調(diào)用癣猾,而有效時(shí)間就是倒計(jì)時(shí)時(shí)間間隔啦敛劝。

三、CountDownTimer 源碼解析

3.1纷宇、基本結(jié)構(gòu)

CountDownTimer 成員變量和成員函數(shù)均比較少夸盟,圖中已經(jīng)給出很詳細(xì)的解釋了,看到方法名你就知道有什么作用了像捶。



通過(guò)源碼可知上陕,CountDownTimer 采用的是 Handler 機(jī)制,通過(guò) sendMessageDelayed 延遲發(fā)送一條 message 到主線程的 looper 中拓春,然后在自身中收到之后判斷剩余時(shí)間释簿,并發(fā)出相關(guān)回調(diào),然后再次發(fā)出 message 的方式硼莽。

public abstract class CountDownTimer {
    //剩余的總時(shí)間, 單位ms.
    private final long mMillisInFuture;
    //間隔時(shí)間, 單位ms.
    private final long mCountdownInterval;
    private long mStopTimeInFuture;
    //是否取消
    private boolean mCancelled = false;
    
    public CountDownTimer(long millisInFuture, long countDownInterval) {
        mMillisInFuture = millisInFuture;
        mCountdownInterval = countDownInterval;
    }
    //取消當(dāng)前的任務(wù)
    public synchronized final void cancel() {
        mCancelled = true;
        mHandler.removeMessages(MSG);
    }
    //開(kāi)始當(dāng)前的任務(wù)
    public synchronized final CountDownTimer start() {
        mCancelled = false;
        if (mMillisInFuture <= 0) {
            onFinish();
            return this;
        }
        //SystemClock.elapsedRealtime():返回系統(tǒng)啟動(dòng)到現(xiàn)在的毫秒數(shù)庶溶,包含休眠時(shí)間。
        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
        mHandler.sendMessage(mHandler.obtainMessage(MSG));
        return this;
    }
    //當(dāng)前任務(wù)每完成一次倒計(jì)時(shí)間隔時(shí)間時(shí)回調(diào)
    public abstract void onTick(long millisUntilFinished);
    //當(dāng)前任務(wù)完成的時(shí)候回調(diào)
    public abstract void onFinish();
    //MSG
    private static final int MSG = 1;
    //主要是處理對(duì) millisLeft 剩余時(shí)間的判斷,其中 delay 的處理需要注意偏螺,當(dāng) onTick() 方法耗時(shí)過(guò)長(zhǎng)會(huì)進(jìn)行跳過(guò)
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            synchronized (CountDownTimer.this) {
                if (mCancelled) {
                    return;
                }
                //剩余時(shí)間行疏,這就是上例中為什么有大約 1-10ms 的誤差的原因了
                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
                if (millisLeft <= 0) {
                    onFinish();
                } else if (millisLeft < mCountdownInterval) {
                    // no tick, just delay until done
                    //發(fā)送剩余時(shí)間數(shù)
                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
                } else {
                    //獲取啟動(dòng)到現(xiàn)在的毫秒數(shù)
                    long lastTickStart = SystemClock.elapsedRealtime();
                    //回調(diào) onTick()
                    onTick(millisLeft);
                    // take into account user's onTick taking time to execute
                    //處理用戶(hù)onTick執(zhí)行的時(shí)間,時(shí)間間隔始終和么算出來(lái)的
                    //啟動(dòng)時(shí)間 + 指定時(shí)間間隔 - 現(xiàn)在的時(shí)間
                    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
                    // special case: user's onTick took more than interval to
                    // complete, skip to next interval
                    //特殊情況:用戶(hù)的onTick方法花費(fèi)的時(shí)間比interval長(zhǎng),那么直接跳轉(zhuǎn)到下一次interval
                    while (delay < 0) delay += mCountdownInterval;
                    //發(fā)送延遲時(shí)間
                    sendMessageDelayed(obtainMessage(MSG), delay);
                }
            }
        }
    };
}

3.2套像、CountDownTimer 執(zhí)行流程圖

根據(jù) CountDownTimer 的執(zhí)行過(guò)程酿联,以及 Handler 的源碼,可以用流程圖來(lái)描述倒計(jì)時(shí)的執(zhí)行流程:


synchronized 關(guān)鍵字

public synchronized final void cancel();
public synchronized final CountDownTimer start();
private Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
      synchronized (CountDownTimer.this) {}
  };
}

在源碼中夺巩,cancle()贞让,start() 函數(shù)被 synchronized 修飾,handleMessage(Message msg) 中代碼段也被 synchronized 修飾劲够,它主要是用來(lái)保證在同一時(shí)刻震桶,至多只有一個(gè)線程執(zhí)行該段代碼,主要有以下兩個(gè)特點(diǎn):
1征绎、修飾一個(gè)方法蹲姐,被修飾的方法稱(chēng)為同步方法,其作用的范圍是整個(gè)方法人柿, 作用的對(duì)象是調(diào)用這個(gè)方法的對(duì)象柴墩;
2、修飾一個(gè)代碼塊凫岖,被修飾的代碼塊稱(chēng)為同步語(yǔ)句塊江咳,其作用的范圍是大括號(hào){}括起來(lái)的代碼, 作用的對(duì)象是調(diào)用這個(gè)代碼塊的對(duì)象哥放;當(dāng)某個(gè)線程訪問(wèn)的某具體對(duì)象的代碼塊被 synchronized 修飾時(shí)歼指,其它線程對(duì)象中所有被 synchronized 修飾的代碼塊都會(huì)被阻塞,沒(méi)有被 synchronized 的代碼塊訪問(wèn)正常甥雕。

關(guān)于 synchronized 的更多細(xì)節(jié)踩身,請(qǐng)查看 Java中Synchronized的用法

在上面的示例中用到了弱引用,筆者一直沒(méi)有提起社露,查了資料挟阻,學(xué)習(xí)之后發(fā)現(xiàn),遠(yuǎn)遠(yuǎn)不止想象中的那些知識(shí)點(diǎn)峭弟,只能在寫(xiě)一篇文章中進(jìn)行講解啦附鸽。

【從 0 開(kāi)始開(kāi)發(fā)一款直播 APP】8 弱引用 WeakReference

參考:
http://blog.qiji.tech/archives/7485
http://blog.csdn.net/qq_20785431/article/details/51571300

【五一大促】菜鳥(niǎo)窩全場(chǎng)android項(xiàng)目實(shí)戰(zhàn)課程低至五折,更有價(jià)值33元的四款熱門(mén)技術(shù)免費(fèi)領(lǐng)瞒瘸,17年初優(yōu)惠力度最大的一次活動(dòng)坷备,有意向的童鞋不要錯(cuò)過(guò)
狂戳>>http://www.cniao5.com/hd/2017/51.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市情臭,隨后出現(xiàn)的幾起案子击你,更是在濱河造成了極大的恐慌玉组,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丁侄,死亡現(xiàn)場(chǎng)離奇詭異惯雳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鸿摇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)石景,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人拙吉,你說(shuō)我怎么就攤上這事潮孽。” “怎么了筷黔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵往史,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我佛舱,道長(zhǎng)椎例,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任请祖,我火速辦了婚禮订歪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肆捕。我一直安慰自己刷晋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布慎陵。 她就那樣靜靜地躺著眼虱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪席纽。 梳的紋絲不亂的頭發(fā)上蒙幻,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音胆筒,去河邊找鬼。 笑死诈豌,一個(gè)胖子當(dāng)著我的面吹牛仆救,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矫渔,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼彤蔽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了庙洼?” 一聲冷哼從身側(cè)響起顿痪,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镊辕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蚁袭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體征懈,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年揩悄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卖哎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡删性,死狀恐怖亏娜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹬挺,我是刑警寧澤维贺,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站巴帮,受9級(jí)特大地震影響溯泣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晰韵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一发乔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雪猪,春花似錦栏尚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至官觅,卻和暖如春纵菌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背休涤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工咱圆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人功氨。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓序苏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捷凄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忱详,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容