我所理解的Handler消息机制

822 阅读11分钟

消息机制是Android系统中两大基石之一,其中一个是Binder IPC机制,另一个便是消息机制;Android系统使用大量的消息驱动方式来进行交互,比如,Android中四大组件(Activity、Service、BroadcastReceiver、ContentProvider)的启动过程,都离不开消息机制,Android系统,从某种意义上也可以说成是以消息来驱动。

消息机制在Java层面主要涉及到Handler、Looper、MessageQueue、Message这4个类。

  • Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;其中,Message持有用于消息处理的target(Handler);
  • MessageQueue:消息队列的主要功能是向消息池插入消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);其中,MessageQueue持有一组待处理的Message链表(单向链表);
  • Handler:消息辅助类,主要功能是向消息池发送各种消息事件(Handler.sendMessage)和处理相应的消息事件(Handler.handleMessage);其中,Handler持有Looper和MessageQueue;
  • Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者;其中,Looper持有MessageQueue消息队列。

Handler机制如何使用

一个典型的使用场景如下:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        //步骤1:初始化Looper
        Looper.prepare(); 
		
        //步骤2:初始化Handler
        mHandler = new Handler() { 
            public void handleMessage(Message msg) {
                // 在这里处理不同类型的消息
            }
        };

        //步骤3:开启消息循环
        Looper.loop(); 
    }
}

我们再来看一下系统源码中是如何使用Handler的:

// API版本28
// android.app.ActivityThread.java

public static void main(String[] args) {
    //...省略部分无关代码

    //步骤1:初始化主线程Looper
    Looper.prepareMainLooper();

    //...省略部分无关代码

    //步骤2:初始化系统默认Handler 
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    //...省略部分无关代码

    //步骤3:开启主线程消息循环
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

通过上面两部分代码的对比,我们发现,其它步骤都是一样的,只有Looper的初始化不同。简单来说,当我们调用默认无参的prepare()时,实际调用的是重载方法prepare(true),表示这个Looper允许退出;而系统调用的prepareMainLooper(),实际调用的是prepare(false),表示主线程的Looper不允许退出。

步骤1:初始化Looper

我们首先来看一下初始化Looper的逻辑,其中,prepare(boolean)的方法声明如下:

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));
}

我们通过源码发现,每个线程只允许执行一次该方法,当多次执行时线程的TLS中已经有了Looper对象,则会抛出异常;而我们创建的Looper对象,保存到了当前线程的TLS区域中。

ThreadLocal

ThreadLocal:线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。

TLS的常用操作方法:ThreadLocal.set(T value),将value存储到当前线程的TLS区域,源码如下:

// API版本28
// java.lang.ThreadLocal.java

public void set(T value) {
    Thread t = Thread.currentThread(); //获取当前线程
    ThreadLocalMap map = getMap(t); //查找当前线程的本地存储区,也就是t.threadLocals变量
    if (map != null)
        // 以当前ThreadLocal对象为key,把value存储到TLS中
    	map.set(this, value);
    else
        // 如果当前线程本地存储区未初始化,则创建ThreadLocalMap对象,并把value存储其中
    	createMap(t, value);
}

ThreadLocal.get():获取当前线程TLS区域的数据,源码如下:

// API版本28
// java.lang.ThreadLocal.java

public T get() {
    Thread t = Thread.currentThread(); //获取当前线程
    ThreadLocalMap map = getMap(t); //查找当前线程的本地存储区,也就是t.threadLocals变量
    if (map != null) {
    	// 以当前ThreadLocal对象为key,查找存储在TLS中对应的数据
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }

    // 如果TLS为空,则初始化TLS,并调用initialValue()初始化默认值,默认值为null
    return setInitialValue();
}

ThreadLocal的get()和set()方法才做的类型都是泛型。其中,在Looper定义的变量为sThreadLocal,源码如下:

// API版本28
// android.os.Looper.java

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

我们接着Looper的构造方法往下说,源码如下:

// API版本28
// android.os.Looper.java

private Looper(boolean quitAllowed) {
	// 创建MessageQueue对象
    mQueue = new MessageQueue(quitAllowed);
    // 记录当前创建线程
    mThread = Thread.currentThread();
}

我们来看一下MessageQueue的构造方法,源码如下:

// API版本28
// android.os.MessageQueue.java

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    // 通过native方法初始化消息队列,其中mPtr是供native层使用的指针引用
    mPtr = nativeInit();
}

在MessageQueue中涉及到多个native方法,除了MessageQueue的native方法,native层本身也有一套完整的消息机制,用于处理native的消息,在整个消息机制中,MessageQueue是连接Java层和native层的纽带,换言之,Java层可以向MessageQueue消息队列中添加消息,native层也可以向MessageQueue消息队列中添加消息。

在MessageQueue中的native方法如下:

// API版本28
// android.os.MessageQueue.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);

其中,nativeInit()初始化过程的调用链如下:

nativeInit调用链.png 其中,在构建native层的Looper.cpp时,会调用Looper::rebuildEpollLocked()方法,源码如下:

// API版本28
// system/core/libutils/Looper.cpp

void Looper::rebuildEpollLocked() {
    // Close old epoll instance if we have one.
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        close(mEpollFd); //关闭旧的epoll实例
    }

    // Allocate the new epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT); //创建新的epoll实例,并注册wake管道
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    struct epoll_event eventItem;
    // 把未使用的数据区域进行置0操作
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN; // 可读事件
    eventItem.data.fd = mWakeEventFd;
    // 将唤醒事件(mWakeEventFd)添加到epoll实例(mEpollFd)
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                        strerror(errno));

    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        // 将request队列的事件,分别添加到epoll实例
        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}

在上述native层源码中,涉及到一个很重要的一个机制:IO多路复用epoll机制。

epoll机制

select/poll/epoll都是Linux内核中IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作。本质上select/poll/epoll都是同步I/O,即读写是阻塞的。

select缺点:

  • select调用需要传入fd数组,需要拷贝一份到内核,高并发场景下,这样的拷贝消耗的资源是惊人的。(可优化为不复制)

  • select在内核层,仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)

  • select仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)

  • 文件描述符个数受限:单进程能够监控的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义增大上限,但同样存在效率低的弱势;

  • 性能衰减严重:IO随着监控的描述符数量增长,其性能会线性下降。

整个select流程如下:

select流程.webp.jpg

可以看到,这种方式,既做到了一个线程处理多个客户端连接(文件描述符),又减少了系统调用的开销(多个文件描述符只有一次select系统调用 + n次就绪状态的文件描述符的read系统调用)。

poll缺点:

  • poll和select的主要区别就是,去掉了select只能监听1024个文件描述符的限制;select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。同时连接的大量客户端在同一时刻可能只有很少的处于就绪状态,因此,随着监视的描述符数量的增长,其性能会线性下降。

epoll是在Linux内核2.6中提出的,是select和poll的增强版。相对于select和poll来说,epoll更加灵活,没有描述符数量的限制。epoll使用一个文件描述符管理多个描述符,将用户空间的文件描述符的事件存放到内核的一个事件列表中,这样在用户空间和内核空间的copy只需要1次。epoll机制是Linux中最高效的IO多路复用机制,在一处等待多个文件句柄的IO事件。

epoll是最优方案,它解决了select和poll的一些问题。

还记得上面说的select的三个细节么?

  • select调用需要传入fd数组,需要拷贝一份到内核,高并发场景下,这样的拷贝消耗的资源是惊人的。(可优化为不复制)
  • select在内核层,仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)
  • select仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)

所以epoll主要就是针对这三点进行了改进。

  • 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
  • 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步IO事件唤醒。
  • 内核仅会将有IO事件的文件描述符返回给用户,用户无需遍历整个文件描述符集合。

select/poll都只有一个方法,epoll机制操作过程有三个方法:epoll_create()epoll_ctl()epoll_wait()

epoll_create()
int epoll_create(int size)

用于创建一个epoll的句柄,size是指监听的描述符个数,现在内核支持动态扩展,该值的意义仅仅是初次分配的fd个数,后面空间不够时会动态扩容。当创建完epoll句柄后,占用一个fd值。使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

epoll_ctl()
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

用于对需要监听的文件描述符(fd)执行op操作,比如将fd加入到epoll句柄。

  • epfd:是epoll_create()的返回值;
  • op:表示op操作,用三个宏来表示,分别代表添加、删除和修改对fd的监听事件:EPOLL_CTL_ADD(添加)、EPOLL_CTL_DEL(删除)和EPOLL_CTL_MOD(修改);
  • fd:需要监听的文件描述符;
  • epoll_event:需要监听的事件。

其中struct epoll_event结构体的源码如下:

struct epoll_event {
	__uint32_t events;  /* Epoll事件 */
	epoll_data_t data;  /*用户可用数据*/
};

events(表示对应的文件描述符的操作)可取值如下:

  • EPOLLIN:可读(包括对端SOCKET正常关闭);
  • EPOLLOUT:可写;
  • EPOLLERR:错误;
  • EPOLLHUP:中断;
  • EPOLLPRI:高优先级的可读(这里应该表示有额外数据到来);
  • EPOLLET:将EPOLL设为边缘触发模式,这是相对于水平触发来说的;
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后就不再监听该事件。
epoll_wait()
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

用于等待事件的上报;该函数返回需要处理的事件数目,如返回0表示已经超时。

  • epfd:等待epfd上的IO事件,最多返回maxevents个事件;
  • events:用来从内核得到事件的集合;
  • maxevents:events数量,该maxevents值不能大于创建epoll_create()时的size;
  • timeout:超时时间(毫秒,0会立即返回)。

在select/poll机制中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核就会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知(此处去掉了遍历文件描述符,而是通过监听回调的机制。这也正是epoll的魔力所在。)。

epoll优势
  • 监视的描述符数量不受限制,所支持的fd上限是最大可以打开文件得数目,具体数目可以使用cat /proc/sys/fs/file-max命令查看,一般来说,这个数目和系统内存关系很大,以3G内存的手机来说,这个值为20-30万;
  • IO性能不会随着监视fd的数量增长而下降。epoll不同于select和poll中的轮询方式,而是通过每个fd定义的回调函数来实现的,只有就绪的fd才会执行回调函数;
  • 如果没有大量的空闲或者死亡连接,epoll的效率并不会比select/poll高很多;但当遇到大量的空闲连接的场景下,epoll的效率则大大高于select和poll。

步骤2:初始化Handler

我们再来看一下Handler的初始化方法,源码如下:

// API版本28
// android.os.Handler.java

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
	// 匿名类、内部类或本地类都必须声明为static,否则会警告可能出现内存泄漏
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    // 必须先执行Looper.prepare()。才能获取Looper对象,否则为null。
    mLooper = Looper.myLooper(); //从当前线程的TLS中获取Looper对象
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; //把Looper中的消息队列赋值给Handler内部成员变量
    mCallback = callback; //回调方法
    mAsynchronous = async; //设置是否为异步Handler,当该值为true时,通过该Handler发送的所有消息都为异步消息
}

通过上面源码,我们发现,在构建Handler时,并没有做一些特殊的操作,只是获取了当前线程的Looper和MessageQueue赋值给了自己内部的成员变量。

步骤3:开启消息循环

我们最后来看一下Handler消息机制的最核心方法,Looper.loop()的源码如下:

// API版本28
// android.os.Looper.java

public static void loop() {
    final Looper me = myLooper(); //获取TLS中存储的Looper对象
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue; //获取Looper对象中的消息队列

    // 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();
    // 确保在权限检查时基于本地进程,而不是调用进程。
    final long ident = Binder.clearCallingIdentity();

    //...省略部分无关代码

    for (;;) { //进入loop的主循环方法
    	// 当通过MessageQueue获取消息时,可能会阻塞
        Message msg = queue.next(); // might block
        if (msg == null) { // 当获取的消息为null时,则结束消息循环机制
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging; //默认值为null,可通过setMessageLogging()方法来指定输出,用于debug功能
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        //...省略部分无关代码

        try {
            msg.target.dispatchMessage(msg); //通过Message中的Handler来分发Message
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        //...省略部分无关代码

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        // 确保在调度过程中线程的identity未发生变化
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        //回收Message消息,放入Message中的消息缓存池中
        msg.recycleUnchecked();
    }
}

通过上面的源码,我们发现loop()方法的主要核心逻辑为:

  • 读取MessageQueue的下一条Message消息;
  • 通过Message中的target,调用Handler对象中的dispatchMessage()来分发消息;
  • 最后在Message使用完之后,把Message消息回收到缓存池中,以便重复利用。

接下来,我们来看一下MessageQueue中获取下一条消息时都有什么逻辑,也就是MessageQueue.next()的源码:

// API版本28
// android.os.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) { //当消息循环已经退出,则直接返回null
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        //该处调用是Looper线程休眠的核心逻辑;当等待了nextPollTimeoutMillis时长之后,或者消息队列被唤醒,都会从该方法处返回
        //当nextPollTimeoutMillis值为-1时,表示当前消息队列中没有要处理的消息,则会一直休眠下去,直到被唤醒
        //当nextPollTimeoutMillis值为0时,最终会执行到native层的epoll_wait()方法,该方法会立即返回,不会进入休眠
        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.
                
                // 特殊消息类型,表示消息队列中有同步屏障存在;此逻辑会找到同步屏障后第一个异步消息
                // 如果没找到异步消息时,则会把nextPollTimeoutMillis赋值为-1,在下次轮询时,消息队列将进入阻塞
                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; //成功获取到MessageQueue中的下一条将要执行的消息
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1; // 没有消息,进入无线休眠,直到被唤醒
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) { //MessageQueue正在退出,则返回null
                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.
            
            // 能执行到这里,说明消息队列在下一次轮询中马上就要进入休眠状态啦
            // 当消息队列为空,或者还没到下一次消息的执行时间时,给pendingIdleHandlerCount重新赋值,准备执行IdleHandler中相关逻辑
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }

            // 如果没有IdleHandler需要执行,则标记当前消息队列为阻塞状态,进入下一次轮询,下一次轮询时会阻塞
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            // 初始化mPendingIdleHandlers
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            // 给即将要运行的IdleHandler数组任务赋值,通过mIdleHandlers转换为IdleHandler数组
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        // 当前消息队列处于空闲状态,执行IdleHandler逻辑
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // 获取完之后,释放数组中对IdleHandler的引用

            boolean keep = false;
            try {
            	// 执行IdleHandler空闲逻辑,该方法需要返回一个boolean值
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            // 当IdleHandler不需要保存时,keep为false,执行完一次后,从mIdleHandlers移除该任务
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0; //重置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.
        
        // 当IdleHandler执行完时,可能已经到了下一个消息执行的时间,因此,在下一次轮询时无需等待,直接获取下一个消息
        // 当nextPollTimeoutMillis为0时,nativePollOnce()方法底层逻辑会立即返回,不会阻塞休眠
        nextPollTimeoutMillis = 0; 
    }
}

我们通过上面的源码分析发现,在调用nativePollOnce()方法时,有可能会使当前线程进入阻塞休眠状态,具体是由nextPollTimeoutMillis参数来决定的。nativePollOnce()方法的调用链如下:

nativePollOnce调用链.png 其中,native层的核心逻辑Looper::pollOnce()源码如下:

// API版本28
// system/core/libutils/Looper.cpp

/**
 * @param  timeoutMillis [超时时长]
 * @param  outFd         [发生事件的文件描述符]
 * @param  outEvents     [当前outFd上发生的事件,包含4类事件:EVENT_INPUT(可读)、EVENT_OUTPUT(可写)、EVENT_ERROR(错误)和EVENT_HANGUP(中断)]
 * @param  outData       [上下文数据]
 */
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
    	// 先处理没有callback方法的response事件
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) { //ident大于0,则表示没有callback,因为POLL_CALLBACK值为-2,该值定义在Looper.h中
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                        this, ident, fd, events, data);
#endif
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        // 再处理内部轮询
        result = pollInner(timeoutMillis);
    }
}

在上述源码中,pollOnce()方法的返回值,总共有以下几种:

  • POLL_WAKE:表示由wake()触发,即pipe写端的write事件触发;
  • POLL_CALLBACK:表示某个被监听fd被触发;
  • POLL_TIMEOUT:表示等待超时;
  • POLL_ERROR:表示等待期间发生了错误。

我们再来看一下Looper::pollInner()的源码:

int Looper::pollInner(int timeoutMillis) {
	//...省略部分无关代码

    // Poll.
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mPolling = true; //即将处于idle状态

    struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //fd的最大个数为16
    // 核心函数:等待事件发生或超时,当调用nativeWake()方法,向管道写端写入字符,则该方法会返回。
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false; //标识为不再处于idle状态

    // Acquire lock.
    mLock.lock(); //请求锁

    // Rebuild epoll set if needed.
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked(); //epoll重建,直接跳转到Done逻辑
        goto Done;
    }

    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
        result = POLL_ERROR; //epoll事件个数小于0,发生错误,直接跳转到Done逻辑
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = POLL_TIMEOUT; //epoll事件个数等于0,发生超时,直接跳转到Done逻辑
        goto Done;
    }

    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif

    // 循环遍历,处理所有事件
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken(); //已经唤醒了,则读取并清空管道数据
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;

                //处理request,生成对应的response对象,push到响应数组
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;

    // Invoke pending message callbacks.
    // 再处理native层的Message,调用相应的回调方法
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock(); //释放锁

#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
                        this, handler.get(), message.what);
#endif
                handler->handleMessage(message); //处理native层消息事件
            } // release handler

            mLock.lock(); //请求锁
            mSendingMessage = false;
            result = POLL_CALLBACK; //发生回调
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock(); //释放锁

    // Invoke all response callbacks.
    // 处理带有Callback()方法的Response事件,执行Response相应的回调方法
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            // Invoke the callback.  Note that the file descriptor may be closed by
            // the callback (and potentially even reused) before the function returns so
            // we need to be a little careful when removing the file descriptor afterwards.
            
            // 处理请求的回调方法
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq); //移除fd
            }

            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear(); //清除response引用的回调方法
            result = POLL_CALLBACK; //发生回调
        }
    }
    return result;
}

通过上述源码分析,我们发现,pollInner()方法主要的逻辑如下:

  1. 先调用epoll_wait(),这是个阻塞方法,用于等待事件发生或者超时;当调用nativeWake()方法,向管道写端写入字符,则该方法会返回;
  2. 对于epoll_wait()方法的返回,当且仅当以下3种情况出现:
  • POLL_ERROR:发生错误,直接跳转到Done;
  • POLL_TIMEOUT:发生超时,直接跳转到Done;
  • 检测到管道有事件发生,则再根据情况做相应处理:如果是管道读端产生事件,则直接读取管道的数据;如果是其它事件,则处理request,生成对应的response对象,push到response数组;
  1. 进入Done标记位的代码段:
  • 先处理native层的Message,调用native层的Handler来处理该Message;
  • 再处理Response数组,POLL_CALLBACK类型的事件;

从上面的流程中,我们发现,对于Request先收集,一并放入response数组,而不是马上执行。真正在Done开始执行的时候,是先处理native层Message,再处理Request,说明native层Message的优先级高于Request请求的优先级。

另外,在pollOnce()方法中,先处理Response数组中不带Callback的事件,再调用了pollInner()方法。

步骤4:消息发送

当我们通过Handler发送消息时,具体的调用链如下:

sendMessage调用链.png

通过上图,我们发现所有的消息发送方式,最终都调用到了Handler中的私有成员方法enqueueMessage(),在该方法内部最终又调用到了MessageQueue.enqueueMessage()

接下来,我们来看一下MessageQueue.enqueueMessage()中的源码:

// API版本28
// android.os.MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
	// 每一个普通的Message对象,都必须有一个target
    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) { //当前消息队列正在退出,回收Message到消息缓存池中
            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.
            // 当p为null时,说明当前消息队列中没有消息;
            // 或者当前要插入的消息是队列中最早需要执行的,则把该消息插入到消息队列头部
            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); //当前消息队列需要唤醒时,调用native层的唤醒方法
        }
    }
    return true;
}

通过上面Java层插入消息的源码分析,我们发现:

  • 当插入消息时,如果当前消息队列中没有消息,或者当前要插入的消息是队列中最早需要执行的,则把该消息插入到消息队列头部;如果当前消息队列处于阻塞休眠状态的话,则需要唤醒消息队列;
  • 其它情况,将消息按照时间顺序插入到消息队列中时,一般来说,此时并不需要唤醒事件队列;除非,当前消息队列处于阻塞休眠状态,并且消息队列头为同步屏障消息,并且当前要插入的消息为异步消息时,才需要唤醒消息队列。

其中,nativeWake()唤醒方法的调用链如下:

nativeWake调用链.png

我们来看一下native层的核心方法Looper::wake()的源码:

// API版本28
// system/core/libutils/Looper.cpp

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    // 向管道mWakeEventFd中写入字符1
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
                    mWakeEventFd, strerror(errno));
        }
    }
}

在上述源码中,TEMP_FAILURE_RETRY是一个宏定义,当执行write失败后,会不断重复执行,直到执行成功为止;在执行成功后,在阻塞休眠状态的线程,将从epoll_wait()处返回,进而唤醒了整个消息队列,继续处理消息。

总结

MessageQueue通过mPtr变量保存了native层的NativeMessageQueue对象的指针,从而使得MessageQueue成为Java层和Native层的枢纽,既能处理上层消息,也能处理native层消息;下面是Java层与Native层的对应图:

Handler机制Java层与Native层对应图.png

在上图中:

  • 红色虚线关系:Java层和Native层的MessageQueue通过JNI建立关联,彼此之间能相互调用,搞明白这个互调关系,也就搞明白了Java如何调用C++代码,C++代码又是如何调用Java代码。
  • 蓝色虚线关系:Handler/Looper/Message这三大类Java层与Native层并没有任何的真正关联,只是分别在Java层和Native层的handler消息模型中具有相似的功能。都是彼此独立的,各自实现相应的逻辑。
  • 在Native层中,WeakMessageHandler继承于MessageHandler类;NativeMessageQueue继承于MessageQueue类。

另外,消息处理的流程是先处理Native层的Message,再处理Native层的Request,最后处理Java层的Message。理解了该流程,也就明白有时上层信息很少,但响应时间却较长的真正原因。

参考链接:

Android同步屏障机制

select/poll/epoll机制对比分析

Android消息机制-Handler(Java层)

Android消息机制-Handler(native层)

你管这破玩意儿叫IO多路复用?

深入揭秘epoll是如何实现IO多路复用的!