带你手把手重读-Handler-源码,聊聊那些你所不知道一二三

}
final MessageQueue queue = me.mQueue;
// 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 (;😉 {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// …
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
// …
}
// …
msg.recycleUnchecked();
}
}

这里其实是一个死循环,它的主要作用是遍历 MessageQueue,获取到 LooperMessageQueue 后,不断通过 MessageQueue 的 next 方法获取到消息列表中的下一个 Message,之后调用了 Message 的 target 的 dispatchMessage 方法对 Message 进行消费,最后对 Message 进行了回收。

通过上面的代码可以看出,Looper 主要的作用是遍历 MessageQueue,每找到一个 Message 都会调用其 target 的dispatchMessage 对该消息进行消费,这里的 target 也就是我们之前发出该 Message 的 Handler。

消息遍历

我们接着看到消息的遍历过程,它不断地从 MessageQueue 中调用 next 方法拿到消息,并对其进行消费,那我们具体看看 next 的过程:

Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;😉 {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 1
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 2
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// 3
if (now < msg.when) {
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;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
// 4
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;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

for (int i = 0; i < pendingIdleHandlerCount; i++) {
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);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}

上面的代码比较长,我们一步步来进行分析

首先在 next 方法内,它在不断地进行着循环,在 1 处它先调用了一次 nativePollOnce 这个 native 方法,它与 Handler 的阻塞唤醒机制有关,我们后面再进行介绍。

之后,在 2 处,它进行了一个非常特殊的处理。这里判断当前的消息是否是 target 为 null 的消息,若 target 为 null,则它会不断地向下取 Message,直到遇到一个异步的消息。到这里可能会有读者觉得很奇怪了,明明在 enqueueMessage 中避免了 Message 的 target 为 null,为什么这里还会存在 target 为 null 的消息呢?其实这与 Handler 的同步屏障机制有关,我们稍后介绍

之后便在注释 3 处判断判断当前消息是否到了应该发送的时间,若到了应该发送的时间,就会将该消息取出并返回,否则仅仅是将 nextPollTimeoutMillis 置为了剩余的时间(这里为了防止 int 越界做了防越界处理)

之后在注释 4 处,第一次循环的前提下,若 MessageQueue 为空或者消息未来才会执行,则会尝试去执行一些 idleHandler,并在执行后将 pendingIdleHandlerCount 置为 0 避免下次再次执行。

若这一次拿到的消息不是现在该执行的,那么会再次调用到 nativePollOnce,并且此次的 nextPollTimeoutMillis 不再为 0 了,这与我们后面会提到的阻塞唤醒机制有关。

消息的处理

消息的处理是通过 Handler 的 dispatchMessage 实现的:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

它优先调用了 Message 的 callback,若没有 callback 则会调用 Handler 中 Callback 的 handleMessage 方法,若其仍没定义则最终会调用到 Handler 自身所实现的 handleMessage 方法。

因此我们在使用的时候可以根据自己的需求来重写上面三者其中一个。

同步屏障机制

Handler 中存在着一种叫做同步屏障的机制,它可以实现异步消息优先执行的功能,让我们看看它是如何实现的。

加入同步屏障

在 Handler 中还存在了一种特殊的消息,它的 target 为 null,并不会被消费,仅仅是作为一个标识处于 MessageQueue 中。它就是 SyncBarrier (同步屏障)这种特殊的消息。我们可以通过 MessageQueue::postSyncBarrier 方法将其加入消息队列。

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++;
final Message msg = Message.obtain();
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;
}
}

可以看到,这里并没有什么特殊的,只是将一个 target 为 null 的消息加入了消息队列中,但我们在前面的 enqueueMessage 方法中也看到了,普通的 enqueue 操作是没有办法在消息队列中放入这样一个 target 为 null 的消息的。因此这种同步屏障只能通过这个方法发出。

移除同步屏障

我们可以通过 removeSyncBarrier 方法来移除消息屏障。

public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 找到 target 为 null 且 token 相同的消息
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "

  • " barrier token has not been posted or has already been removed.");
    }
    final boolean needWake;
    if (prev != null) {
    prev.next = p.next;
    needWake = false;
    } else {
    mMessages = p.next;
    needWake = mMessages == null || mMessages.target != null;
    }
    p.recycleUnchecked();
    // If the loop is quitting then it is already awake.
    // We can assume mPtr != 0 when mQuitting is false.
    if (needWake && !mQuitting) {
    nativeWake(mPtr);
    }
    }
    }

这里主要是将同步屏障从 MessageQueue 中移除,一般执行完了异步消息后就会通过该方法将同步屏障移除。

最后若需要唤醒,调用了 nativeWake 方法进行唤醒。

同步屏障的作用

而看了前面 MessageQueue::next 的代码我们知道,当 MessageQueue 中遇到了一个同步屏障,则它会不断地忽略后面的同步消息直到遇到一个异步的消息,这样设计的目的其实是为了使得当队列中遇到同步屏障时,则会使得异步的消息优先执行,这样就可以使得一些消息优先执行。比如 View 的绘制过程中的 TraversalRunnable 消息就是异步消息,在放入队列之前先放入了一个消息屏障,从而使得界面绘制的消息会比其他消息优先执行,避免了因为 MessageQueue 中消息太多导致绘制消息被阻塞导致画面卡顿,当绘制完成后,就会将消息屏障移除。

阻塞唤醒机制

从前面可以看出来 Handler 中其实还存在着一种阻塞唤醒机制,我们都知道不断地进行循环是非常消耗资源的,有时我们 MessageQueue 中的消息都不是当下就需要执行的,而是要过一段时间,此时如果 Looper 仍然不断进行循环肯定是一种对于资源的浪费。因此 Handler 设计了这样一种阻塞唤醒机制使得在当下没有需要执行的消息时,就将 Looper 的 loop 过程阻塞,直到下一个任务的执行时间到达或者一些特殊情况下再将其唤醒,从而避免了上述的资源浪费。

epoll

这个阻塞唤醒机制是基于 Linux 的 I/O 多路复用机制 epoll 实现的,它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作。
epoll 主要有三个方法,分别是 epoll_createepoll_ctlepoll_wait

epoll_create

int epoll_create(int size)

其功能主要是创建一个 epoll 句柄并返回,传入的 size 代表监听的描述符个数(仅仅是初次分配的 fd 个数)

epoll_ctl

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

其功能是对 epoll 事件进行注册,会对该 fd 执行指定的 op 操作,参数含义如下:

  • epfd:epoll 的句柄值(也就是 epoll_create 的返回值)

  • op:对 fd 执行的操作

  • EPOLL_CTL_ADD:注册 fd 到 epfd

  • EPOLL_CTL_DEL:从 epfd 中删除 fd

  • EPOLL_CTL_MOD:修改已注册的 fd 的监听事件

  • fd:需要监听的文件描述符

  • epoll_event:需要监听的事件

epoll_event 是一个结构体,里面的 events 代表了对应文件操作符的操作,而 data 代表了用户可用的数据。
其中 events 可取下面几个值:

  • EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外部数据来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

其功能是等待事件的上报,参数含义如下:

  • epfd:epoll 的句柄值
  • events:从内核中得到的事件集合
  • maxevents:events 数量,不能超过 create 时的 size
  • timeout:超时时间

当调用了该方法后,会进入阻塞状态,等待 epfd 上的 IO 事件,若 epfd 监听的某个文件描述符发生前面指定的 event 时,就会进行回调,从而使得 epoll 被唤醒并返回需要处理的事件个数。若超过了设定的超时时间,同样也会被唤醒并返回 0 避免一直阻塞。

而 Handler 的阻塞唤醒机制就是基于上面的 epoll 的阻塞特性,我们来看看它的具体实现。

native 初始化

在 Java 中的 MessageQueue 创建时会调用到 nativeInit 方法,在 native 层会创建 NativeMessageQueue 并返回其地址,之后都是通过这个地址来与该 NativeMessageQueue 进行通信(也就是 MessageQueue 中的 mPtr,类似 MMKV 的做法),而在 NativeMessageQueue 创建时又会创建 Native 层下的 Looper,我们看到 Native 下的 Looper 的构造函数:

Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK); //构造唤醒事件的fd
AutoMutex _l(mLock);
rebuildEpollLocked();
}

可以看到,它调用了 rebuildEpollLocked 方法对 epoll 进行初始化,让我们看看其实现

void Looper::rebuildEpollLocked() {
if (mEpollFd >= 0) {
close(mEpollFd);
}
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
}
}

可以看到,这里首先关闭了旧的 epoll 描述符,之后又调用了 epoll_create 创建了新的 epoll 描述符,然后进行了一些初始化后,将 mWakeEventFdmRequests 中的 fd 都注册到了 epoll 的描述符中,注册的事件都是 EPOLLIN

这就意味着当这些文件描述符其中一个发生了 IO 时,就会通知 epoll_wait 使其唤醒,那么我们猜测 Handler 的阻塞就是通过 epoll_wait 实现的。

同时可以发现,Native 层也是存在 MessageQueueLooper 的,也就是说 ative 层实际上也是有一套消息机制的,这些我们到后面再进行介绍。

native 阻塞实现

我们看看阻塞,它的实现就在我们之前看到的 MessageQueue::next 中,当发现要返回的消息将来才会执行,则会计算出当下距离其将要执行的时间还差多少毫秒,并调用 nativePollOnce 方法将返回的过程阻塞到指定的时间。

nativePollOnce 很显然是一个 native 方法,它最后调用到了 Looper 这个 native 层类的 pollOnce 方法。

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;😉 {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
}
if (result != 0) {
if (outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = 0;
if (outData != NULL) *outData = NULL;
return result;
}
result = pollInner(timeoutMillis);
}
}

前面主要是一些对 Native 层消息机制的处理,我们先暂时不关心,这里最后调用到了 pollInner 方法:

int Looper::pollInner(int timeoutMillis) {
// …
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 1
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

// …
return result;
}

可以发现,这里在 1 处调用了 epoll_wait 方法,并传入了我们之前在 natviePollOnce 方法传入的当前时间距下个任务执行时间的差值。这就是我们的阻塞功能的核心实现了,调用该方法后,会一直阻塞,直到到达我们设定的时间或之前我们在 epollfd 中注册的几个 fd 发生了 IO。其实到了这里我们就可以猜到,nativeWake 方法就是通过对注册的 mWakeEventFd 进行操作从而实现的唤醒。

后面主要是一些对 Native 层消息机制的处理,这篇文章暂时不关注,它的逻辑和 Java 层是基本一致的。

native 唤醒

nativeWake 方法最后通过 NativeMessageQueue 的 wake 方法调用到了 Native 下 Looper 的 wake 方法:

void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
ALOGW(“Could not write wake signal, errno=%d”, errno);
}
}
}

这里其实就是调用了 write 方法,对 mWakeEventFd 中写入了 1,从而使得监听该 fdpollOnce 方法被唤醒,从而使得 Java 中的 next 方法继续执行。

那我们再回去看看,在什么情况下,Java 层会调用 natvieWake 方法进行唤醒呢?

MessageQueue 类中调用 nativeWake 方法主要有下列几个时机:

  • 调用 MessageQueue 的 quit 方法进行退出时,会进行唤醒
  • 消息入队时,若插入的消息在链表最前端(最早将执行)或者有同步屏障时插入的是最前端的异步消息(最早被执行的异步消息)
  • 移除同步屏障时,若消息列表为空或者同步屏障后面不是异步消息时

总结

我个人认为,如果你想靠着背面试题来获得心仪的offer,用癞蛤蟆想吃天鹅肉形容完全不过分。想必大家能感受到面试越来越难,想找到心仪的工作也是越来越难,高薪工作羡慕不来,却又对自己目前的薪资不太满意,工作几年甚至连一个应届生的薪资都比不上,终究是错付了,错付了自己没有去提升技术。

这些面试题分享给大家的目的,其实是希望大家通过大厂面试题分析自己的技术栈,给自己梳理一个更加明确的学习方向,当你准备好去面试大厂,你心里有底,大概知道面试官会问多广,多深,避免面试的时候一问三不知。

大家可以把Java基础,JVM,并发编程,MySQL,Redis,Spring,Spring cloud等等做一个知识总结以及延伸,再去进行操作,不然光记是学不会的,这里我也提供一些脑图分享给大家:

希望你看完这篇文章后,不要犹豫,抓紧学习,复习知识,准备在明年的金三银四拿到心仪的offer,加油,打工人!
提升技术。

这些面试题分享给大家的目的,其实是希望大家通过大厂面试题分析自己的技术栈,给自己梳理一个更加明确的学习方向,当你准备好去面试大厂,你心里有底,大概知道面试官会问多广,多深,避免面试的时候一问三不知。

大家可以把Java基础,JVM,并发编程,MySQL,Redis,Spring,Spring cloud等等做一个知识总结以及延伸,再去进行操作,不然光记是学不会的,这里我也提供一些脑图分享给大家:

[外链图片转存中…(img-l0z1hFgn-1720104147372)]

[外链图片转存中…(img-B7R1L2Gb-1720104147373)]

[外链图片转存中…(img-9KiyuIvf-1720104147373)]

希望你看完这篇文章后,不要犹豫,抓紧学习,复习知识,准备在明年的金三银四拿到心仪的offer,加油,打工人!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值