1.前言
在Android中的消息机制中,Looper几乎无处不在,从Java到Native,程序开发和系统源码中都大量使用到Looper,本文从Looper使用开始,再逐步解析Looper的实现原理
2.Lopper的使用
2.1 Looper的启用
2.1.1 Looper的初始化
Looper是不允许直接new 的,构造函数都是私有的,这么做主要是为了 Looper和线程的绑定关系。 Looper是可以在任一线程中使用的,但是 一个线程只能有一个Looper。Looper的启用是通过Looper.loop()开始的,但是Looper的源码注释中写的很清楚,启用loop前必须先调用Looper.prepare()
frameworks/base/core/java/android/os/Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //注释1
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//注释2
}
-
注释1 静态的
ThreadLocal<Looper>对象,所有线程的Looper 共享的. -
注释2 构造一个Looper对象,并将其存储到 sThreadLocal 中
接下来分析一下ThreadLocal
2.1.2 ThreadLocal
ThreadLocal是通过线程隔离的方式防止任务在共享资源上产生冲突, 线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储
libcore/ojluni/src/main/java/java/lang/ThreadLocal.java
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前的线程
ThreadLocalMap map = getMap(t); // 获取线程的内部变量 threadLocals
if (map != null) {
map.set(this, value); // 不会空,设置value
} else {
createMap(t, value); // 为空,为线程创建一个 ThreadLocalMap,设置 value,并赋给线程的 threadLocals
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
libcore/ojluni/src/main/java/java/lang/Thread.java
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null; //线程的成员变量 threadLocals
这里主要是为每个线程创建一个 ThreadLocal.ThreadLocalMap,并把当前要设置的值 value设置给 ThreadLocalMap,而 ThreadLocalMap 就是一个自定义的hash键值对映射,仅用于维护线程本地值,没有操作导出到ThreadLoacal类之外,hash键值对映射的键是key的虚引用 WeakReference,这样当 key 被回收后,改键值对也会从 ThreadLocalMap 中自动移除。
经过上述逻辑之后,便将 Looper中的静态变量 sThreadLocal:ThreadLocal 的 WeakReference 做为key,新建的Looper做为 key 存储到 当前线程 Thread.threadLocals中。这里的Looper中的变量 sThreadLocal 静态属性很关键,静态所有线程共享的,这样多线程 通过 ThreadLocal.set时设置到Thread.threadLocals 里的key一直都是同一个对象的 WeakReference。Thread.threadLocals能保证线程的私有性,而Looper中的静态的sThreadLocal 保证了key的唯一性, 这样就可以保证存储的内容是线程私有的,且是唯一的。后续 通过 Looper.sThreadLocal.get()就可以获取当前线程的 Looper对象了,从而实现线程和Looper的绑定和一一对应。
2.1.3 Looper的启动
Looper的初始化完之后,启动Looper就比较简单了 直接Looper.loop()就会开始消息轮询和阻塞了,有消息时就会分发消息,无消息时就会进入阻塞,等待 消息来唤醒。这个里面的详细源码 后面 第3章节 会分析。
一个快速启动Looper的示例如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//处理传入进来的消息
}
}
Looper.loop();
}
}
2.2 Looper的退出
Looper不使用了,便需要退出,Looper 提供了一下两种方式退出.
frameworks/base/core/java/android/os/Looper.java
/**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
}
/**
* Quits the looper safely.
* <p>
* Causes the {@link #loop} method to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* However pending delayed messages with due times in the future will not be
* delivered before the loop terminates.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p>
*/
public void quitSafely() {
mQueue.quit(true);
}
-
quit() 不安全的退出 Looper
- 导致 Looper.loop 方法直接终止,不会处理消息队列中的任何其他消息
- 在Looper 被请求退出后,任何尝试将消息发布到队列的操作都将失败。例如,Handler.sendMessage(Message)方法将返回 false
- 使用此方法可能不安全,因为某些消息可能无法在Looper 终止前传递。请考虑使用 quitSafely 来确保所有待处理工作都有序完成。
-
quitSafely() 安全的退出 Looper
- 一旦消息队列中所有剩余的、应该被发送的消息都处理完毕,就会导致Looper.loop 方法终止。但是,在循环终止之前,不会发送未来到期时间的待处理延迟消息.
- 在要求循环器退出后,任何试图将消息发布到队列的尝试都将失败。例如,Handler.sendMessage(Message)方法将返回 false。
这两个方法 官方是建议用 quitSafely ,这样能确保处理完已经到期的所有消息才会退出。但是这两个方法 都不会处理未来(未到到期时间)的消息,且退出后,任何尝试往队列发送消息的操作都会失败。退出的具体源码实现在 第3章节的 源码分析中也会提到。
3.Looper 的源码分析
前面章节已经讲解过Looper的使用了,这里开始从源码上分析Looper的工作原理。Looper是消息轮询器,Handler是消息发送和处理的入口,这里我们 先从消息发送开始,一步步往下分析.
3.1 消息发送
3.1.1 普通消息的发送
Handler.sendMessageXXX最终都会调用到 enqueueMessage 将消息塞到MessageQueue中
frameworks/base/core/java/android/os/Handler.java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; // 注释3, 设置 target,handler post 的消息的target不可能为null,
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
frameworks/base/core/java/android/os/MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
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; // 如果 消息队列阻塞,则需要唤醒
if (p == null) {
mLast = mMessages;
}
} 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); // 注释4
}
}
return true;
}
- 注释3 设置 target,Handler send 的消息的target不可能为null,后续 开启同步屏障 的时候 会用到
- 注释4 判断是否需要唤醒消息队列,一下两种情况需要唤醒
- 当消息队列阻塞时且当前消息要插入到队列最前面,需要唤醒处理刚插入的消息
- 当消息队列阻塞时且开启了同步屏障且消失是异步消息,需要唤醒处理刚插入的异步消息
消息发送主要由Handler将消息按时间顺序塞到MessageQueue的消息队列 mMessages 中,并判断是否需要唤醒,如需要唤醒则会调用 nativeWake,这个后续会讲到。
3.1.2 同步屏障标记消息和异步消息的发送
上面讲了正常消息的发送。但是Android的Handler还可以发送异步消息,开启同步屏障,只处理异步消息。 MessageQueue中的同步屏障 主要是用在 UI 绘制时优先处理的,因为 UI绘制的信号也是通过 Handler发送的,为了确保UI 绘制的优先处理,设计了同步屏障标记,当开启了同步屏障postSyncBarrier,Looper就只处理异步消息,直到移除同步屏障removeSyncBarrier。同步屏障的源码应用在ViewRootImpl中,大家可以翻阅相关源码阅读。
开启同步屏障 postSyncBarrier 是系统 hide api,只有系统可以调用,普通应用是无法使用相关功能,但是了解他的对Looper的原理和View的渲染绘制都有帮助, postSyncBarrier的关键代码如下
frameworks/base/core/java/android/os/MessageQueue.java
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++;
//注意这个msg 是没有 target 的,
final Message msg = Message.obtain(); //注释5
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;
}
}
- 注释5 开启同步屏障其实是 往 MessageQueue 中塞了 一个 Message,但是这个Message 是没有 target,这样和
注释3的普通消息形成了差异,普通消息Message都是有target,开启 同步屏障的标记消息Message是没有target的,这样在 后续 读取消息时就能区分 同步屏障标记消息和普通消息.
开启同步屏障之后,就可以发送异步消息了,此时Looper就只处理异步消息,异步消息的发送比较简单,只需要调用
msg.setAsynchronous(true) 即可.
3.2 读取消息
3.2.1 Java 层的 源码分析
读取消息是从Looper.loop()开始的
frameworks/base/core/java/android/os/Looper.java
public static void loop() {
final Looper me = myLooper();
if (me == null) { // loop前判断Looper是否准备好
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
me.mInLoop = true;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity(); //注释6
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride = getThresholdOverride();
me.mSlowDeliveryDetected = false;
for (;;) { //开启死循环
if (!loopOnce(me, ident, thresholdOverride)) { //注释7
return;
}
}
}
/**
* Poll and deliver single message, return true if the outer loop should continue.
*/
@SuppressWarnings({"UnusedTokenOfOriginalCallingIdentity",
"ClearIdentityCallNotFollowedByTryFinally"})
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); //注释8 might block
...
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting(); // 注释9
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg); // 注释10
if (observer != null) {
observer.messageDispatched(token, msg); // 注释11
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
return true;
}
- 注释6 调用Binder.clearCallingIdentity 清除 IPC通信中其他进程的信息,确保当前的线程的调用 进程信息是当前进程,具体原理可以参考 gityuan.com/2016/03/05/… 这里就不展开了.
- 注释7 开启每次循环获取,如果 Looper 退出了,则返回false,此时退出整个Looper.loop,否则就 获取到消息分发消息或者阻塞等待获取消息。
- 注释8 调用 MessageQueue.next() 来获取消息,这里可能会阻塞,没有消息就会阻塞在这里,这个方法下面会展开分析
- 注释9 开始分发消息前的回调
- 注释10 分发消息处理
- 注释11 分发消息后的回调,这里可以结合
注释9做一些消息分发耗时的监测.
接下来继续跟进 MessageQueue.next() 获取消息
frameworks/base/core/java/android/os/MessageQueue.java
@UnsupportedAppUsage
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; // 注释12
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);// 注释13
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) {// 注释14
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//注释15
}
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);//注释16
} else { //注释17
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
if (prevMsg.next == null) {
mLast = prevMsg;
}
} else {
mMessages = msg.next;
if (msg.next == null) {
mLast = null;
}
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
if (TRACE) {
Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
}
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1; // 注释18
}
// 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; //注释19
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++) { //注释20
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; //注释21
}
}
- 注释12 设置 nextPollTimeoutMillis 为 0,这个参数最终会传入到 nativePollOnce 中,0 表示 不阻塞
- 注释13 native 循环一次获取事件,这个里面涉及到 epoll_wait 逻辑,下面会分析到,这里主要注意 nextPollTimeoutMillis 的取值对应不同的阻塞逻辑。
- nextPollTimeoutMillis=0 不会阻塞,只是执行一次立马就返回
- nextPollTimeoutMillis=-1 会一直阻塞,一直等到有事件输入才唤醒
- nextPollTimeoutMillis>0 至多会阻塞 nextPollTimeoutMillis秒,如果 nextPollTimeoutMillis 秒内都没事件输入,到时候后也会唤醒
- 注释14 因为
message.target == null,这里表示消息头的消息是同步屏障标记消息,和上面的注释5对应,即开启了同步屏障 - 注释15 由于开启了同步屏障,此时优先处理队列里的异步消息,此时会一直遍历找到第一个异步消息,即
msg.isAsynchronous()==true的消息 - 注释16 找到消息判断消息的预计执行时间和当前时间,若执行时间未到,则赋值 nextPollTimeoutMillis 为剩余的时间差,这样 下一个循环
注释13的 nativePollOnce 就会至多阻塞对应的时间就会被唤醒. - 注释17 找到要执行的消息,从队列中移除消息,返回消息。同时赋值
mBlocked = false表示队列没有阻塞,注释4MessageQueue.enqueueMessage时不需要唤醒.
到这里一个非延时消息经Handler发送到MessageQueue中,经Looper.nativePollOnce(0) 之后就能立马获取并进行分发了.
- 注释18 此时消息队列中没有符合要求的消息,即消息队列中的消息为空,或者开启了同步屏障,但是还没有异步消息时,此时的msg便为null. 赋值
nextPollTimeoutMillis = -1,表示需要一直阻塞 nativePollOnce 直到有新的事件或者被唤醒. - 注释19 此时也没有要处理的 IdleHandle,IdleHandle 是在MessageQueue中没有要处理的消息时才会执行的,此时 赋值
mBlocked = true表示如果有消息来了需要唤醒,新消息会走到注释4. 并执行下一个轮询,由于此时 肯定执行了注释18nextPollTimeoutMillis = -1,所以此时下一个轮询的 nativePollOnce(-1) 便会一直阻塞 直到新消息来了被唤醒. - 注释20 有要处理的 IdleHandle,遍历处理IdleHandle,IdleHandle中也不能处理耗时事件,否则会导致整个消息队列被阻塞.
- 注释21 表示处理完了 IdleHandle,因为IdleHandle可能耗时了,所以 赋值
nextPollTimeoutMillis = 0表示下一个轮询 nativePollOnce 需要立马执行完,这样就能快速进入下一次的消息分发.
到这里 整个 Java 层的 消息发送(包括同步屏障和异步消息)的发送和读取源码都分析完了,接下来重点看下 nativePollOnce 这个方法
3.2.2 Native 层的源码分析
Looper中消息分发时机控制主要依赖 nativePollOnce 来控制,nativePollOnce 传入 0时则会立马返回,传入大于0的 nextPollTimeoutMillis 就会最多阻塞 nextPollTimeoutMillis 秒再返回,传入-1时就会一直阻塞,直到被唤醒(一般是nativeWake),接下来重点分析下Native的实现
3.2.2.1 初始化和唤醒
这里还是从初始化开始,Java层MessageQueue初始化时就调用了 nativeInit 进行初始化
frameworks/base/core/java/android/os/MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
...
return reinterpret_cast<jlong>(nativeMessageQueue);
}
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); // 注释22
LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));
AutoMutex _l(mLock);
rebuildEpollLocked(); // 注释23
}
void Looper::rebuildEpollLocked() {
// Close old epoll instance if we have one.
if (mEpollFd >= 0) {
...
mEpollFd.reset();
}
// Allocate the new epoll instance and register the WakeEventFd.
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);//注释24
...
}
Java 层的初始化一步步调用到Native,最终构造了Native 的 NativeMessageQueue 和Looper,这里主要看下Looper的构造函数的初始化逻辑
- 注释22 这里初始化了 一个 eventfd的唤醒fd mWakeEventFd ,主要用来唤醒时写入数据,唤醒epoll_wait的阻塞的
- 注释23,注释24 建立 mWakeEventFd 和 mEpollFd 的关联,通过 epoll_ctl将 mWakeEventFd 添加到 mEpollFd的监听中。
这里简单说一下epoll_create1 和 epoll_ctl 函数
- epoll_create1
int epoll_create1(int size);
参数 size 指定了我们想要通过 epoll 实例来检查的文件描述符个数。该参数只是一个估计值,无需精确指定。
该函数返回一个代表 epoll 实例的文件描述符 epfd,epfd 在 epoll_ctl 和 epoll_wait 中都需要用到,整个 epoll 机制都需要在 epoll 实例上进行操作。
- epoll_ctl
typedef union epoll_data {
void *ptr; // 用户自定义数据
int fd; // 待监控的文件描述符
uint32_t u32; // 一个 32 位整数
uint64_t u64; // 一个 64 位整数
} epoll_data_t;
struct epoll_event {
uint32_t events; // 在该文件描述符上感兴趣的事件集合,用掩码表示
epoll_data_t data; // 用户数据,当描述符 fd 稍后成为就绪态时,可用来指定传回给调用进程的信息
};
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
参数说明:
-
epfd:表示 epoll 实例的文件描述符。
-
fd:指定感兴趣的文件描述符。
-
op:指定需要执行的操作,取值如下:
- EPOLL_CTL_ADD:将描述符 fd 添加到 epoll 实例 epfd 中的兴趣列表中去。
- EPOLL_CTL_DEL:将文件描述符 fd 从 epfd 的兴趣列表中移除。
- EPOLL_CTL_MOD:修改描述符 fd 上设定的事件,需要用到由 ev 所指向的结构体中的信息。
-
ev:指向结构体 epoll_event 的指针。
这样后续唤醒时 只需要调用 mWakeEventFd 写数据就会唤醒 mEpollFd的epoll_wait,这里顺便看一下唤醒的实现,Java层的nativeWake 最终调用的是 Looper::wake()
system/core/libutils/Looper.cpp
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));//注释25
}
- 注释25 调用mWakeEventFd 写入了标记1,这样就能唤醒 下面 mEpollFd的epoll_wait
注释26
其实在Android6.0之间Looper的唤醒使用的不是eventfd,而是pipe,这里主要有以下两点原因
- pipe 本质上是内核空间分配的一个字节流缓冲器,需要预先为期分配至少 4K 的内存空间,这对于简单的事件通知来说是一种空间浪费。而 eventfd 只是内核空间的一个 64位无符号整型计数器,相比 pipe 可以节省很大的内存空间。
- pipe 是单向的,读端和写端是分开的,各需要分配一个文件描述符,对于事件通知模型来说显得过重。且在创建管道的过程中需要通信双方各自关闭未使用的文件描述符,虽属必要,但使用复杂性过高,容易出错。而 eventfd 只需一个文件描述符(内核分配的文件描述符数量是有限的),且对 eventfd 的读操作就是直接清空计数器(如果没有设置信号量原语),操作非常简单高效,几乎就是为事件通知模型量身定做的方案。
3.2.2.2 轮询获取消息
上面分析了初始化和唤醒相关的逻辑,接下来从 nativePollOnce 开始分析 轮询逻辑
frameworks/base/core/jni/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);
}
frameworks/base/core/jni/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);
}
system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
// Poll.
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
// We are about to idle.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);//注释26
// No longer idling.
mPolling = false;
// Acquire lock.
mLock.lock();
.... // 省去部分native 层消息读取和分发的逻辑
return result;
}
这个Looper除了Java层在使用,native层也有很多在使用,包括我们常见的事件分发等,所以这里 隐藏了部分 读取消息后分发事件的逻辑,只重点关注 Java 层的 轮询逻辑
- 注释26 这里利用linux 的 epoll机制,通过 epoll_wait 实现对 mEpollFd 的监控,当mEpollFd里的fd有 信号输入是或者 timeoutMillis>0 超时时间到了就会唤醒,否则就会一直阻塞在这里。
这里简单说一下 epoll_wait函数
- epoll_wait
int epoll_wait(
int epfd,
struct epoll_event *evlist,
int maxevents,
int timeout);
参数说明:
-
epfd:表示 epoll 实例的文件描述符。
-
evlist:是一个返回参数,指向的是返回的有关就绪态文件描述符的信息。其结构体元素的 events 属性表示的是在相应文件描述符上已经发生的的事件。
-
maxevents:evlist 中的元素个数。
-
timeout:用来确定 epoll_wait()的阻塞行为,有如下几种:
- 如果 timeout 等于−1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件产生,或者直到捕获到一个信号为止。
- 如果 timeout 等于 0,执行一次非阻塞式的检查,看兴趣列表中的文件描述符上产生了哪个事件。
- 如果 timeout 大于 0,调用将阻塞至多 timeout 毫秒,直到文件描述符上有事件发生,或者直到捕获到一个信号为止。
调用成功后,epoll_wait()返回数组 evlist 中的元素个数。如果在 timeout 超时间隔内没有任何文件描述符处于就绪态的话,返回 0。出错时返回−1。
通过上面的 epoll_wait和Java层传入到nativePollOnce 的 nextPollTimeoutMillis(大于0,等于0,小于0)就可以精准的控制阻塞时间了,从而实现精准唤醒 去获取下一个消息进行分发处理.
至此 Looper 的初始化和轮询获取消息的源码已经分析完毕。Looper.loop通过调用MessageQueue.nativePollOnce-->Looper::pollOnce-->epoll_wait 来实现 阻塞且不占用CPU资源的。
3.3 Looper 退出
Looper的退出是通过 quit 和quitSafely实现的,我们也简单看一下他的源码实现
frameworks/base/core/java/android/os/Looper.java
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
frameworks/base/core/java/android/os/MessageQueue.java
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(); //注释27
} else {
removeAllMessagesLocked(); //注释28
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr); //注释29
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
mLast = null;
mAsyncMessageCount = 0;
}
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;
mLast = p;
do {
p = n;
n = p.next;
if (p.isAsynchronous()) {
mAsyncMessageCount--;
}
p.recycleUnchecked();
} while (n != null);
}
}
}
Message next() {
...
if (mQuitting) {//注释30
dispose();
return null;
}
...
}
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
if (mQuitting) { //注释31
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;
}
}
...
}
- 注释27 安全退出,此时会遍历Message队列,移除执行时间大于当前时间的未来消息
- 注释28 非安全退出,此时直接移除所有消息
- 注释29 直接唤醒 Looper 中的epoll_wait, 执行下一个nativePollOnce,此时就会处理消息队列中的消息了,当 为安全退出时,消息队列中可能有未处理的消息,此时就会处理。若为非安全退出,则无消息处理.
调用退出之后会设置 标记 mQuitting = true,处理完所有消息便会调用 dispose 的 去释放Looper
- 注释30 处理完消息,调用 nativeDestroy 去释放资源,这里的逻辑就不展开分析,大家可以自行阅读源码。
- 注释31 当标记
mQuitting = true也不再允许往MessageQueue消息队列中添加消息,即Handler.sendMessage 也会报错
4.总结
至此,整个Android系统的消息机制核心部分已经分析完毕,关键逻辑都在Native层的Looper, 主要 通过 epoll 和 eventfd 来实现App层的阻塞和唤醒以便MessageQueue来获取和分发消息,同时这个Looper(.cpp)里面还监听一些其他的fd,还可以实现fd事件的读取和分发(例如输入事件等)。此外,Android 源码中还有很多模块都直接使用/间接了 这个 Looper(.cpp) 来实现事件的异步分发处理,期核心 都是 通过 epoll来实现 fd的监听、阻塞和唤醒。epoll的相关内容大家可以参考 IO多路复用之select, poll, epoll