官方文档。CountDownTimer是一个倒计时的类,还可以指定时间间隔定期通知,举个栗子,比如说你倒计时是100秒的,可以指定每20秒通知一次,这样开始的时候会回调一次,20秒时会回调一次,40秒时会回调一次…,200秒时的回调和时间间隔的回调不同方法的。
CountDownTimer只有一个构造,
/**
* @param millisInFuture The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
从方法的注释中可以看出,第一个参数是总的倒计时时间,第二个参数是定期的回调时间。
使用的时候:
new CountDownTimer(10000, 2000) {
@Override
public void onTick(long millisUntilFinished) {
Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
}
@Override
public void onFinish() {
Log.v("CountDownTimerTest", "onFinish");
}
}.start();
onTick()方法是定期间隔回调的方法,onFinish()就是结束时回调的方法了。
可以看出在9970毫秒的时候回调了一次onTick()方法,因为消息传递会消耗一点时间,使用传过来时是9976毫秒,后面的也是,这里执行了onTick()方法4次,那剩下2秒的时候不应该还要执行一次么?怎么3964后就不执行onTick()方法了?原因是3964毫秒再经过2秒就剩下1900多毫秒,比2秒小,使用不执行onTick()方法。
CountDownTimer的核心代码
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(millisLeft);
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
如果倒计时后不让它执行了也可以取消倒计时,调用CountDownTimer的cancel()方法就可以取消倒计时了。
有时候我们并不关心onTick(),只需要关心onFinish()方法的执行,我们可以封装个工具类,把这两个功能分开,想单独用时就单独用,想合起来用时就合起来用。
package com.ce.countdowntimertest.utils;
import android.os.CountDownTimer;
/**
* 倒计时工具类
*/
public class CountDownTimerUtils {
/**
* 倒计时结束的回调接口
*/
public interface FinishDelegate {
void onFinish();
}
/**
* 定期回调的接口
*/
public interface TickDelegate {
void onTick(long pMillisUntilFinished);
}
private final static long ONE_SECOND = 1000;
/**
* 总倒计时时间
*/
private long mMillisInFuture = 0;
/**
* 定期回调的时间 必须大于0 否则会出现ANR
*/
private long mCountDownInterval;
/**
* 倒计时结束的回调
*/
private FinishDelegate mFinishDelegate;
/**
* 定期回调
*/
private TickDelegate mTickDelegate;
private MyCountDownTimer mCountDownTimer;
/**
* 获取 CountDownTimerUtils
* @return CountDownTimerUtils
*/
public static CountDownTimerUtils getCountDownTimer() {
return new CountDownTimerUtils();
}
/**
* 设置定期回调的时间 调用{@link #setTickDelegate(TickDelegate)}
* @param pCountDownInterval 定期回调的时间 必须大于0
* @return CountDownTimerUtils
*/
public CountDownTimerUtils setCountDownInterval(long pCountDownInterval) {
this.mCountDownInterval=pCountDownInterval;
return this;
}
/**
* 设置倒计时结束的回调
* @param pFinishDelegate 倒计时结束的回调接口
* @return CountDownTimerUtils
*/
public CountDownTimerUtils setFinishDelegate(FinishDelegate pFinishDelegate) {
this.mFinishDelegate=pFinishDelegate;
return this;
}
/**
* 设置总倒计时时间
* @param pMillisInFuture 总倒计时时间
* @return CountDownTimerUtils
*/
public CountDownTimerUtils setMillisInFuture(long pMillisInFuture) {
this.mMillisInFuture=pMillisInFuture;
return this;
}
/**
* 设置定期回调
* @param pTickDelegate 定期回调接口
* @return CountDownTimerUtils
*/
public CountDownTimerUtils setTickDelegate(TickDelegate pTickDelegate) {
this.mTickDelegate=pTickDelegate;
return this;
}
public void create() {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
mCountDownTimer = null;
}
if (mCountDownInterval <= 0) {
mCountDownInterval = mMillisInFuture + ONE_SECOND;
}
mCountDownTimer = new MyCountDownTimer(mMillisInFuture, mCountDownInterval);
mCountDownTimer.setTickDelegate(mTickDelegate);
mCountDownTimer.setFinishDelegate(mFinishDelegate);
}
/**
* 开始倒计时
*/
public void start() {
if (mCountDownTimer == null) {
create();
}
mCountDownTimer.start();
}
/**
* 取消倒计时
*/
public void cancel() {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
}
}
private static class MyCountDownTimer extends CountDownTimer {
private FinishDelegate mFinishDelegate;
private TickDelegate mTickDelegate;
/**
* @param millisInFuture The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/
public MyCountDownTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onTick(long millisUntilFinished) {
if (mTickDelegate != null) {
mTickDelegate.onTick(millisUntilFinished);
}
}
@Override
public void onFinish() {
if (mFinishDelegate != null) {
mFinishDelegate.onFinish();
}
}
void setFinishDelegate(FinishDelegate pFinishDelegate) {
this.mFinishDelegate=pFinishDelegate;
}
void setTickDelegate(TickDelegate pTickDelegate) {
this.mTickDelegate=pTickDelegate;
}
}
}
使用也简单,用getCountDownTimer()方法获取实例;setMillisInFuture()方法是设置总倒计时时间;setFinishDelegate()方法是设置倒计时完成的回调;setCountDownInterval()方法是设置定期回调的时间间隔,但值要大于0,不然会出现ANR的,因为不停的回调会导致Looper消息处理不过来;setTickDelegate()方法是设置定期回调的。
只需要最终倒计时的使用:
Log.v("CountDownTimerTest", "Start");
CountDownTimerUtils.getCountDownTimer()
.setMillisInFuture(5000)
.setFinishDelegate(new CountDownTimerUtils.FinishDelegate() {
@Override
public void onFinish() {
Log.v("CountDownTimerTest", "onFinish");
}
}).start();
Log.v("CountDownTimerTest", "Start");
CountDownTimerUtils.getCountDownTimer()
.setMillisInFuture(10000)
.setCountDownInterval(2000)
.setTickDelegate(new CountDownTimerUtils.TickDelegate() {
@Override
public void onTick(long pMillisUntilFinished) {
Log.v("CountDownTimerTest", "pMillisUntilFinished = " + pMillisUntilFinished);
}
}).start();
Log.v("CountDownTimerTest", "Start");
CountDownTimerUtils.getCountDownTimer()
.setMillisInFuture(10000)
.setCountDownInterval(2000)
.setTickDelegate(new CountDownTimerUtils.TickDelegate() {
@Override
public void onTick(long pMillisUntilFinished) {
Log.v("CountDownTimerTest", "pMillisUntilFinished = " + pMillisUntilFinished);
}
})
.setFinishDelegate(new CountDownTimerUtils.FinishDelegate() {
@Override
public void onFinish() {
Log.v("CountDownTimerTest", "onFinish");
}
}).start();
虽然CountDownTimer很好用,但有个缺点,就是只能在主线程中运行,如果在子线程中运行就会报错,
new Thread(new Runnable() {
@Override
public void run() {
Log.v("CountDownTimerTest", "SubThread Start");
new android.os.CountDownTimer(10000, 2000) {
@Override
public void onTick(long millisUntilFinished) {
Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
}
@Override
public void onFinish() {
Log.v("CountDownTimerTest", "onFinish");
}
}.start();
}
}).start();
原因是在主线程中Handler的Looper Android系统已经在framework层帮我们prepare(prepareMainLooper())过了,但在我们这个子线程没有Looper,那怎么办?我们可以借助HandlerThread完成,新建个自己的CountDownTimer类,把Android的CountDownTimer的源代码复制到我们新建的CountDownTimer类中,修改下包名,就不要用Android的CountDownTimer这个类的了,改造如下:
package com.ce.countdowntimertest.common;
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
/**
*
* Created by CE
*
* Schedule a countdown until a time in the future, with
* regular notifications on intervals along the way.
*
* Example of showing a 30 second countdown in a text field:
*
*
* new CountDownTimer(30000, 1000) {
*
* public void onTick(long millisUntilFinished) {
* mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
* }
*
* public void onFinish() {
* mTextField.setText("done!");
* }
* }.start();
*
*
* The calls to {@link #onTick(long)} are synchronized to this object so that
* one call to {@link #onTick(long)} won't ever occur before the previous
* callback is complete. This is only relevant when the implementation of
* {@link #onTick(long)} takes an amount of time to execute that is significant
* compared to the countdown interval.
*/
public abstract class CountDownTimer {
/**
* Millis since epoch when alarm should stop.
*/
private final long mMillisInFuture;
/**
* The interval in millis that the user receives callbacks
*/
private final long mCountdownInterval;
private long mStopTimeInFuture;
/**
* boolean representing if the timer was cancelled
*/
private boolean mCancelled = false;
/**
* @param millisInFuture The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
if (!isMainThread()) {
mHandlerThread = new HandlerThread("CountDownTimerThread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper(), mCallback);
} else {
mHandler = new Handler(mCallback);
}
}
/**
* Cancel the countdown.
*/
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}
/**
* Start the countdown.
*/
public synchronized final CountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
private boolean isMainThread() {
return Looper.getMainLooper().getThread().equals(Thread.currentThread());
}
/**
* Callback fired on regular interval.
* @param millisUntilFinished The amount of time until finished.
*/
public abstract void onTick(long millisUntilFinished);
/**
* Callback fired when the time is up.
*/
public abstract void onFinish();
private static final int MSG = 1;
// handles counting down
/*private android.os.Handler mHandler = new android.os.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) {
// no tick, just delay until done
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;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};*/
private HandlerThread mHandlerThread;
private Handler mHandler;
private Handler.Callback mCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
if (mCancelled) {
return true;
}
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
if (mHandlerThread != null) mHandlerThread.quit();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
mHandler.sendMessageDelayed(mHandler.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;
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG), delay);
}
}
return false;
}
};
}
如果是主线程就不用管了,如果是子线程我们就把HandlerThread的getLooper()拿出来给Handler。这里我用的是Handler.Callback来进行对消息处理的。现在我们把刚才在子线程中运行的CountDownTimer类改成我们自己定义的CountDownTimer类在运行一下:
new Thread(new Runnable() {
@Override
public void run() {
Log.v("CountDownTimerTest", "SubThread Start");
new com.ce.countdowntimertest.common.CountDownTimer(10000, 2000) {
@Override
public void onTick(long millisUntilFinished) {
Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
}
@Override
public void onFinish() {
Log.v("CountDownTimerTest", "onFinish");
}
}.start();
}
}).start();
Log.v("CountDownTimerTest", "Start");
new com.ce.countdowntimertest.common.CountDownTimer(10000, 2000) {
@Override
public void onTick(long millisUntilFinished) {
Log.v("CountDownTimerTest", "onTick millisUntilFinished = " + millisUntilFinished);
}
@Override
public void onFinish() {
Log.v("CountDownTimerTest", "onFinish");
}
}.start();
如果在CountDownTimerUtils类里想用这个改进的CountDownTimer,改下CountDownTimer的包名就可以运行。