java自定义计时器
前言
用android官方自带的CountDownTimer计时器的时候安卓六和安卓十的源码有差别,导致我的计时一直有一些小bug
所以就自己写了个自用的计时器,方便以后查阅
提示:以下是本篇文章正文内容,下面案例可供参考
一、模仿countdownTimer来自定义一个自己的计时器
源码如下:
package com.chuyitech.gourmagic.device.utils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.chuyitech.gourmagic.device.views.base.BaseThread;
public abstract class ChuyiCountDownTimer {
/**
* Millis since epoch when alarm should stop.
*/
private final long mMillisInFuture;
private long mCurrentTime;
/**
* The interval in millis that the user receives callbacks
*/
private final long mCountdownInterval;
/**
* boolean representing if the timer was cancelled
*/
private boolean mCancelled = false;
private Thread thread;
/**
* @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 ChuyiCountDownTimer(long millisInFuture, long countDownInterval) {
if(millisInFuture==0||millisInFuture%1000!=0||countDownInterval==0||countDownInterval%1000!=0){
throw new RuntimeException("参数不合法");
}
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
/**
* Cancel the countdown.
*/
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
if(thread!=null && thread.isAlive()){
thread.interrupt();
thread=null;
}
}
/**
* Start the countdown.
*/
public synchronized final ChuyiCountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mCurrentTime=mMillisInFuture;
if(thread==null||!thread.isAlive()){
thread=new BaseThread(){
@Override
public void run() {
super.run();
while (mCurrentTime>=0 && !isInterrupted()){
mCurrentTime-=1000;
ProgramUtils.sleep(1000);
//识别结束标识
if(mCurrentTime<=0){
mHandler.sendMessage(mHandler.obtainMessage(MSG));
}
//识别倒计时(第一次降低到时间以内不上报)
if((mMillisInFuture-mCurrentTime)%mCountdownInterval==0 ){
mHandler.sendMessage(mHandler.obtainMessage(MSG));
}
}
}
};
thread.start();
}
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(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
synchronized (this) {
if (mCancelled) {
return;
}
if (mCurrentTime <= 0) {
onFinish();
} else {
onTick(mCurrentTime);
}
}
}
};
}
由上面源码可以看出这是个抽象类,类中含有两个抽象方法
/**
* 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();
含有一个成员方法:
/**
* Start the countdown.
*/
public synchronized final ChuyiCountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mCurrentTime=mMillisInFuture;
if(thread==null||!thread.isAlive()){
thread=new BaseThread(){
@Override
public void run() {
super.run();
while (mCurrentTime>=0 && !isInterrupted()){
mCurrentTime-=1000;
ProgramUtils.sleep(1000);
//识别结束标识
if(mCurrentTime<=0){
mHandler.sendMessage(mHandler.obtainMessage(MSG));
}
//识别倒计时(第一次降低到时间以内不上报)
if((mMillisInFuture-mCurrentTime)%mCountdownInterval==0 ){
mHandler.sendMessage(mHandler.obtainMessage(MSG));
}
}
}
};
thread.start();
}
return this;
}
具体方法中在工作线程中调用了mHandler,之后主线程收到消息,根据条件执行onTick()和onFinish()方法,
private static final int MSG = 1;
// handles counting down
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
synchronized (this) {
if (mCancelled) {
return;
}
if (mCurrentTime <= 0) {
onFinish();
} else {
onTick(mCurrentTime);
}
}
}
};
根据上面可以看出我们实现这个抽象类后,调用start方法,start方法就可以通过handler传递消息调用实现类中实现的onTick()和onFinish()方法,(解释下为何这里用handler,因为计时器在子线程中,而onTick/onFinish实现方法中的内容是和主线程有关的),
至于上面如何实现的计时器功能,因为start方法中子线程在未满足条件前一直在执行循环,同时子线程中在执行线程休眠1s的逻辑,故而计时器是按照1s来递减的。
同时该自定义计时器提供给外部cancel方法,
/**
* Cancel the countdown.
*/
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
if(thread!=null && thread.isAlive()){
thread.interrupt();
thread=null;
}
}
其中的thread.interrupt();可以让start方法中
while (mCurrentTime>=0 && !isInterrupted())
循环条件不满足,从而退出计时器
二、使用步骤
在需要调用计时器的类中加上以下代码:
/*开始计时*/
if (timer != null) {
timer.cancel();
timer = null;
}
timer = new ChuyiCountDownTimer(reserveTime.get() * 1000, 60 * 1000) {
public void onTick(long millisUntilFinished) {
if (!isCancel) {
reserveTime.set(reserveTime.get() - 60);
Global.reserveTime = reserveTime.get();
changeShowTimeAboutReserve();
}
}
public void onFinish() {
if (!isCancel) {
reserveTime.set(-1);
Global.reserveTime = reserveTime.get();
remind.set(free.getContent());
if (!DeviceState.isIsRun() && status.get().equals("running"))
handler.sendEmptyMessage(MESSAGE_BEGIN);
}
}
};
timer.start();
上面代码只需关注
/*开始计时*/
if (timer != null) {
timer.cancel();
timer = null;
}
timer = new ChuyiCountDownTimer(reserveTime.get() * 1000, 60 * 1000) {
public void onTick(long millisUntilFinished) {
// if (!isCancel) {
// reserveTime.set(reserveTime.get() - 60);
// Global.reserveTime = reserveTime.get();
//
// changeShowTimeAboutReserve();
// }
}
public void onFinish() {
// if (!isCancel) {
// reserveTime.set(-1);
// Global.reserveTime = reserveTime.get();
// remind.set(free.getContent());
// if (!DeviceState.isIsRun() && status.get().equals("running"))
// handler.sendEmptyMessage(MESSAGE_BEGIN);
// }
}
};
timer.start();
其中
timer = new ChuyiCountDownTimer(reserveTime.get() * 1000, 60 * 1000)
中的reserveTime.get()是个秒数值,是个int型,*1000,结果为毫秒,这个参数的意义是倒计时总时长,第二个参数是回调onTick函数的间隔时间长,60 * 1000则代表60s就调用一次onTick方法,
//识别结束标识
if(mCurrentTime<=0){
mHandler.sendMessage(mHandler.obtainMessage(MSG));
}
//识别倒计时(第一次降低到时间以内不上报)
if((mMillisInFuture-mCurrentTime)%mCountdownInterval==0 ){
mHandler.sendMessage(mHandler.obtainMessage(MSG));
}
看,计时器中的start方法当mCurrentTime<=0,即计时结束时会发送一次handler消息,第二个当mMillisInFuture-mCurrentTime可以整除mCountdownInterval(即上面的传参60*1000)时就会发送一次handler消息,
if (mCurrentTime <= 0) {
onFinish();
} else {
onTick(mCurrentTime);
}
由上面可以看出,外部调用自定义计时器类时传入两个参数,第一个参数为总时长,第二个参数为onTick调用的间隔时长。
总时长mCurrentTime <= 0就调用onFinish,间隔时长每到达一次mMillisInFuture-mCurrentTime)%mCountdownInterval==0即调用onTick方法,这两个方法在外部会重写(你可以加入自己想重写的逻辑),
三、总结
我为什么不用安卓自带的countdownTimer呢,因为当时客户群体中有安卓六手机,安卓六手机使用自带的计时器时会出现一种情况,用户设置了倒计时30分钟,用户刚刚设置完ui就显示成了29分钟(因为此时用户设置完那一刻已经过了一s,倒计时只剩29min59s了),但是因为ui图上没有秒数的ui,所以导致用户设置完时间,就立刻变成了少一分钟,非常的不合适,安卓六以上系统的计时器没这个问题,不得已自定义了个计时器,解决了这个bug。