前言
此前有过一篇关于JAVA层Looper的介绍和MessageQueue的介绍
网络上也有一篇很详细的介绍
用一张图来说明looper运作的机制
looper顾名思义是轮询,轮询的目的是为了查找消息队列(MessageQueue)中的消息并且处理消息(Message)。MessageQueue和Looper是一一对应的关系,一个prepare后的线程中只有一个Looper对象和以及MessageQueue,且线程独有。消息可以是来自Looper所在线程,也可以是来自其他子线程,贴一段代码解释一下
HandlerThread handlerThread = new HandlerThread("test");
handlerThread.start();
Handler mainHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//这里是在主线程中运行的事情
switch (msg.what) {
case 0:
//todo
break;
case 2:
//todo
break;
}
}
};
Handler workHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//这里是在子线程中运行的,如果需要到主线程执行,需要发送message消息到主线程looper中的MQ中
mainHandler.sendEmptyMessage(2);
break;
}
}
};
mainHandler.sendEmptyMessage(0);//主线程发送message消息到主线程looper中的MQ中
workHandler.sendEmptyMessage(1);//主线程发送message消息到子线程looper中的MQ中
轮询底层机制
之前的文章写到了MQ阻塞后没有深入探讨block原因,这里来解惑。之前的流程
Looper中会在queue.next()阻塞
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block 阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
}
}
MessageQueue中nativePollOnce()
Message next() {
final long ptr = mPtr;
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
...
//阻塞,除非到了超时时间或者唤醒
nativePollOnce(ptr, nextPollTimeoutMillis);
}
...}
nativePollOnce阻塞方法:到了超时时间(nextPollTimeoutMillis)或者通过唤醒方式(nativeWake),会解除阻塞状态
- nextPollTimeoutMillis大于等于零,会规定在此段时间内休眠,然后唤醒
- 消息队列为空时,nextPollTimeoutMillis为-1,进入阻塞;重新有消息进入队列,插入头结点的时候会触发nativeWake唤醒方法
为了搞懂nativePollOnce阻塞的原理,我们先来了解Linux epoll机制
epoll机制
这里有篇文章也不错。
概念
epoll是一种I/O事件通知机制,是linux 内核实现IO多路复用的一个实现。
IO多路复用是指,在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。
epoll的通俗解释是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制。
I/O
输入输出(input/output)的对象可以是文件(file), 网络(socket),进程之间的管道(pipe)。在linux系统中,都用文件描述符(fd)来表示。
事件
可读事件,当文件描述符关联的内核读缓冲区可读,则触发可读事件。
(可读:内核缓冲区非空,有数据可以读取)
可写事件,当文件描述符关联的内核写缓冲区可写,则触发可写事件。
(可写:内核缓冲区不满,有空闲空间可以写入)
图解
epoll_create():创建并初始化eventpoll结构体ep,并将ep放入file->private,并返回fd。会在内核的高速cache区中建立一颗红黑树以及就绪链表(该链表存储已经就绪的文件描述符)
epoll_ctl():注册fd到epoll实例上。在执行epoll_ctl的add操作时,不仅将文件描述符放到红黑树上,而且也注册了回调函数,内核在检测到某文件描述符可读/可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中。
epoll_wait():应用进程阻塞在epoll上,超时时长置为-1表示一直等到有目标事件才会返回。epoll_wait只用观察就绪链表中有无数据即可,最后将链表的数据返回给数组并返回就绪的数量
JAVA层Looper
等待消息
Message next() {
final long ptr = mPtr;
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
...
//阻塞,除非到了超时时间或者唤醒,最终会调用到 epoll_wait()方法,等待消息
nativePollOnce(ptr, nextPollTimeoutMillis);
}
...}
生产消息
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
//balabala
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
//nativewake里面会执行write()操作,写入消息
nativeWake(mPtr);
}
}
return true;
}
C++层Looper
线程prepare时会创建Looper和MessgeQueue对象
Looper为STL对象,线程独有,MessageQueue为Looper的成员变量
//创建Looper
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));
}
//创建MQ
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
//新建NativeMQ
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
//新建Looper
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
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);
//epoll操作
rebuildEpollLocked();
}
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
mEpollFd.reset();
}
// Allocate the new epoll instance and register the WakeEventFd.
//创建epoll实例
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);
//监听fd
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
strerror(errno));
for (const auto& [seq, request] : mRequests) {
epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
int epollResult = epoll_ctl(mEpollFd.get(), 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));
}
}
}
从上面的代码我们可以看到创建Looper和MessageQueue对象时,已经完成了
- epoll_create
- epoll_ctl
JAVA层面分析等待消息和消息入queue时也有对应的native操作
等待消息时最终会通过next()方法调用到c++层nativePollOnce
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);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
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);
...
}
这里可以看到最终执行了
- epoll_wait
总结
- Looper使用了epoll机制,创建-> 监听 -> 等待
- 在JAVA层创建Looper和MessageQueue对象时,对应的会通过nativeInit方法到c++侧创建looper和messagequeue对象,并且通过epoll_create和epoll_ctl方法对相关fd进行监听。
- 消息最终会通过write写入
- 消息会通过epoll_wait()阻塞后获取并处理