美文网首页
Handler源码记录

Handler源码记录

作者: 神棄丶Aria | 来源:发表于2020-03-18 14:08 被阅读0次

    一、Handler流程

    Handler负责发送消息和处理消息,
    MessageQueue作为消息队列,
    Message为单个消息的载体,
    Looper负责消息轮询,将消息发送给持有该消息的handler
    一个线程中只能有一个Looper和一个MessageQueue,但可以有多个Handler和多个Message。

    具体流程:Looper.prepare()初始化Looper->生成Message对象->handler分发Message将消息通过MessageQueue入队->Looper.loop()轮询消息队列取出消息->通过handler.dispatchMsg()分发消息->最后在handleMsg()处理消息

    二、Message

    消息载体Model,保存有几个关键属性

    public final class Message implements Parcelable {
          @UnsupportedAppUsage
        /*package*/ Handler target; //指向分发该消息的Handler
    
        @UnsupportedAppUsage
        /*package*/ Runnable callback; //消息回调,优先级高于handler,但外部不可调用
    
        // sometimes we store linked lists of these things
        @UnsupportedAppUsage
        /*package*/ Message next; // 保存下条消息的指针
    
        、、TODO 判断消息是否是异步消息
        public boolean isAsynchronous() {
            return (flags & FLAG_ASYNCHRONOUS) != 0;
        }
    }
    

    三、MessageQueue

    消息队列本身的实现是一个单向链表的实现方式(利用Message.next组成单向链表)

    1、消息进队 :enqueueMessage(Message msg, long when)
        boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {
                if (mQuitting) { //TODO 步骤1
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {   //TODO 步骤2
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {  //TODO 步骤3
                    // 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 (;;) {
                        //TODO 步骤3
                        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;
                }
                //TODO 是否唤醒
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    入队逻辑
    ·1、首先判断队列是否已经退出,如果退出则抛出异常
    ·2、如果当前消息队列为空,则将目前的消息作为队列头
    ·3、根据when时间将消息插入队列中
    ·4、插入结束时会判断是否需要调用nativeWake()。这里的needWake是根据mBlocked以及当前消息是否为同步屏障消息来判断,而mBlocked则只在当前消息队列处于空闲状态时才为true。

    2、同步屏障 postSyncBarrier

    MessageQueue的消息屏障是什么:其本质是一个Message,只是该Message与其他Message不同,它的target为null,同步屏障的功能是在消息队列取数据时告诉代码优先取异步消息队列

        public int postSyncBarrier() {
            return postSyncBarrier(SystemClock.uptimeMillis());
        }
    
        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;
            }
        }
    
        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;
                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);
                }
            }
        }
    

    ·1、添加同步屏障时,实际上在队列中插入一个arg1=token以及target ==null的消息
    ·2、移除同步屏障时,实际上移除了队列中target== null或token == 传入的参数值的消息

    3、获取下一个消息:next()
        Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            final long ptr = mPtr;
            if (ptr == 0) { //TODO 注释1
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
                nativePollOnce(ptr, nextPollTimeoutMillis); //TODO 注释2
    
                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) { //TODO 注释3
                        // 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) { //TODO 注释4
                        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);
                }
    
                //TODO 步骤5
                // 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;
            }
        }
    

    取队列数据的逻辑
    ·1、首先判断mPtr,mPtr用于判断队列是否退出或销毁,为0表示销毁则return。
    ·2、 nativePollOnce(ptr, nextPollTimeoutMillis),执行Native的消息机制,该方法会一直等待Native消息,其中 timeOutMillis参数为超时等待时间。如果为-1,则表示无限等待,直到有事件发生为止。如果值为0,则无需等待立即返回,所以主线程一直轮询是不会一直消耗cpu性能的,也不会造成卡顿,因为一有消息就会被唤醒。
    ·3、 if (msg != null && msg.target == null) 是否有屏障消息,如果有就过滤掉同步消息直接执行就近的异步消息,代码再往下看就是时间是否到了消息等待的时候,到了则返回。
    ·4、判断该消息是否已经到了执行时间,如果未到则设置下次等待时间,如果该消息到了可执行的时刻,队列中移除该消息,并且返回
    ·5、如果此时执行的msg为空(有2种情况:(1)当前已经无消息 (2)当前处于同步屏障下且无异步消息)会执行IdleHandler,即利用线程的空闲时间做一些事情,具体见下。
    6、next()中使用for循环的原因:个人理解是处理消息队列为空的情况,让线程进入空闲状态,同时让Looper.loop()不会因为返回的msg为空而直接退出。

    四、Looper

    1、初始化 prepare()
        public static void prepare() {
            prepare(true);
        }
    
        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));
        }
    

    初始化逻辑:判断当前的sThreadLocal是否有looper样本,如果已存在则直接抛出异常。这里再次证明一个线程只能创建一个looper,而主线程ActivityThread不需调用Looper.prepare()的原因是ActiivtyThread已经自动创建了Looper

    1、轮询消息 loop()
        /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        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;
    
            //TODO 省略部分代码
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                //TODO 省略部分代码
    
                try {
                    msg.target.dispatchMessage(msg);
                } catch (Exception exception) {
                    throw exception;
                } finally {
                }
                //TODO 省略部分代码
    
                msg.recycleUnchecked();
            }
        }
    

    轮询逻辑:
    ·1、看省略后的代码其实很好理解,从MessageQueue中取Message,如果为空则表示队列已退出,return
    ·2、如果有数据,则通过msg.target.dispatchMessage()分发消息

    五、Handler

    1、发送消息
        public boolean sendMessageAtTime(@NonNull 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);
        }
    
        private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    发送逻辑:
    ·1、所有的handler.sendMessage()最后都会调用sendMessageAtTime().这里的uptimeMillis是指msg调用的时间点,即Message中的when参数
    2、handler的入队方法,这里会给msg指定target为自己,然后调用MessageQueue.enqueueMessage()将消息加入消息队列中

    2、消息分发 dispatchMessage
        /**
         * Handle system messages here.
         */
        public void dispatchMessage(@NonNull Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    分发逻辑:
    ·1、这里面有三层判断,第一层先判断msg本身是否需要callback
    2、判断handler本身是否实现mCallback,如果实现则走mCallback.handlerMsg()
    3、最后handler本身的方法处理消息

    六、IdleHandler

    本意是让Handler能在Looper事件循环的过程中,在队列空闲时执行任务的机制。

        /**
         * Callback interface for discovering when a thread is going to block
         * waiting for more messages.
         */
        public static interface IdleHandler {
            /**
             * Called when the message queue has run out of messages and will now
             * wait for more.  Return true to keep your idle handler active, false
             * to have it removed.  This may be called if there are still messages
             * pending in the queue, but they are all scheduled to be dispatched
             * after the current time.
             */
            boolean queueIdle();
        }
    

    该接口仅提供了一个接口,当queueIdle()返回true时表示该IdleHandler执行完后不移除出队列,下次空闲时仍执行该任务,为false表示执行完后就将该任务移除。

    1、执行时机与机制

    IdleHandler在MessageQueue执行next()时执行

                synchronized (this) {
                    //TODO 省略代码
                    // 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.
                    //TODO 步骤1
                    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.
                //TODO 步骤2
                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);
                        }
                    }
                }
    
                //TODO 步骤3
                // 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;
    

    ·1、首先判断pendingIdleHandlerCount < 0 与队列是否为空或者未到执行消息的时机,如果为true则赋值pendingIdleHandlerCount。如果mIdleHandlers为空则调用continue重新进行循环。
    ·2、依次执行mIdleHandlers里前 pendingIdleHandlerCount 个任务。
    ·3、reset pendingIdleHandlerCount 和 nextPollTimeoutMillis。
    (1)将nextPollTimeoutMillis置为0表示下次循环线程不进入休眠,直接判断队列中是否有新消息。这里的意图我猜测是排除执行IdleHandler时消耗时长过长导致有新消息进队,从而导致等待时间不正确。
    (2)pendingIdleHandlerCount 置为0,从而让步骤一中的判断条件一直不通过而直接continue,这里的设计意图是让IdleHandler在一次next()循环中只执行一次,防止消息队列一直为空而陷入执行IdleHandler任务的死循环中。

    未解决问题:
    1、ThreadLocal

    结尾

    参考文章:
    https://www.jianshu.com/p/fefd613893d7
    https://mp.weixin.qq.com/s/E7NUpkhBrjHATqEh9k1KNQ
    https://juejin.im/post/5e4de2f2f265da572d12b78f

    相关文章

      网友评论

          本文标题:Handler源码记录

          本文链接:https://www.haomeiwen.com/subject/fqvsgctx.html