Handler 消息机制解析

Handler 消息机制

Looper 对象实例化

主线程中,Looper 的对象创建如下

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
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));
}
MessageQueue

Looper 构造方法中又去创建了 MessageQueue 的对象

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
}

nativeInit 进入到 C++ 层的初始化逻辑

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    ...
    nativeMessageQueue->incStrong(env);
    return reinterpret_cast(nativeMessageQueue);
}

NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

在 C++ 层同样创建了 NativeMessageQueue 对象,内部同时创建了 Looper 对象。

Looper 的对象初始化过程如下

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));
    LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));

    AutoMutex _l(mLock);
    rebuildEpollLocked();
}

其中通过 eventfd 方法创建来打开文件,返回的文件句柄覆值给了 mWakeEventFd。(eventfd 方法具体作用查看下文介绍)

mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)

接着看一下 rebuildEpollLocked 流程

constexpr uint64_t WAKE_EVENT_FD_SEQ = 1;

void Looper::rebuildEpollLocked() {
     ...
    // Allocate the new epoll instance and register the WakeEventFd.
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
    
    epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
    ...
}

这里使用到了 epoll 机制,具体查看下文介绍

其中通过 epoll_create1 创建了 epoll 句柄。通过 epoll_ctl 该方法,去监听 mWakeEventFd 文件句柄上的事件。wakeEvent 所代表的事件是,该文件可读,且文件中的内容为 WAKE_EVENT_FD_SEQ。

至此 Looper 对象初始化完毕。

Looper.loop 方法
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); 
        if (msg == null) {
                return;
         }
        msg.target.dispatchMessage(msg);
    }
}

loop 方法是像 MessageQueue 取消息,消息不为空,则执行消息。

Message next() {
  int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);
        ...
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                       nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
        }
}

MessageQueue 的 next 方法大概流程是 nativePollOnce 方法之后,从 mMessages 中取消息,然后返回,没有消息则再次执行 nativePollOnce。接下来看一下 nativePollOnce 的实现。对应的 C++ 方法如下

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
        ...
    mLooper->pollOnce(timeoutMillis);
    ...
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    ...
    for (;;) {
        ...
        result = pollInner(timeoutMillis);
    }
}
  
int Looper::pollInner(int timeoutMillis) {
    ...
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 等待 epoll 注册事件的上报,当 timeout 为 -1 时,epoll_wait 调用将永远阻塞,直到某个事件发生。当          timeout 为 0 时,epoll_wait 调用将立即返回。
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}

C++ 层 nativePollOnce 的方法依次执行,关键代码到了 Looper::pollInner 方法中,epoll_wait 方法进行等待,等待注册的事件进行上报。如果 timeout 为 -1 ,则会永久阻塞。故 Looper.loop方法执行 nativePollOnce 时,如果没有指定事件发生,则会阻塞住。其实这里就是 Looper 中 消息队列为空时,就会被堵塞会。下面我们看一下发送消息的流程。

Handler 发送消息
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
                        ...
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

通过 Handler 发送消息,会把消息加入到 MessageQueue 的队列中去,并且最后会判断一下然后执行 nativeWake(mPtr) 方法。下面我们看一下 nativeWake 的实现流程。

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

void Looper::wake() {
        ...
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    ...
}

接下来是本篇文章的重点内容

如上所试,nativeWake 方法会执行到 Looper.wake 方法。重点是 write(mWakeEventFd.get(), &inc, sizeof(uint64_t) 方法

还记得上面 C++ 层 Looper 对象的初始化中,通过 eventfd 方法创建来打开文件,返回的文件句柄覆值给了 mWakeEventFd。此时这里 write 则是像对应的文件进行写操作,写入的内容是是 1.

另外 Looper 初始化 rebuildEpollLocked 方法中。利用 epoll 机制去监听 mWakeEventFd 句柄下对应的文件事件。

以及 Looper.loop 中方法中执行 epoll_wait 进行等待对应文件句柄下的事件发生

// Looper 对象初始化中的实现
// epoll 注册句柄以及事件
constexpr uint64_t WAKE_EVENT_FD_SEQ = 1;
epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);

// Looper.loop 中实现
// 进行等待对应文件句柄下的事件发生,事件没来,我就堵塞等待 
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

上面 nativeWake 方法中执行的 write 操作,刚好就是 epoll_wait 所等待的事件。

整个流程大致如下

  1. Looper 创建好 MessageQueue 队列,底层利用 epoll 机制进行句柄和事件注册e 。

  2. Looper.loop 方法从消息队列中取消息,如果消息队列为空,或者消息执行时机没到,那么由于 nativePollOnce

    方法导致堵塞。底层则是由于 epoll_wait 方法等待对应文件句柄下的事件发生,发生堵塞的时长,取决于 timeoutMillis 值。

  3. Handler 方法消息时,把消息加入到消息队列,在 nativeWake 执行流程中,向 epoll_wait 等待文件句柄中的文件中写入内容,从而唤醒由于 epoll_wait 造成的堵塞,再而从消息队列中取消息,最后进行消息的执行 -msg.target.dispatchMessage(msg)

evenefd

简介

在 Linux 系统中,eventfd 是一个用来通知事件的文件描述符,它是内核向用户空间的应用发送通知的机制,可以有效地被用来实现用户空间的事件/通知驱动的应用程序。

简而言之,就是 eventfd 可以用来触发事件通知

int eventfd(unsigned int initval, int flags);

创建一个 eventfd 对象,或者说打开一个 eventfd 的文件,类似普通文件的 open 操作。

该对象是一个内核维护的无符号的 64 位整型计数器。初始化为initval的值。

flags 可以以下三个标志位的 OR 结果:

  • EFD_CLOEXEC:FD_CLOEXEC,简单说就是fork子进程时不继承,对于多线程的程序设上这个值不会有错的。
  • EFD_NONBLOCK:文件会被设置成O_NONBLOCK,一般要设置。
  • EFD_SEMAPHORE:(2.6.30以后支持)支持semophore语义的read,简单说就值递减1。

这个新建的fd的操作很简单:

read(): 读操作就是将counter值置0,如果是semophore就减1。

write(): 设置counter的值。

注意,还支持epoll/poll/select操作,当然,以及每种 fd 都都必须实现的close。

epoll 机制

介绍:epoll 使用一个文件描述符管理多个描述符,将用户空间的文件描述符感兴趣的事件存放到内核的一个事件表中, 来管理用户感兴趣的所有事件。每次调用 epoll_wait 时,无需反复传入用户感兴趣的事件。epoll_wait 系统调用的参数 events 仅用来反馈就绪的事件。

epoll 机制是 Linux 最高效的 I/O 复用机制,在一处等待多个文件句柄的 I/O事件。用户空间和内核空间的 copy 只需要一次。

epoll_create
int epoll_create(int size);

功能:用于创建一个 epoll 的句柄,size 是指监听的描述符个数。

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 poll_event 结构如下

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

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

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

作用:等待事件的上报,当 timeout 为 -1 时,epoll_wait调用将永远阻塞,直到某个时间发生。当 timeout 为 0 时,epoll_wait 调用将立即返回。

maxevents:指定最多监听多少个事件

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

参考文章

Linux下的I/O复用与epoll详解

select/poll/epoll对比分析

Android Handler消息机制底层实现

你可能感兴趣的:(Handler 消息机制解析)