首先说下关于handler自身的误差:
如果使用handler.postDealyed(……, 1000)方式来进行每秒的计时,是不准确的,是的,有很大误差,误差的原因在于在你收到消息,到你重新发出handler.postDealyed的时间,并不是瞬间完成的,这里面有很多逻辑处理的时间,即使没有逻辑处理的时间,handler本身也是耗损性能的,所以消息并不可能按照理想的1000延迟来进行发送,这就导致了误差的累积。
下面我们介绍的handler精确计时的两种方式分别来自Android源码的两个控件:TextClock和CountDownTimer:
第一种方式是TextClock对于handler的时间修正:
private final Runnable mTicker = new Runnable() {
public void run() {
if (mStopTicking) {
return; // Test disabled the clock ticks
}
onTimeChanged();
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
getHandler().postAtTime(mTicker, next);
}
};
我们之前是通过postDelay来触发消息事件的,但这里系统使用了postAtTime,这样就是设置了在某一个时间点抛出handler消息,前面的
long now = SystemClock.uptimeMillis(); long next = now + (1000 - now % 1000);
精确控制了1秒的整点时间,因此系统可以在每一个整秒的时间点发出消息。
代码如下:
mCalHandler.post(mTicker);
/**
* 精确修正时间
*/
private Handler mCalHandler = new Handler(Looper.getMainLooper());
private final Runnable mTicker = new Runnable() {
public void run() {
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
mCalHandler.postAtTime(mTicker, next);
Log.e("buder", now + "");
}
};
看一下效果,基本误差都在2毫秒内
参考博客:https://www.jianshu.com/p/56f37988428b
第二种方式我们可以直接参考CountDowntimer的源码:
注意CountdownTimer本身也是不准确的,需要我们手动补偿误差时间,也是我们修改的核心点
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;
}
/**
* 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;
}
/**
* 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 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 {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to execute
long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
long delay;
if (millisLeft < mCountdownInterval) {
// just delay until done
delay = millisLeft - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, trigger onFinish without delay
if (delay < 0) delay = 0;
} else {
delay = mCountdownInterval - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
}
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}
日常我们计时较短一般10分钟以内使用这个countdownTimer时,偶尔会遇到剩下最后一秒钟没有走完,这个还是因为里面的实现handler计时不准确导致的,下面代码是我们日常使用countDownTimer的示例:
countDownTimer.start();
/**
* 第一个参数表示总时间,第二个参数表示间隔时间。意思就是每隔一秒会回调一次方法onTick,然后10秒之后会回调onFinish方法
*/
private CountDownTimer countDownTimer = new CountDownTimer(1000 * 100, 1000) {
@Override
public void onTick(long millisUntilFinished) {
//秒转化成 00:00形式一
// timeView2.setText(formatTime1(millisUntilFinished) + "");
timeView2.setText(formatTime2(millisUntilFinished / 1000));
Log.e("hehehe ", millisUntilFinished + " ");
}
@Override
public void onFinish() {
}
};
看下结果:
很明显已经有十几毫秒的误差了。
时间不到零或者有误差该怎么办?很简单,就是认为的添加上误差的毫秒数,只要不超过1000就可以,例如:
或者直接修改countdownTimer源码的构造函数:
/**
* @param millisInFuture 总倒计时时间
* @param countDownInterval 倒计时间隔时间
*/
public CustomCountDownTimer(long millisInFuture, long countDownInterval) {
// 解决秒数有时会一开始就减去了2秒问题(如10秒总数的,刚开始就8999,然后没有不会显示9秒,直接到8秒)
if (countDownInterval > 1000) millisInFuture += 15;
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
millisInFuture += 15;
其实就是补充上误差的几毫秒,让起始时间不产生跳变2秒的问题。当然上面的900毫秒还是在构造函数里的15毫秒都是需要根据总计时数来动态调整的。