本文為菜鳥(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