0
点赞
收藏
分享

微信扫一扫

【Android面试查漏补缺】之Handler详解,互联网寒冬

若如初梘 2022-03-11 阅读 75
面试

7. Handler 消息延迟是怎么处理的

Handler 引申的另一个问题就是延迟消息在 Handler 中是怎么处理的?定时器还是其他方法?

这里我们先从事件发起开始看起:

// Handler.java

public final boolean postDelayed(Runnable r, long delayMillis)

{

return sendMessageDelayed(getPostMessage®, delayMillis);

}

public final boolean sendMessageDelayed(Message msg, long delayMillis)

{

// 传入的 time 是 uptimeMillis + delayMillis

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

// …

return enqueueMessage(queue, msg, uptimeMillis);

}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

// 调用 MessageQueue.enqueueMessage

return queue.enqueueMessage(msg, uptimeMillis);

}

从上面的代码逻辑来看,Handler post 消息以后,一直调用到 MessageQueue.enqueueMessage 里,其中最重要的一步操作就是传入的时间是 uptimeMillis + delayMillis。

boolean enqueueMessage(Message msg, long when) {

synchronized (this) {

// …

msg.when = when;

Message p = mMessages; // 下一条消息

// 根据 when 进行顺序排序,将消息插入到其中

if (p == null || when == 0 || when < p.when) {

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else {

// 找到 合适的节点

Message prev;

for (;😉 {

prev = p;

p = p.next;

if (p == null || when < p.when) {

break;

}

}

// 插入操作

msg.next = p; // invariant: p == prev.next

prev.next = msg;

}

// 唤醒队列进行取消息

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

通过上面代码我们看到,post 一个延迟消息时,在 MessageQueue 中会根据 when 的时长进行一个顺序排序。

接着我们再看看怎么使用 when 的。

Message next() {

// …

for (;😉 {

// 通过 epoll_wait 等待消息,等待 nextPollTimeoutMillis 时长

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {

// 当前时间

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

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) { // 说明需要延迟执行,通过; nativePollOnce 的 timeout 来进行延迟

// 获取需要等待执行的时间

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;

msg.markInUse();

return msg;

}

} else {

// No more messages.

nextPollTimeoutMillis = -1;

}

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

// 当前没有消息要执行,则执行 IdleHandler 中的内容

pendingIdleHandlerCount = mIdleHandlers.size();

}

if (pendingIdleHandlerCount <= 0) {

// 如果没有 IdleHandler 需要执行,则去等待 消息的执行

mBlocked = true;

continue;

}

if (mPendingIdleHandlers == null) {

mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];

}

mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

}

// 执行 idle handlers 内容

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

}

}

}

// Reset the idle handler count to 0 so we do not run them again.

pendingIdleHandlerCount = 0;

// 如果执行了 idle handlers 的内容,现在消息可能已经到了执行时间,所以这个时候就不等待了,再去检查一下消息是否可以执行, nextPollTimeoutMillis 需要置为 0

nextPollTimeoutMillis = 0;

}

}

通过上面的代码分析,我们知道了执行 Handler.postDelayd 时候,会执行下面几个步骤:

  1. 将我们传入的延迟时间转化成距离开机时间的毫秒数

  2. MessageQueue 中根据上一步转化的时间进行顺序排序

  3. 在 MessageQueue.next 获取消息时,对比当前时间(now)和第一步转化的时间(when),如果 now < when,则通过 epoll_wait 的 timeout 进行等待

  4. 如果该消息需要等待,会进行 idel handlers 的执行,执行完以后会再去检查此消息是否可以执行

8. View.post 和 Handler.post 的区别

我们最常用的 Handler 功能就是 Handler.post,除此之外,还有 View.post 也经常会用到,那么这两个有什么区别呢?

我们先看下 View.post 的代码。

// View.java

public boolean post(Runnable action) {

final AttachInfo attachInfo = mAttachInfo;

if (attachInfo != null) {

return attachInfo.mHandler.post(action);

}

// Postpone the runnable until we know on which thread it needs to run.

// Assume that the runnable will be successfully placed after attach.

getRunQueue().post(action);

return true;

}

通过代码来看,如果 AttachInfo 不为空,则通过 handler 去执行,如果 handler 为空,则通过 RunQueue 去执行。

那我们先看看这里的 AttachInfo 是什么。

这个就需要追溯到 ViewRootImpl 的流程里了,我们先看下面这段代码。

// ViewRootImpl.java

final ViewRootHandler mHandler = new ViewRootHandler();

public ViewRootImpl(Context context, Display display) {

// …

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,

context);

}

private void performTraversals() {

final View host = mView;

// …

if (mFirst) {

host.dispatchAttachedToWindow(mAttachInfo, 0);

mFirst = false;

}

// …

}

代码写了一些关键部分,在 ViewRootImpl 构造函数里,创建了 mAttachInfo,然后在 performTraversals 里,如果 mFirst 为 true,则调用 host.dispatchAttachedToWindow,这里的 host 就是 DecorView,如果有读者朋友对这里不太清楚,可以看看前面【面试官带你学安卓-从View的绘制流程】说起这篇文章复习一下。

这里还有一个知识点就是 mAttachInfo 中的 mHandler 其实是 ViewRootImpl 内部的 ViewRootHandler。

然后就调用到了 DecorView.dispatchAttachedToWindow,其实就是 ViewGroup 的 dispatchAttachedToWindow,一般 ViewGroup 中相关的方法,都是去依次调用 child 的对应方法,这个也不例外,依次调用子 View 的 dispatchAttachedToWindow,把 AttachInfo 传进去,在 子 View 中给 mAttachInfo 赋值。

// ViewGroup

void dispatchAttachedToWindow(AttachInfo info, int visibility) {

mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

super.dispatchAttachedToWindow(info, visibility);

mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

final int count = mChildrenCount;

final View[] children = mChildren;

for (int i = 0; i < count; i++) {

final View child = children[i];

child.dispatchAttachedToWindow(info,

combineVisibility(visibility, child.getVisibility()));

}

final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();

for (int i = 0; i < transientCount; ++i) {

View view = mTransientViews.get(i);

view.dispatchAttachedToWindow(info,

combineVisibility(visibility, view.getVisibility()));

}

}

// View

void dispatchAttachedToWindow(AttachInfo info, int visibility) {

mAttachInfo = info;

// …

}

看到这里,大家可能忘记我们开始刚刚要做什么了。

我们是在看 View.post 的流程,再回顾一下 View.post 的代码:

// View.java

public boolean post(Runnable action) {

final AttachInfo attachInfo = mAttachInfo;

if (attachInfo != null) {

return attachInfo.mHandler.post(action);

}

getRunQueue().post(action);

return true;

}

现在我们知道 attachInfo 是什么了,是 ViewRootImpl 首次触发 performTraversals 传进来的,也就是触发 performTraversals 之后,View.post 都是通过 ViewRootImpl 内部的 Handler 进行处理的。

如果在 performTraversals 之前或者 mAttachInfo 置为空以后进行执行,则通过 RunQueue 进行处理。

那我们再看看 getRunQueue().post(action); 做了些什么事情。

这里的 RunQueue 其实是 HandlerActionQueue。

HandlerActionQueue 的代码看一下。

public class HandlerActionQueue {

public void post(Runnable action) {

postDelayed(action, 0);

}

public void postDelayed(Runnable action, long delayMillis) {

final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

synchronized (this) {

if (mActions == null) {

mActions = new HandlerAction[4];

}

mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);

mCount++;

}

}

public void executeActions(Handler handler) {

synchronized (this) {

final HandlerAction[] actions = mActions;

for (int i = 0, count = mCount; i < count; i++) {

final HandlerAction handlerAction = actions[i];

handler.postDelayed(handlerAction.action, handlerAction.delay);

}

mActions = null;

mCount = 0;

}

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的14套腾讯、字节跳动、阿里、百度等2020面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

2020面试真题解析
腾讯面试真题解析

阿里巴巴面试真题解析

字节跳动面试真题解析
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

%9B%EF%BC%9F%E5%A6%82%E4%BD%95%E9%9D%A2%E8%AF%95%E6%8B%BF%E9%AB%98%E8%96%AA%EF%BC%81.md),我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

[外链图片转存中…(img-jB9O4Bnc-1646482301044)]
[外链图片转存中…(img-OaSPeIQ3-1646482301046)]

[外链图片转存中…(img-gOSPG9lo-1646482301046)]

[外链图片转存中…(img-04VvINkD-1646482301047)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

[外链图片转存中…(img-kCFoGGhf-1646482301047)]

举报

相关推荐

0 条评论