本文为菜鸟窝作者蒋志碧的连载。“从 0 开始开发一款直播 APP ”系列来聊聊时下最火的直播 APP,如何完整的实现一个类"腾讯直播"的商业化项目
视频地址:http://www.cniao5.com/course/10121
倒计时功能常见应用场景:手机发送验证码
以前写定时器的常用做法:Handler + Timer + TimeTask,这个方法可以抛弃啦,android 为我们封装好了一个 CountDownTimer 类。
先来看看一个 Demo,倒计时器的应用。
一、倒计时器的使用
if (null != mLoginView){
mLoginView.verifyCodeSuccess(60,60);
}
@Override
public void verifyCodeSuccess(int reaskDuration, int expireDuration) {
showMsg("注册短信下发,验证码 " + expireDuration / 60 + " 分钟内有效!");
OtherUtils.startTimer(new WeakReference(tvVerifyCode), "验证码", reaskDuration, 1);
}
/**
* 在按钮上启动一个定时器
* @param tvVerifyCode 验证码控件
* @param defaultString 按钮上默认的字符串
* @param max 失效时间(单位:s)
* @param interval 更新间隔(单位:s)
*/
public static void startTimer(final WeakReference tvVerifyCode,
final String defaultString,
int max,
int interval) {
tvVerifyCode.get().setEnabled(false);
// 由于CountDownTimer并不是准确计时,在onTick方法调用的时候,time会有1-10ms左右的误差,这会导致最后一秒不会调用onTick()
// 因此,设置间隔的时候,默认减去了10ms,从而减去误差。
// 经过以上的微调,最后一秒的显示时间会由于10ms延迟的积累,导致显示时间比1s长max*10ms的时间,其他时间的显示正常,总时间正常
new CountDownTimer(max * 1000, interval * 1000 - 10) {
@Override
public void onTick(long time) {
// 第一次调用会有1-10ms的误差,因此需要+15ms,防止第一个数不显示,第二个数显示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();
}
从代码中提炼出来的就这么几步:
1、构造函数 CountDownTimer (long millisInFuture, long countDownInterval)
millisInFuture: 倒计时的总时间, 单位ms.
countDownInterval: 倒计时的间隔时间, 单位ms.
2、重写 onTick(long time) 和 onFinish() 方法
CountDownTimer 的使用很简单,只会使用的程序员不是好程序猿,当然要知道实现原理啦。
二、CountDownTimer 官方示例
看到官方示例以及英文解释,什么意思?
先看代码,代码中创建了一个 CountDownTimer 匿名内部类,实现了两个方法 onTick(long millisUntilFinished) 和 onFinish()。onTick() 方法每隔 1 秒回调一次,显示时间,onFinish() 方法在倒计时结束时候调用。
上面的英文释义:直到今后一段时间,定义一个倒计时,定期间隔通知,在本文字段中展示一个 30 秒倒计时示例。
下面的英文释义:对 onTick(long) 的调用与本对象同步,为了唯一调用 onTick(long),以便在上一次调用完成之前不会发生对 onTick(long) 的再一次调用。这有效仅在当执行 onTick(long) 时,执行时间和倒计时时间间隔相比的时候有意义。
翻译不到位,习惯看英文的还是看英文吧。
简言之,onTick(long) 这个方法是同步的,避免在上一次没调用完的时候重复调用,而有效时间就是倒计时时间间隔啦。
三、CountDownTimer 源码解析
3.1、基本结构
CountDownTimer 成员变量和成员函数均比较少,图中已经给出很详细的解释了,看到方法名你就知道有什么作用了。
通过源码可知,CountDownTimer 采用的是 Handler 机制,通过 sendMessageDelayed 延迟发送一条 message 到主线程的 looper 中,然后在自身中收到之后判断剩余时间,并发出相关回调,然后再次发出 message 的方式。
public abstract class CountDownTimer {
//剩余的总时间, 单位ms.
private final long mMillisInFuture;
//间隔时间, 单位ms.
private final long mCountdownInterval;
private long mStopTimeInFuture;
//是否取消
private boolean mCancelled = false;
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
//取消当前的任务
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}
//开始当前的任务
public synchronized final CountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
//SystemClock.elapsedRealtime():返回系统启动到现在的毫秒数,包含休眠时间。
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
//当前任务每完成一次倒计时间隔时间时回调
public abstract void onTick(long millisUntilFinished);
//当前任务完成的时候回调
public abstract void onFinish();
//MSG
private static final int MSG = 1;
//主要是处理对 millisLeft 剩余时间的判断,其中 delay 的处理需要注意,当 onTick() 方法耗时过长会进行跳过
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
if (mCancelled) {
return;
}
//剩余时间,这就是上例中为什么有大约 1-10ms 的误差的原因了
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()
onTick(millisLeft);
// take into account user's onTick taking time to execute
//处理用户onTick执行的时间,时间间隔始终和么算出来的
//启动时间 + 指定时间间隔 - 现在的时间
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval to
// complete, skip to next interval
//特殊情况:用户的onTick方法花费的时间比interval长,那么直接跳转到下一次interval
while (delay < 0) delay += mCountdownInterval;
//发送延迟时间
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}
3.2、CountDownTimer 执行流程图
根据 CountDownTimer 的执行过程,以及 Handler 的源码,可以用流程图来描述倒计时的执行流程:
synchronized 关键字
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()
函数被 synchronized
修饰,handleMessage(Message msg) 中代码段也被 synchronized 修饰,它主要是用来保证在同一时刻,至多只有一个线程执行该段代码,主要有以下两个特点:
1、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法, 作用的对象是调用这个方法的对象;
2、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码, 作用的对象是调用这个代码块的对象;当某个线程访问的某具体对象的代码块被 synchronized 修饰时,其它线程对象中所有被 synchronized 修饰的代码块都会被阻塞,没有被 synchronized 的代码块访问正常。
关于 synchronized 的更多细节,请查看 Java中Synchronized的用法
在上面的示例中用到了弱引用,笔者一直没有提起,查了资料,学习之后发现,远远不止想象中的那些知识点,只能在写一篇文章中进行讲解啦。
【从 0 开始开发一款直播 APP】8 弱引用 WeakReference
参考:
http://blog.qiji.tech/archives/7485
http://blog.csdn.net/qq_20785431/article/details/51571300
【五一大促】菜鸟窝全场android项目实战课程低至五折,更有价值33元的四款热门技术免费领,17年初优惠力度最大的一次活动,有意向的童鞋不要错过
狂戳>>http://www.cniao5.com/hd/2017/51.html