有时候我们在一些特别的场景可能会需要使用到不是这么精确的倒计时的功能,比如说:发送短信验证码倒计时。有时候我们会发现这个功能也不难但是实现起来的话也挺繁琐的,这个时候系统Api就为我们简化代码封装了一个CountDownTimer的类来使用。我们平时在做这类需求的时候可能很多都会使用Thread+Handler或者是Timer + handler机制,一个简单的功能写了一大片的代码来实现,下面我们就具体来看看它使用以及实现的原理
CountDownTimer是Android sdk中的一个用于倒计时的抽象类,我们在使用的时候需要去继承该类。下面我们来模拟发送手机验证码。现在我们就来看一下
使用方法
public class CountDownTimerActivity extends Activity {
private Button mSend;
private SendCountMessage mCountMessage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_countdown);
mCountMessage = new SendCountMessage();
mSend = (Button) findViewById(R.id.sendCode);
mSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSend.setClickable(false);
//开始执行倒计时的功能
mCountMessage.start();
}
});
}
/**
* 我们继承这个抽象类,然后设置好总共的倒计时的时间,以及间隔的时间
* 并且重写 onTick和onFinish方法
*/
class SendCountMessage extends CountDownTimer {
/**
* 这里我们还需要设置两个参数:
* 第一个参数:表示我们倒计时的总时间
* 第二个参数:表示我们倒计时的间隔,比如说我们是按一秒数还是二秒
*/
public SendCountMessage() {
super(60000, 1000);
}
/**
* 该方法表示会在构造方法中设定的间隔时间下调用这个方法的。
* 比如说我们设置了间隔时间为1秒的话,那么CountDownTimer
* 将会每个一秒的时间调用 onTick方法一下
* @param millisUntilFinished 表示距离倒计时结束的时间
*/
public void onTick(long millisUntilFinished) {
mSend.setText(millisUntilFinished/1000 + " 秒后重发");
}
/**
* 这里表示倒计时完成结束了
*/
public void onFinish() {
mSend.setClickable(true);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
/**
* 最后在这里的时候,我们需要将CountDownTimer取消掉,因为如果我们在销毁界面的时候
* 还没有取消该倒计时器的话,它还会一直在后台不断的跑的直到结束倒计最后才会结束的,
* 这样子为了以免出现问题,我们这里需要取消掉,并且让系统gc该变量。
*/
if(mCountMessage != null) {
mCountMessage.cancel();
mCountMessage = null;
}
}
}
界面布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
........
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:layout_marginTop="20dip">
<EditText
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="number"
android:hint="请输入验证码" />
<Button
android:id="@+id/sendCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送验证码"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
注意
其实CountDownTimer的使用还是挺简单的,我们只要设置到倒计时的总时间以及倒计时的时间间隔就行了,另外 我们再使用 CountDownTimer countdown = CountDownTimer(long millisInFuture, long countDownInterval)
的时候需要注意的是 millisInFuture和countDownInterval的时间单位是毫秒,所以我们在计算的时候就要考虑清楚。最重要的一点是:当我们不需要使用倒计时功能的时候,一定要要调用cancel()方法取消掉,不然它还会在我们页面销毁的时候继续执行的,很有可能会导致内存泄漏的问题
代码分析
通过上面的代码我们很快的就知道如何使用该api了,但是我们很多的时候不仅仅是只是希望知道如何使用,还想知道它内部是如何实现这么简单的功能,这样子对于我们的原理学习以及别人的代码风格以及代码封装的学习也是有好处的。
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
public synchronized final CountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
//通过当前开始的时间 + 倒计时的总时间来计算出结束的毫秒值
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
//然后发送一个message消息给mHandler
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
我们在创建CountDownTimer对象的时候会传入一个倒计时的总时间和计时的间隔时间,当我们创建好了CountDownTimer对象,这个时候我们就调用 start()
方法开启倒计时了。从代码中我们可以看出开启倒计时器的时候,首先会计算出结束计时的毫秒值的,然后发送一个Handler消息。这个时候我们就需要看看mHandler里面的代码是如何执行的了。
// handles counting down
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
//如果用户主动调用了取消方法,则返回
if (mCancelled) {
return;
}
//第一步:首先判断结束的时间跟当前时间的差。
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
//条件一: 如果小于等于0了,说明结束了。
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
//条件二: 如果距离结束的时间小于我们设定的间隔时间值的时候
// 这个时候就发送一个millisLeft延时的消息
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
//调用我们的抽象方法,并且将距离结束的时间值当作参数回调出去
onTick(millisLeft);
// take into account user's onTick taking time to execute
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
//发送一个延时的,时间间隔为我们设定的mCountdownInterval的消息出去
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
在创建构造函数之前会创建一个内部的Handler对象,主要是用于定时发送消息用的。当我们调用start()
方法的时候会发送一个Handler消息出来,这个时候会在mHandler中进行处理。当Handler收到消息之后就会去跟设定的时间间隔值进行一个比对,然后就发送一个延时的消息。
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}
PS:当我们不想使用倒计时功能的时候,如果该倒计时还在计时的时候我们需要将其取消并且关闭掉,其实取消倒计时器也是很简单的,将标志位设置成true,然后再将Handler消息队列中的消息全部清空就可以了,这个时候就再也不会发送延时的消息了,但是我在使用低版本的时候并没有设置标志位mCancelled
只是简单的将消息队列中的消息全部删除掉也是可以取消掉倒计时器的。
总结
通过上面的源代码我们可以分析出该倒计时器的实现是非常的简单,只是通过Handler不断的发送延时的Message,然后Handler接收到消息之后再进行一下时间比对,如果没有超过我们设定的是时间值的话则继续发送消息,同时将距离结束的时间值通过方法回调出去,用户只要实现该方法就可知道结束的时间值。其实我们也完全可以在代码中自己实现一个这样子的功能,只是人家就把这个功能封装成了一个类提供给外部调用可以了,这样子就可以显的代码非常的简洁了。