之前在看分析Handler实现原理的,有注意到sendMessageDelayed这个方法,最终都调用到了MessageQueue的enqueue方法,最终都到MessageQueue里面的next方法,至于具体怎么实现delay的,没有仔细研究,今天记录一下
主要包括2部分的内容:
- Handler sendMessageDelayed实现原理
- 延时任务实现的几种方式(针对sendMessageDelayed的一些扩展思考)
Handler的实现原理,可以参考我之前写的 android Handler消息机制实现原理 ,对整体流程有个了解,从消息是怎么从应用到framewokr再到native,以及怎么从native到framework再到应用的handleMessage方法处理的
本文主要分析sendMessageDelay的实现细节,也是穿插在Handler消息机制实现原理,主要区别在于Message.when参数的值不一样,默认是0,delay消息是传入的值+当前时间
// frameworks/base/core/java/android/os/Handler.java
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
1.Handler sendMessageDelayed实现原理
1.1 Message.when
关于Message的其它几个参数,如what/obj/target等几个参数,平时用得比较多,when参数基本上没怎么用过。先看类里面的定义,不支持App调用
/**
* The targeted delivery time of this message. The time-base is
* {@link SystemClock#uptimeMillis}.
* @hide Only for use within the tests.
*/
@UnsupportedAppUsage
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long when;
when属性的含义是:分发当前Message的具体时间
具体的使用是在MessageQueue方法里面的,sendMessageAtTime最终会调用到queue.enqueueMessage
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
1.2.MessageQueue.enqueueMessage
在enqueueMessage方法里面,会把传入的参数when赋值给Message.when
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
// when的值为SystemClock.uptimeMillis() + delayMillis
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
return true;
}
然后把消息加入到MessageQueue的消息队列里面,分2种情况处理
- 队列为空,直接插入并返回
- 队列非空,加入队列,然后在一个循环里面来判断是否有消息的时候到了需要唤醒阻塞在nativepollOnce的
mBlocked的定义如下:表示在next方法中是否blocked在pollOnce方法
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
1.3.MessageQueue.next
在Looper调用Looper.loop方法时,会阻塞在MessageQueue.next方里面的nativePollOnce方法上
这个方法,是整个Handler消息机制能循环获取消息的核心,利用epoll机制来实现,关于epoll的实现原理,可以在网上找到很多,在这里只是简单的说明一下 来源于linux man epoll_wait
epoll_wait方法的定义如下:
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
参数timeout是用来实现延时的核心,原理是当epoll_wait的timeout到期时,会唤醒epoll_wait
A call to epoll_wait() will block until either:
- a file descriptor delivers an event; // 插入消息走的是这个唤醒
- the call is interrupted by a signal handler; or
- the timeout expires. // delay走的是这个唤醒
下面来分析一下,framework的Message.when是怎么传到入epoll_wait的timeout参数上的
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// 通过when来判断是否应该分发消息
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
next方法,会从消息队列里面取出消息,如果有取到消息,则会在Looper.loop方法里面,把消息分发到对应的Handler去处理
在 android Handler消息机制实现原理 里面有分析过,当插入一条消息后,会唤醒epoll_wait方法,从native层回到framework的next方法,走nativePollOnce之后的逻辑
从前面的分析可以知道,when的值为SystemClock.uptimeMillis() + delayMillis,以下判断为true
if (now < msg.when)
重新设置nextPollTimeoutMillis时间,为我们最初在sendMessageDelayed里面设置的delayMillis
当delay的时间到了后,就和正常的消息处理流程完全一样了
1.4.MessageQueue.nativePollOnce
nativePollOnce是一个native方法,实现是在jni层的android_os_MessageQueue.cpp
// android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
最终调用到system/core/libutils/Looper.cpp的pollInner
int Looper::pollInner(int timeoutMillis) {
...
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
}
在epoll_wait方法里面,timeoutMillis设置为在sendMessageDelayed里面设置的delayMillis,然后利用epoll_wait的超时机制来实现延时处理
2. 延时任务实现的几种方式
除了上面的利用epoll_wait的超时机制,还可以使用ScheduledThreadPoolExecutor和Timer来实现
提到延时任务处理,正好想到了最近在看RxJava里面的实现,线程池基本上是利用ScheduledThreadPoolExecutor来实现的
类的定义如下:
/**
* A {@link ThreadPoolExecutor} that can additionally schedule
* commands to run after a given delay, or to execute
* periodically. This class is preferable to {@link java.util.Timer}
* when multiple worker threads are needed, or when the additional
* flexibility or capabilities of {@link ThreadPoolExecutor} (which
* this class extends) are required.
* /
在ThreadPoolExecutor的基础中扩展了对周期性任务的支持,在多线程场景下,建议使用ScheduledThreadPoolExecutor来替换Timer
ScheduledThreadPoolExecutor由3部分组成:
- 任务调度控制:ScheduledThreadPoolExecutor,负责任务调度控制,实现了ScheduledExecutorService接口;
- 阻塞队列:DelayedWorkQueue,作为ScheduledThreadPoolExecutor的内部类,用于缓存线程任务的阻塞队列,仅能够存放RunnableScheduledFuture对象;该队列实现了延迟调度任务的逻辑,如果当前时间大于等于任务的延迟执行时间,任务才可以被调度。
- 调度任务:ScheduledFutureTask,作为ScheduledFutureTask的内部类,实现了RunnableScheduledFuture,封装了调度任务的执行逻辑。其中的time字段存放下一次执行时间,DelayedWorkQueue会据此判断任务是否可以被执行。period字段存放执行周期,对于周期性执行任务,每次会根据period计算time。
ScheduledThreadPoolExecutor的任务执行分为单次执行和周期性执行
- 单次执行:通过schedule方法执行的任务属于单次执行任务
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
- 周期性执行:通过scheduleAtFixedRate、scheduleWithFixedDelay方法执行的任务均为周期性执行任务
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
DelayedWorkQueue的实现原理:
- 基于最小二叉堆实现的优先队列,根据ScheduledFutureTask.compareTo方法比较任务执行时间,使得最近要执行的任务位于队首;
- 任务出队时,通过轮询判断任务是否到达执行时间点,ScheduledFutureTask实现了Delayed接口,通过getDelay方法能够获取到任务还有多长时间执行;
- 当队列中所有任务都没有到达执行时间时,队列中会维持一个leader线程,用于轮询等待队首任务,其余线程均await()。
- ScheduledFutureTask增加heapIndex属性,用于标记任务在堆数组中的索引,从而便于任务的快速查找(是否存在)与取消(删除)
leader线程的实现,使用了**“Leader/Follower” 设计模式** , 一些解释可以参考 explain-leader-follower-pattern
核心思想:
leader线程相当于一个哨兵,其它线程都处于休眠状态,只有这一个线程在监听任务队列,当leader线程A开始工作前,先唤醒一个线程B来充当leader线程,然后线程A执行并完成任务后,进入休眠状态,等待下次被唤醒