昨天碰到一个关于handler的问题,当我使用sendEmptyMessageDelayed这个方法,在手机熄屏的情况下并没有按时发送消息,而且中间的时间还不固定,有时候短,有时候长,但是在亮屏和充电情况下,没有该问题。
难道handler的延时本身就不准?带着疑问我看了下handler的运行机制,找到了答案!
本篇文章默认为你已经知道handler的使用发法,所以我不会对handler的基础只是做介绍了。
当我使用sendEmptyMessageDelayed的时候,源码依次的调用顺序是;
sendEmptyMessageDelayed(int what, long delayMillis) —>
sendMessageDelayed(Message msg, long delayMillis) —>
sendMessageAtTime(Message msg, long uptimeMillis) —>
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
其中enqueueMessage调用的MessageQueue的boolean enqueueMessage(Message msg, long when),接下来我们来看看该函数。
文件:MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//退出时,回收msg,放到消息池
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
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; //这时为true
} else {
// 插入到队列中间,经常我们不需要唤醒事件队列,除非队列的
// 头部有一个障碍或者消息是该队列最早的异步消息
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); //唤醒线程
}
}
return true;
}
从代码可以看出该类的作用是把消息按时间顺序排序,并且控制线程的唤醒,但是我们对mBlocked和handler是怎么记时,还是不清楚,接来我们来看看Loop是怎么取消息的
我们看Loop.loop()就可以知道,取消息是这行代码:
Message msg = queue.next(); // might block
所以我们再看看next()
文件:MessageQueue.java
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // 第一个迭代时为 -1
int nextPollTimeoutMillis = 0; // 阻塞的时间
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞操作
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 获取系统启动后,到现在的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 查找下一条异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果时间未到,设置下一轮需要等待的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 得到消息
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 {
// 没有消息
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有idle handlers 在运行,loop需要等待
mBlocked = true; // 可以控制是否需要唤醒线程
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置idle handler 的数量为0,这样我们就不会再次运行
pendingIdleHandlerCount = 0;
// 当调用一个空闲的handler时, 一个新的消息可以被分发
// 因此可以不需要等待,直接查询pending message
nextPollTimeoutMillis = 0;
}
}
从这个方法中可以看出,handler获取时间的方式是调用SystemClock.uptimeMillis(),并用它和消息的里包含的时间进行对比。
同时next()方法内部如果有阻塞,会把mBlocked设置true,在下一个Message进队列时会判断这个message的位置,如果在队首就会调用nativeWake()方法唤醒线程。
综上所述:
1. 如果我sendEmptyMessageDelayed发送了消息A,延时为500ms,这时消息进入队列,触发了nativePollOnce,Looper阻塞,等待下一个消息,或者是Delayed时间结束,自动唤醒;
2. 在1的前提下,紧接着又sendEmptyMessage了消息B,消息进入队列,但这时A的阻塞时间还没有到,于是把B插入到A的前面,然后调用nativeWake()方法唤醒线程
3. 唤醒之后,会重新都取队列,这是B在A前面,有不需要等待,于是直接返回给Looper
4. Looper处理完该消息后,会再次调用next()方法,如果发现now大于msg.when则返回A消息,否则计算下一次该等待的时间
看到这里你可能想说,这和我的问题没什么关系啊,别急,答案就在SystemClock.uptimeMillis(),handler是通过它来获取时间的,但uptimeMillis()是不包括休眠的时间的,所以手机如果在休眠状态下(android 7.0在灭屏情况下很容易进入休眠),那时间就一直不变,至于中途又发送消息了,那是因为手机被唤醒了(android7.0会定时唤醒手机,接收消息),这时执行完delay操作,就可以发送消息了。