首先,MessageQueue是属于底层类且它依附于创建他的Looper,除Looper外其他类无法单独创建他,如果要使用他,只能从Looper出获得。
下面将从几方面分析:
1. 消息队列存储原理
跟message中obtain一样,通过一个链表缓存池来存储消息,避免重复创建message对象造成额外的消耗。也同样以“sPool”作为缓存池链表的表头,以“next”作为链表的next指针。
2. 使用jni实现native方法
MessageQueue的源码中调用了许多C/C++方法,使用了jni,这些方法所属的底层C/C++创建了属于native层自己的NativeMessageQueue和NativeLooper消息模型。他们对Java层作用就是控制线程是否阻塞。
// 初始化
private native static long nativeInit();
// 注销
private native static void nativeDestroy(long ptr);
// 让线程阻塞指定时长
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
// 立刻唤醒线程
private native static void nativeWake(long ptr);
// 判断线程是否处于阻塞状态
private native static boolean nativeIsPolling(long ptr);
// 设置文件描述符
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
3. 创建
创建方法只能由Looper调用。涉及代码:
// True if the message queue can be quit.
private final boolean mQuitAllowed;// 是否可以手动退出
/**
* native层中NativeMessageQueue队列指针的地址(0——表示退出队列)
*/
@SuppressWarnings("unused")
private long mPtr; // used by native code
// native层代码,创建native层的 NativeMessageQueue
private native static long nativeInit();
// 构造方法
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();// 执行native方法,初始化
}
其中,mQuitAllowed 表示当前的MessageQueue是否可以手动退出。
Android中要求UI线程不可手动退出,除此之外,其他的线程都可以手动退出,具体操作在Looper和UI线程中。
4. 销毁或退出
退出就是当前这个MessageQueue停止服务,将队列中已存在的所有消息全部清空。看代码:
// 是否退出标记
private boolean mQuitting;
// native退出方法
private native static void nativeDestroy(long ptr);
void quit(boolean safe) {
if (!mQuitAllowed) {// 如果设置Wie不可以手动退出,但调用该方法,抛异常
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {// 如果已经退出,直接结束
return;
}
mQuitting = true;// 改变标记状态
if (safe) {
// 清除掉可能还没有被处理的message(或未到处理时间的message)
removeAllFutureMessagesLocked();
} else {
// 直接粗暴的清空队列中的所有message
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
// 直接粗暴的清空队列中的所有message
private void removeAllMessagesLocked() {
Message p = mMessages;
// 直接死循环,全部回收
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
// 清除掉可能还没有被处理的message(或未到处理时间的message)
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();// 当前时间
Message p = mMessages;
if (p != null) {
if (p.when > now) {// 该message执行时间晚于当前时间,即还未达到执行时间
removeAllMessagesLocked();// 直接回收
} else {
Message n;
/**
* 如果当前消息的预处理时间并不晚于当前时间,说明这个消息可能正在被分发处理,
* 则跳过该消息,继续找
*/
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
// 找到了下一个未执行的消息
break;
}
p = n;// 以这个消息为界,之后的消息都大于当前时间,即都是未执行的消息
}
p.next = null;
// 从消息n开始的消息,全部被回收。
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
其中的逻辑,都在注释中。
5.消息入队方法:enqueueMessage()
说白了就是将消息放入队列,我们知道消息是按其执行顺序排序的,所以肯定会判断when属性。具体代码:
boolean enqueueMessage(Message msg, long when) {
// msg.target即msg所属的handler
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 判断该msg是否处于in-use状态(因为in-use状态不能拿来使用)
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {// 如果当前的messagequeue是退出状态,则抛异常,并释放该msg
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标记为in-use状态
msg.when = when;
Message p = mMessages;// messagequeue的消息头
boolean needWake;// 是否需要唤醒线程
/**
* 判断插入的依据:
* 1. p为空,即当前的messagequeue为空,那直接插入即可
* 2. when=0,表示该msg需要立即执行,需要出入队列的头,最先执行
* 3. when < p.when,虽然不需要立即执行,但它比最先要执行的消息执行时间还要早,也放入队头
*/
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.
// 线程已经被阻塞 && 消息所属handler为异步的
needWake = mBlocked && p.target == null && msg.isAsynchronous();
// 如果以上条件都不满足,就按消息的when属性插入队列中
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;
}
总结一下消息入队的逻辑大致为:
1.检查消息的合法性:包括其所属handler是否为空、是否是in-use状态、队列是否存活;
2. 如果满足条件:队列为空 或 when=0 或 when<队列头的when,则直接将此消息放到队列头
3.如果以上条件都不满足,则从头遍历队列,根据when将消息放到对应的位置。
6.同步消息拦截器
(同步消息拦截器的本质也是一个message对象,只不过他的“target”为空)
除了enqueueMessage()可以向队列中添加消息外,还有一个postSyncBarrier()方法也可以向队列标价消息,但他添加的不是普通的message对象,整个被添加的特殊的Message就是同步消息拦截器。该拦截器会影响同步消息:消息默认都是同步的(只有设置 了setAsychronous(true)后的消息才是异步消息),但不会影响异步消息,这也是他的作用:拦截队列中的同步消息, 放行异步消息. 就好像交警一样, 在道路拥挤的时候决定哪些车可以先通过, 在这里这些先通过的车辆指的就是异步消息。
涉及代码:
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken; // 标识拦截器的token(这也是该拦截器的唯一标识)
/**
* Posts a synchronization barrier to the Looper's message queue.
*
* Message processing occurs as usual until the message queue encounters the
* synchronization barrier that has been posted. When the barrier is encountered,
* later synchronous messages in the queue are stalled (prevented from being executed)
* until the barrier is released by calling {@link #removeSyncBarrier} and specifying
* the token that identifies the synchronization barrier.
*
* This method is used to immediately postpone execution of all subsequently posted
* synchronous messages until a condition is met that releases the barrier.
* Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
* and continue to be processed as usual.
*
* This call must be always matched by a call to {@link #removeSyncBarrier} with
* the same token to ensure that the message queue resumes normal operation.
* Otherwise the application will probably hang!
*
* @return A token that uniquely identifies the barrier. This token must be
* passed to {@link #removeSyncBarrier} to release the barrier.
*
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;// 获得拦截器token
/**
* 实例化拦截器:状态设置为in-use,token设置到属性args1中
*/
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;// 拦截器插入成功,返回token
}
}
/**
* 通过token删除拦截器
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 遍历队列找拦截器,查找条件为:target为空,args1为指定的token
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;// 是否唤醒标记
if (prev != null) {// 队列中移除拦截器
prev.next = p.next;
// 如果prev不等于空说明拦截器前面还有别的消息,就不需要唤醒
needWake = false;
} else {
mMessages = p.next;
// 拦截器在队列头部,移除它之后如果队列空了或者他的下一个消息是正常消息就需要唤醒
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();// 回收
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
// 再次判断是否需要唤醒
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
7.队列空闲处理器:IdleHandler
理解可参考:【Android 异步操作】Handler 机制 ( MessageQueue 空闲任务 IdleHandler 机制 )_韩曙亮的博客-CSDN博客_messagequeue.idlehandler
由于在从队列中取出消息时队里可能是空的,这时候就会阻塞线程等待消息到来,每次队列中没有消息二进入的阻塞状态,就叫“空闲状态”,为了更好的利用资源,也为了更好的掌握线程的状态,于是就有了度低劣空闲处理器——“IdleHandler”。
IdleHandler是一个接口,内部只封装了一个方法:queueIdle();。当一个线程的消息队列为空,或保存在消息队列头部的消息的处理时间大于系统的当前时间时,线程处于一种空闲状态,接下来它会进入睡眠状态。在进入睡眠状态前,线程会发出线程空闲消息给那些注册了的空闲消息处理器来处理。涉及代码:
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
/**
* Add a new {@link IdleHandler} to this message queue. This may be
* removed automatically for you by returning false from
* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
* invoked, or explicitly removing it with {@link #removeIdleHandler}.
*
* <p>This method is safe to call from any thread.
*
* @param handler The IdleHandler to be added.
*/
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
/**
* Remove an {@link IdleHandler} from the queue that was previously added
* with {@link #addIdleHandler}. If the given object is not currently
* in the idle list, nothing is done.
*
* <p>This method is safe to call from any thread.
*
* @param handler The IdleHandler to be removed.
*/
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
8.消息出队方法:next()
直接上代码吧,看注释:
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.
/**
* mPtr 是从native方法中得到的NativeMessageQueue地址,如果mPtr=0,说明队列不存在或被清除掉了
*/
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 待处理的IdleHandler数量,只有第一次初始化时为-1
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 线程被阻塞时间: -1则一直阻塞;0则不阻塞;大于0则为阻塞时长(单位:毫秒)
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {// 如果nextPollTimeoutMillis != 0,说明线程要阻塞了
// 为长时间阻塞做准备:把该释放的对象都释放了
Binder.flushPendingCommands();
}
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) {// 是否为同步拦截器(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) {// 判断是否有可取出的消息
// 如果贷取出的消息还没到被处理的时间,则线程一直阻塞到处理时间
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();// 置为in-use状态
return msg;// 返回消息,结束循环,结束next()方法
}
} else {
// No more messages. 队列中没有可取的消息,则一直阻塞线程
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.
// IdleHandler初始化为:-1,所以在本循环中该条件成立次数<=1
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// 得到IdleHandler的数量
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {// 即没有合适的消息也没有合适的闲时处理
// No idle handlers to run. Loop and wait some more.
mBlocked = true;// 直接进入下次循环阻塞线程
continue;
}
// 说明线程中有待处理的IdleHandler,则从IdleHandler集合中取出IdleHandler
if (mPendingIdleHandlers == null) {
// 初始化待处理的IdleHandler数组的长度为4
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
// 从IdleHandler集合中获取待处理的IdleHandler
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++) {
// 取出一个IdleHandler
final IdleHandler idler = mPendingIdleHandlers[i];
// 释放掉引用
mPendingIdleHandlers[i] = null; // release the reference to the handler
// IdleHandler的执行模式:true为执行一次;false为一直执行
boolean keep = false;
try {
// 获得执行模式
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 通过执行模式,判断是否需要移除掉对应的IdleHandler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;// 处理完所有的IdleHandler,将数量置为0
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
// 因为执行了IdleHandler的代码块,有可能已经有新的消息入队列了,所有这里不阻塞线程,直接去查看是否有新的消息
nextPollTimeoutMillis = 0;
}
}
分析一下核心代码for循环里的逻辑:
1. 根据 nextPollTimeoutMillis 判断是否阻塞线程,初始值为0,不阻塞线程。
2. 将“待取出消息指针”指向队列头。
3. 如果队列头是同步消息拦截器的话,就将“待取出消息指针”指向队列的第一个异步消息。
4. 如果“待取出消息指针”不可用,即msg==null,说明队列中没有可取出的消息,让 nextPollTimeoutMillis = -1,阻塞线程,等待消息到来唤醒它。
5. 如果“待取出消息指针”可用,那再判断消息的待处理时间:
如果消息的待处理时间>当前时间,那让线程阻塞到其执行时间;
如果消息的待处理时间<=当前时间,直接取出返回给调用的地方(此处会直接结束整个循环,结束next()方法)。
6. 如果队列已经退出,那就直接结束next方法
7. 如果是第一次死循环就初始化IdleHandler数量的局部变量:pendingIdleHandlerCount
8. 如果IdleHandler数量<=0,说明没有合适的IdleHandler,直接进入下一次循环阻塞线程(此处会直接结束本次循环)。
9. 初始化IdleHandler数组,里面保存这本地待处理的IdleHandler
10. 遍历IdleHandler数组,执行对应的queueIdle()方法。
11. 执行完所有的IdleHandler之后,将IdleHandler数据清0。
12. 因为执行了IdleHandler的代码块,有可能已经有新的消息入队列了,所有这里不阻塞线程,直接去查看是否有新的消息。
13. 本次循环结束,进行下一次循环。
总结:MessageQueue队列消息是有序的(按消息待处理时间排序);同步拦截器可以拦截他之后的所有同步消息,知道这个拦截器被移除;取出消息时如果没有合适的消息线程会阻塞。
最后附上MessageQueue源码:
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.MessageQueueProto;
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
* <p>You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue {
private static final String TAG = "MessageQueue";
private static final boolean DEBUG = false;
// True if the message queue can be quit.
private final boolean mQuitAllowed;
@SuppressWarnings("unused")
private long mPtr; // used by native code
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
@Override
protected void finalize() throws Throwable {
try {
dispose();
} finally {
super.finalize();
}
}
// Disposes of the underlying message queue.
// Must only be called on the looper thread or the finalizer.
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
/**
* Returns true if the looper has no pending messages which are due to be processed.
*
* <p>This method is safe to call from any thread.
*
* @return True if the looper is idle.
*/
public boolean isIdle() {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
return mMessages == null || now < mMessages.when;
}
}
/**
* Add a new {@link IdleHandler} to this message queue. This may be
* removed automatically for you by returning false from
* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
* invoked, or explicitly removing it with {@link #removeIdleHandler}.
*
* <p>This method is safe to call from any thread.
*
* @param handler The IdleHandler to be added.
*/
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
/**
* Remove an {@link IdleHandler} from the queue that was previously added
* with {@link #addIdleHandler}. If the given object is not currently
* in the idle list, nothing is done.
*
* <p>This method is safe to call from any thread.
*
* @param handler The IdleHandler to be removed.
*/
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
/**
* Returns whether this looper's thread is currently polling for more work to do.
* This is a good signal that the loop is still alive rather than being stuck
* handling a callback. Note that this method is intrinsically racy, since the
* state of the loop can change before you get the result back.
*
* <p>This method is safe to call from any thread.
*
* @return True if the looper is currently polling for events.
* @hide
*/
public boolean isPolling() {
synchronized (this) {
return isPollingLocked();
}
}
private boolean isPollingLocked() {
// If the loop is quitting then it must not be idling.
// We can assume mPtr != 0 when mQuitting is false.
return !mQuitting && nativeIsPolling(mPtr);
}
/**
* Adds a file descriptor listener to receive notification when file descriptor
* related events occur.
* <p>
* If the file descriptor has already been registered, the specified events
* and listener will replace any that were previously associated with it.
* It is not possible to set more than one listener per file descriptor.
* </p><p>
* It is important to always unregister the listener when the file descriptor
* is no longer of use.
* </p>
*
* @param fd The file descriptor for which a listener will be registered.
* @param events The set of events to receive: a combination of the
* {@link OnFileDescriptorEventListener#EVENT_INPUT},
* {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and
* {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested
* set of events is zero, then the listener is unregistered.
* @param listener The listener to invoke when file descriptor events occur.
*
* @see OnFileDescriptorEventListener
* @see #removeOnFileDescriptorEventListener
*/
public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
@OnFileDescriptorEventListener.Events int events,
@NonNull OnFileDescriptorEventListener listener) {
if (fd == null) {
throw new IllegalArgumentException("fd must not be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
synchronized (this) {
updateOnFileDescriptorEventListenerLocked(fd, events, listener);
}
}
/**
* Removes a file descriptor listener.
* <p>
* This method does nothing if no listener has been registered for the
* specified file descriptor.
* </p>
*
* @param fd The file descriptor whose listener will be unregistered.
*
* @see OnFileDescriptorEventListener
* @see #addOnFileDescriptorEventListener
*/
public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
if (fd == null) {
throw new IllegalArgumentException("fd must not be null");
}
synchronized (this) {
updateOnFileDescriptorEventListenerLocked(fd, 0, null);
}
}
private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
OnFileDescriptorEventListener listener) {
final int fdNum = fd.getInt$();
int index = -1;
FileDescriptorRecord record = null;
if (mFileDescriptorRecords != null) {
index = mFileDescriptorRecords.indexOfKey(fdNum);
if (index >= 0) {
record = mFileDescriptorRecords.valueAt(index);
if (record != null && record.mEvents == events) {
return;
}
}
}
if (events != 0) {
events |= OnFileDescriptorEventListener.EVENT_ERROR;
if (record == null) {
if (mFileDescriptorRecords == null) {
mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
}
record = new FileDescriptorRecord(fd, events, listener);
mFileDescriptorRecords.put(fdNum, record);
} else {
record.mListener = listener;
record.mEvents = events;
record.mSeq += 1;
}
nativeSetFileDescriptorEvents(mPtr, fdNum, events);
} else if (record != null) {
record.mEvents = 0;
mFileDescriptorRecords.removeAt(index);
nativeSetFileDescriptorEvents(mPtr, fdNum, 0);
}
}
// Called from native code.
private int dispatchEvents(int fd, int events) {
// Get the file descriptor record and any state that might change.
final FileDescriptorRecord record;
final int oldWatchedEvents;
final OnFileDescriptorEventListener listener;
final int seq;
synchronized (this) {
record = mFileDescriptorRecords.get(fd);
if (record == null) {
return 0; // spurious, no listener registered
}
oldWatchedEvents = record.mEvents;
events &= oldWatchedEvents; // filter events based on current watched set
if (events == 0) {
return oldWatchedEvents; // spurious, watched events changed
}
listener = record.mListener;
seq = record.mSeq;
}
// Invoke the listener outside of the lock.
int newWatchedEvents = listener.onFileDescriptorEvents(
record.mDescriptor, events);
if (newWatchedEvents != 0) {
newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
}
// Update the file descriptor record if the listener changed the set of
// events to watch and the listener itself hasn't been updated since.
if (newWatchedEvents != oldWatchedEvents) {
synchronized (this) {
int index = mFileDescriptorRecords.indexOfKey(fd);
if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
&& record.mSeq == seq) {
record.mEvents = newWatchedEvents;
if (newWatchedEvents == 0) {
mFileDescriptorRecords.removeAt(index);
}
}
}
}
// Return the new set of events to watch for native code to take care of.
return newWatchedEvents;
}
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 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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) {
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;
}
// 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) {
// No idle handlers to run. Loop and wait some more.
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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 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;
}
}
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
/**
* Posts a synchronization barrier to the Looper's message queue.
*
* Message processing occurs as usual until the message queue encounters the
* synchronization barrier that has been posted. When the barrier is encountered,
* later synchronous messages in the queue are stalled (prevented from being executed)
* until the barrier is released by calling {@link #removeSyncBarrier} and specifying
* the token that identifies the synchronization barrier.
*
* This method is used to immediately postpone execution of all subsequently posted
* synchronous messages until a condition is met that releases the barrier.
* Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
* and continue to be processed as usual.
*
* This call must be always matched by a call to {@link #removeSyncBarrier} with
* the same token to ensure that the message queue resumes normal operation.
* Otherwise the application will probably hang!
*
* @return A token that uniquely identifies the barrier. This token must be
* passed to {@link #removeSyncBarrier} to release the barrier.
*
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
/**
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
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) {
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;
} 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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
boolean hasMessages(Handler h, int what, Object object) {
if (h == null) {
return false;
}
synchronized (this) {
Message p = mMessages;
while (p != null) {
if (p.target == h && p.what == what && (object == null || p.obj == object)) {
return true;
}
p = p.next;
}
return false;
}
}
boolean hasMessages(Handler h, Runnable r, Object object) {
if (h == null) {
return false;
}
synchronized (this) {
Message p = mMessages;
while (p != null) {
if (p.target == h && p.callback == r && (object == null || p.obj == object)) {
return true;
}
p = p.next;
}
return false;
}
}
boolean hasMessages(Handler h) {
if (h == null) {
return false;
}
synchronized (this) {
Message p = mMessages;
while (p != null) {
if (p.target == h) {
return true;
}
p = p.next;
}
return false;
}
}
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
void removeMessages(Handler h, Runnable r, Object object) {
if (h == null || r == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h && p.callback == r
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.callback == r
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
void dump(Printer pw, String prefix, Handler h) {
synchronized (this) {
long now = SystemClock.uptimeMillis();
int n = 0;
for (Message msg = mMessages; msg != null; msg = msg.next) {
if (h == null || h == msg.target) {
pw.println(prefix + "Message " + n + ": " + msg.toString(now));
}
n++;
}
pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
+ ", quitting=" + mQuitting + ")");
}
}
void writeToProto(ProtoOutputStream proto, long fieldId) {
final long messageQueueToken = proto.start(fieldId);
synchronized (this) {
for (Message msg = mMessages; msg != null; msg = msg.next) {
msg.writeToProto(proto, MessageQueueProto.MESSAGES);
}
proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked());
proto.write(MessageQueueProto.IS_QUITTING, mQuitting);
}
proto.end(messageQueueToken);
}
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
/**
* A listener which is invoked when file descriptor related events occur.
*/
public interface OnFileDescriptorEventListener {
/**
* File descriptor event: Indicates that the file descriptor is ready for input
* operations, such as reading.
* <p>
* The listener should read all available data from the file descriptor
* then return <code>true</code> to keep the listener active or <code>false</code>
* to remove the listener.
* </p><p>
* In the case of a socket, this event may be generated to indicate
* that there is at least one incoming connection that the listener
* should accept.
* </p><p>
* This event will only be generated if the {@link #EVENT_INPUT} event mask was
* specified when the listener was added.
* </p>
*/
public static final int EVENT_INPUT = 1 << 0;
/**
* File descriptor event: Indicates that the file descriptor is ready for output
* operations, such as writing.
* <p>
* The listener should write as much data as it needs. If it could not
* write everything at once, then it should return <code>true</code> to
* keep the listener active. Otherwise, it should return <code>false</code>
* to remove the listener then re-register it later when it needs to write
* something else.
* </p><p>
* This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
* specified when the listener was added.
* </p>
*/
public static final int EVENT_OUTPUT = 1 << 1;
/**
* File descriptor event: Indicates that the file descriptor encountered a
* fatal error.
* <p>
* File descriptor errors can occur for various reasons. One common error
* is when the remote peer of a socket or pipe closes its end of the connection.
* </p><p>
* This event may be generated at any time regardless of whether the
* {@link #EVENT_ERROR} event mask was specified when the listener was added.
* </p>
*/
public static final int EVENT_ERROR = 1 << 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "EVENT_" }, value = {
EVENT_INPUT,
EVENT_OUTPUT,
EVENT_ERROR
})
public @interface Events {}
/**
* Called when a file descriptor receives events.
*
* @param fd The file descriptor.
* @param events The set of events that occurred: a combination of the
* {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
* @return The new set of events to watch, or 0 to unregister the listener.
*
* @see #EVENT_INPUT
* @see #EVENT_OUTPUT
* @see #EVENT_ERROR
*/
@Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
}
private static final class FileDescriptorRecord {
public final FileDescriptor mDescriptor;
public int mEvents;
public OnFileDescriptorEventListener mListener;
public int mSeq;
public FileDescriptorRecord(FileDescriptor descriptor,
int events, OnFileDescriptorEventListener listener) {
mDescriptor = descriptor;
mEvents = events;
mListener = listener;
}
}
}