一、用法详解
在Android开发中,有时需要使用倒计时功能,在Android系统中提供了一个倒计时的抽象类来辅助倒计时行为。
public class CountDownTimeActivity extends Activity implements OnClickListener {
TextView mTextView;
Button mButton1;
Button mButton2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.countdown);
mTextView = (TextView)findViewById(R.id.textView1);
mButton1 = (Button)findViewById(R.id.button1);
mButton2 = (Button)findViewById(R.id.button2);
mButton1.setOnClickListener(this);
mButton2.setOnClickListener(this);
}
CountDownTimer timer = new CountDownTimer(40000,1000) { //定义40秒,每一秒执行一次
@Override
public void onTick(long millisUntilFinished) {
mTextView.setText("seconds remaining: " + millisUntilFinished / 1000);
//millisUntilFinished不是精确值,需要进行计算millisUntilFinished / 1000
}
@Override
public void onFinish() {
mTextView.setText("done!");
}
};
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.button1:
timer.start();
break;
case R.id.button2:
timer.cancel(); //取消后停止运行。下次还会重新开始,而不是接着开始
break;
}
}
}
这个类有点缺陷,就是不能暂停后再次接着继续使用,为此,需要自定义一个类似的倒计时计时器
package android.os;
import android.util.Log;
public abstract class CountDownTimer {
private final long mMillisInFuture;
private final long mCountdownInterval;
private long mStopTimeInFuture;
private long millisUntilFinished = 0;
private boolean isPause = false;
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
public final void cancel() {
mHandler.removeMessages(MSG);
}
public final void pause()
{
cancel();
isPause = true;
}
public synchronized final CountDownTimer start() {
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
if(isPause)
{
mStopTimeInFuture = mStopTimeInFuture - millisUntilFinished;
isPause = false;
}
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
public abstract void onTick(long millisUntilFinished);
public abstract void onFinish();
private static final int MSG = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
millisUntilFinished = 0;
} else if (millisLeft < mCountdownInterval) {
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
millisUntilFinished = millisLeft;
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}
二、核心源码解析
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
if (mCancelled) {
return;
}
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// 剩余时间小于一次时间间隔的时候,不再通知,只是延迟一下
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// 处理用户onTick执行的时间
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
// 特殊情况:用户的onTick方法花费的时间比interval长,那么直接跳转到下一次interval
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
通过源码可知,CountDownTimer采用的是handler机制,通过sendMessageDelayed延迟发送一条message到主线程的looper中,然后在自身中收到之后判断剩余时间,并发出相关回调,然后再次发出message的方式。之前实现这种倒计时是通过asynctask,在线程中通过Thread.sleep来实现,通过asyntask的cancel来实现取消,通过构造asynctask传入接口的实现来onTick的类似功能。这个CountDownTimer默认是在当前looper当中,可以是在UI线程也可以是在非UI线程中执行,如果在UI线程中执行,那是不是会稍微加重UI线程的负担?
此外、主线程队列阻塞、倒计时的准确度如何?
try doing it!