Android系统下Looper深度解析

2 阅读22分钟

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 表示队列没有阻塞,注释4 MessageQueue.enqueueMessage 时不需要唤醒.

到这里一个非延时消息经Handler发送到MessageQueue中,经Looper.nativePollOnce(0) 之后就能立马获取并进行分发了.

  • 注释18 此时消息队列中没有符合要求的消息,即消息队列中的消息为空,或者开启了同步屏障,但是还没有异步消息时,此时的msg便为null. 赋值 nextPollTimeoutMillis = -1,表示需要一直阻塞 nativePollOnce 直到有新的事件或者被唤醒.
  • 注释19 此时也没有要处理的 IdleHandle,IdleHandle 是在MessageQueue中没有要处理的消息时才会执行的,此时 赋值 mBlocked = true 表示如果有消息来了需要唤醒,新消息会走到注释4. 并执行下一个轮询,由于此时 肯定执行了 注释18 nextPollTimeoutMillis = -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,这里主要有以下两点原因

  1. pipe 本质上是内核空间分配的一个字节流缓冲器,需要预先为期分配至少 4K 的内存空间,这对于简单的事件通知来说是一种空间浪费。而 eventfd 只是内核空间的一个 64位无符号整型计数器,相比 pipe 可以节省很大的内存空间。
  2. 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