关于安卓Handler之延时我不准时
背景
最近在做一个小功能,其中涉及到一个延时处理逻辑,如果是通过定时去轮询并且执行,那么就会导致一个耗电问题,所以定时轮询是不实际的,所以暂停的思路,就是通过延时实现。
思考
安卓延时,好家伙,一看还能有几个能实现,第一个handler,第二个AlarmManager,还有一些什么thread pool interval之类的。本文只会讲述,Handler实现,其他后续有机会再讲解。
开发环境
win10,as4+,jdk8+
过程
文末将会放出,所有关联的代码
要点
有小伙子可能会发现,哟,这个不是手到拈来吗,直接handler postDelay就行了啊,何必再浪费口舌?的确,这种思路上是没有任何问题,有问题的是,系统的cpu会休眠,如果handler delay的时间过长,那么就会导致时间不准。
举个例子,如果业务上面,需要延时10个小时,直接用handler的delay实现,你会发现百分之九十九都是不准的。一般情况,都会延时个几分钟,甚至十几分钟。导致这种原因,就是cpu休眠以及应用冻结等方面的影响,具体源码不再一一列出了。
还有一种场景,有一个业务,handler做定时器,一秒执行一次,如果手机机型了锁屏,正常情况,handler也会失效,这也和系统的休眠机制有关系。所以,这里就涉及到一个结束时间的概念,要以结束时间为准,而不是算好delay的次数为准。
实现
那么,有了以上的问题,应该要怎么去实现,怎么去避免?目前有一个大概的思路,就是把延时的时间,就行一个“阶梯划分”。
举例:如果你要延时一个小时执行,那么就不能直接delay一个小时,我们需要划分为,先delay个半小时,然后半小时到了,再计算剩余时间,然后再delay个十五分钟,同样地,最后粒度越来越小,这种就能够保证大部分情况下,延时的准确性,当然,这是大部分情况。博主实测,应用挂后台24小时,延时任务依旧按时执行。
这个逻辑,虽然有点麻烦,但是按道理是实用的。不过这种实现方式,不适用于对时间要求很高的场景。
下面是全部关联的代码,博主把它们封装成为一个类了:
package com.example.demo.utils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.Serializable;
/**
* FileName: HandlerDelayer
* Author: lzt
* Date: 2024/10/31 10:06
* Description:延时工具类
*/
public class ModuleTimerHandlerDelayer implements Serializable {
private static final String TAG = ModuleTimerHandlerDelayer.class.getSimpleName();
private final Handler mDelayHandler;
private DelayCallback mDelayCallback;
//延时阈值
private static final long DELAY_THRESHOLD = 2 * 1000 * 60;
//多线程锁
private final Object SYNC_LOCK = new Object();
//是否结束了
private boolean isRelease = false;
public ModuleTimerHandlerDelayer() {
mDelayHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Object messageObj = msg.obj;
if (!(messageObj instanceof DelayClass)) {
return;
}
DelayClass delayClass = (DelayClass) messageObj;
if (!delayClass.isSegment()) {
//不用切割,直接回调
if (getDelayCallback() != null) {
getDelayCallback().runTask(delayClass.getMessage());
}
return;
}
//需要切割,重新切割
long endTime = delayClass.getEndTime();
long nextDelay = getDelayTime(endTime);
if (nextDelay <= 0) {
if (getDelayCallback() != null) {
getDelayCallback().runTask(delayClass.getMessage());
}
return;
}
//存在延时,继续delay
Message obtMessage = mDelayHandler.obtainMessage();
obtMessage.what = delayClass.messageWhat;
obtMessage.obj = delayClass;
mDelayHandler.sendMessageDelayed(obtMessage, nextDelay);
}
};
}
private String createMessageKey() {
return String.valueOf(System.currentTimeMillis());
}
public DelayCallback getDelayCallback() {
return mDelayCallback;
}
public Message getMessage() {
return mDelayHandler.obtainMessage();
}
/**
* 传入结束时间,计算delay
*/
private long getDelayTime(long endTime) {
if (System.currentTimeMillis() >= endTime) {
//已经结束了
return 0L;
}
long interval = endTime - System.currentTimeMillis();
if (interval > 1000 * 60 * 60L * 2L) {
//大于两小时
return 1000 * 60 * 60L;
} else if (interval > 1000 * 60 * 30L) {
//大于三十分钟
return 1000 * 30L;
} else if (interval > 1000 * 60 * 10L) {
//大于十分钟
return 1000 * 10L;
} else if (interval > 1000 * 60L) {
//大于一分钟
return 1000 * 20L;
} else {
return interval;
}
}
/**
* 延时包装类
*/
private static class DelayClass implements Serializable {
private final int messageWhat;
private final long startTime;
private final long endTime;
private final long delayTime;
private final Message message;
private final String key;
//是否为分段延时
private boolean isSegment = false;
private DelayClass(String key, int messageWhat, long startTime, long endTime, long delayTime, Message message) {
this.messageWhat = messageWhat;
this.key = key;
this.startTime = startTime;
this.endTime = endTime;
this.delayTime = delayTime;
this.message = message;
}
public boolean isSegment() {
return isSegment;
}
public void setSegment(boolean segment) {
isSegment = segment;
}
public String getKey() {
return key;
}
public long getEndTime() {
return endTime;
}
public int getMessageWhat() {
return messageWhat;
}
public long getStartTime() {
return startTime;
}
public long getDelayTime() {
return delayTime;
}
public Message getMessage() {
return message;
}
}
public interface DelayCallback {
void runTask(Message message);
}
//外部回调--------------------------------------------------------------------
public void setDelayCallback(DelayCallback delayCallback) {
mDelayCallback = delayCallback;
}
//外部调用--------------------------------------------------------------------
public void delay(long delay, Message message) {
if (isRelease) {
return;
}
new Thread(new Runnable() {
@Override
public void run() {
synchronized (SYNC_LOCK) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
String messageKey = createMessageKey();
Log.d(TAG, "delay start messageKey: " + messageKey);
int what = message.what;
long endTime = System.currentTimeMillis() + delay;
DelayClass delayClass = new DelayClass(messageKey, what, System.currentTimeMillis(), endTime, delay, message);
if (delay <= DELAY_THRESHOLD) {
//直接发送
delayClass.setSegment(false);
Message obtMessage = mDelayHandler.obtainMessage();
obtMessage.what = delayClass.messageWhat;
obtMessage.obj = delayClass;
mDelayHandler.sendMessageDelayed(obtMessage, delayClass.delayTime);
return;
}
//分段延时
delayClass.setSegment(true);
Message obtMessage = mDelayHandler.obtainMessage();
obtMessage.what = delayClass.messageWhat;
obtMessage.obj = delayClass;
long delayTime = getDelayTime(endTime);
mDelayHandler.sendMessageDelayed(obtMessage, delayTime);
}
}
}).start();
}
public void emptyDelay(int what, long nextTaskDelayTime) {
synchronized (SYNC_LOCK) {
String messageKey = createMessageKey();
long endTime = System.currentTimeMillis() + nextTaskDelayTime;
Message orgMessage = getMessage();
orgMessage.what = what;
DelayClass delayClass = new DelayClass(messageKey, what, System.currentTimeMillis(), endTime, nextTaskDelayTime, orgMessage);
delayClass.setSegment(true);
Message obtMessage = mDelayHandler.obtainMessage();
obtMessage.what = delayClass.messageWhat;
obtMessage.obj = delayClass;
mDelayHandler.sendMessageDelayed(obtMessage, delayClass.delayTime);
}
}
public void release() {
isRelease = true;
mDelayHandler.removeCallbacksAndMessages(null);
}
}
代码解析
对于上面代码核心的思路,就是通过一个wrapper,进行包装外部传入的message,同时把结束时间也带上,方便后续的结束时间重新计算。聪明的你们,估计一看代码,就会恍然大悟了。
注意,用完以后,记得release哦
that’s all---------------------------------------------------