Handler延迟消息执行机制堵塞实现机制

Handler延迟消息执行机制,会阻塞吗?

注:本篇文章为个人记录使用,只为后期方便知识点快速查阅到,如有不对请及时告知更正

面试经常被问到到问题

  1. sendMessageDelayed是如何实现延时发送消息的?
  2. sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?

sendMessageDelayed方法原理

sendMessageDelayed延时处理消息

sendMessageDelayed方法在Handler内部用的地方很多,比如:sendMessage、sendEmptyMessageDelayed、post、postDelayed方法最后都是调用的 sendMessageDelayed方法作为处理的。全部方法体如下:

    public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

Handler延迟消息执行机制堵塞实现机制_第1张图片
注意红框标注部分:这里先获取到当前时间的long值,并将long值同给定的延时时长进行相加,生成一个新的时间点long值。

问题解答

  1. 如何实现延时的
    是通过线程堵塞实现延时的(个人感觉其实是实现线程休眠),当Message需要延时时,Looper拿到消息不做处理,进行睡眠(也就是实现自我堵塞),直到时间到了或者由新的Message进入队列了。这里的线程堵塞其实是使用到了 Linux系统里面epoll机制,也就是IO多路复用机制。这个比较高深没研究那么深

  2. 阻塞后会不会影响新添加的Message呢
    答案当然是: 不会影响新添加的Message 又被问到为什么呢 (这里就得看下面的代码了),最总总结就是,当添加新的Message时会执行唤起Looper操作,这时looper取出头部的Message(A)一判断还没有到执行时间,执行堵塞操作,在取出Message(B)需要立即操作的,将B 设置到A的前面,执行Message。

主要代码解读模块:

代码追踪

由源码可见 sendMessageDelayed -> **sendMessageAtTime(Message msg, long uptimeMillis) ** -> enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) -> queue.enqueueMessage(msg, uptimeMillis)

A.**sendMessageAtTime(Message msg, long uptimeMillis) **

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

B.**enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) **

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //主要这句代码   将Handler本身复制给 Message的  target字段
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //这句代码才是核心 
        return queue.enqueueMessage(msg, uptimeMillis);
    }

将Message插入到MessageQueue中

boolean enqueueMessage(Message msg, long when) {
        ...//代码较多,省去了部分抛出异常的代码
        synchronized (this) {
            ...
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //这里的 p 是当前MessageQueue队首Message
            //这个判断是  问题 2 的实现部分,  当 头Message为 null || 给定的时间戳 == 0  || 给定的时间戳 小于 头部Message的时间戳
            if (p == null || when == 0 || when < p.when) {
                // 如果头部Message处于阻塞状态  将头部设置到当前Message后面 
                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;
            }

            //最终是否唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

在后面next()方法内部,如果有阻塞(没有消息了或者只有Delay的消息),会把mBlocked这个变量标记为true(下面会说明next方法),在下一个Message进队时会判断这个message的位置,如果在队首并且时间满足条件,会调用nativeWake()方法唤醒线程!

Looper 取出消息

Looper内循环读取Message,looper的loop方法

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
}

Queue.next方法可能被阻塞,所以最终调用还是queue的next方法。

Message next() {
        final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

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

        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.
                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;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                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.
            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);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        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;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

取出消息,如果Message不满足执行条件,时间没到now < msg.when,计算下一个延迟 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE),继续阻塞;在循环开始的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞,如果时间到了直接分发消息进行调用。

由于执行delay消息会阻塞,那么如果发送多个delay消息,是不是前一个消息阻塞执行完了,才会执行后一个,现实使用中肯定不是这样的。上面已经说了在enqueueMessage方法中,加入新的消息时会首先判断需不需要唤醒线程,这样线程就不会一直阻塞(加入新的消息,唤醒线程,就不需要等待上一个阻塞的消息)。

delay消息会一直阻塞线程,直到延迟走完,或者下一个消息到来。

参考博客如下

Handler延迟消息执行机制,会阻塞吗?
Handler进阶之sendMessage原理探索

你可能感兴趣的:(android)