美文网首页
Handler(五)--MessageQueue

Handler(五)--MessageQueue

作者: azu_test | 来源:发表于2019-02-15 18:11 被阅读0次

    系列目录: Handler机制原理

    MessageQueue介绍

    MessageQueue是一个消息队列,Handler将Message发送并加入到消息队列中,消息队列会按照规则取出要执行的Message。
    Java层的MessageQueue负责处理Java的消息,native也有一个MessageQueue负责处理native的消息,我也没看JNI代码,此处不做分析了。

    代码分析

    1. 成员变量
        //用于标示消息队列是否可以被关闭,主线程的消息队列不可关闭
        private final boolean mQuitAllowed;
    
        // 该变量用于保存native代码中的MessageQueue的指针
        private long mPtr; 
        //在MessageQueue中,所有的Message是以链表的形式组织在一起的,该变量保存了链表的第一个元素,也可以说它就是链表的本身
        Message mMessages;
        private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
        private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
        private IdleHandler[] mPendingIdleHandlers;
        //标示MessageQueue是否正在关闭。
        private boolean mQuitting;
        // 标示 MessageQueue是否阻塞
        private boolean mBlocked;
    
        // 在MessageQueue里面有一个概念叫做障栅,它用于拦截同步的Message,阻止这些消息被执行,
        // 只有异步Message才会放行。障栅本身也是一个Message,只是它的target为null并且arg1用于区分不同的障栅,
        // 所以该变量就是用于不断累加生成不同的障栅。
        private int mNextBarrierToken;
    
    2. 构造函数
        MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;
            mPtr = nativeInit();
        }
    
    1. 设置MessageQueue是否可以退出
    2. native层代码的初始化
    3. MessageQueue中的Message分类
      1. 同步消息
        正常情况下我们通过Handler发送的Message都属于同步消息,除非我们在发送的时候执行该消息是一个异步消息。
        同步消息会按顺序排列在队列中,除非指定Message的执行时间,否咋Message会按顺序执行。
      1. 异步消息
        想要往消息队列中发送异步消息,我们必须在初始化Handler的时候通过构造函数public Handler(boolean async)中指定Handler是异步的,这样Handler在讲Message加入消息队列的时候就会将Message设置为异步的。
      1. 障栅
        障栅(Barrier) 是一种特殊的Message,它的target为null(只有障栅的target可以为null,如果我们自己视图设置Message的target为null的话会报异常),并且arg1属性被用作障栅的标识符来区别不同的障栅。
        障栅的作用是用于拦截队列中同步消息,放行异步消息。
        在道路拥挤的时候会决定哪些车辆可以先通过,这些可以通过的车辆就是异步消息。
    4. enqueueMessage()
        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) {
                    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;
                // 第六步
                //根据when的比较来判断要添加的Message是否应该放在队列头部,当第一个添加消息的时候,
                // 测试队列为空,所以该Message也应该位于头部。
                if (p == null || when == 0 || when < p.when) {
                    // 把msg的下一个元素设置为p
                    msg.next = p;
                    // 把msg设置为链表的头部元素
                    mMessages = msg;
                     // 如果有阻塞,则需要唤醒
                    needWake = mBlocked;
                } else {
                    // 第七步
                    //除非消息队列的头部是障栅(barrier),或者消息队列的第一个消息是异步消息,
                    //否则如果是插入到中间位置,我们通常不唤醒消息队列,
                    // 第八步
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                     // 第九步
                     // 不断遍历消息队列,根据when的比较找到合适的插入Message的位置。
                    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;
        }
    

    第1步骤、 判断msg的target变量是否为null,如果为null,则为障栅(barrier),而障栅(barrier)入队则是通过postSyncBarrier()方法入队,所以msg的target一定有值
    第2步骤、 判断msg的标志位,因为此时的msg应该是要入队,意味着msg的标志位应该显示还未被使用。如果显示已使用,明显有问题,直接抛异常。
    第3步骤、 加入同步锁。
    第4步骤、 判断消息队列是否正在被关闭,如果是正在被关闭,则return false告诉消息入队是失败,并且回收消息
    第5步骤、 设置msg的when并且修改msg的标志位,msg标志位显示为已使用
    第6步骤、 如果p==null则说明消息队列中的链表的头部元素为null;when == 0 表示立即执行;when< p.when 表示 msg的执行时间早与链表中的头部元素的时间,所以上面三个条件,那个条件成立,都要把msg设置成消息队列中链表的头部是元素
    第7步骤、 如果上面三个条件都不满足则说明要把msg插入到中间的位置,不需要插入到头部
    第8步骤、 如果头部元素不是障栅(barrier)或者异步消息,而且还是插入中间的位置,我们是不唤醒消息队列的。
    第9步骤、 进入一个死循环,将p的值赋值给prev,前面的带我们知道,p指向的是mMessage,所以这里是将prev指向了mMessage,在下一次循环的时候,prev则指向了第一个message,一次类推。接着讲p指向了p.next也就是mMessage.next,也就是消息队列链表中的第二个元素。这一步骤实现了消息指针的移动,此时p表示的消息队列中第二个元素。
    第10步骤、 p==null,则说明没有下一个元素,即消息队列到头了,跳出循环;p!=null&&when < p.when 则说明当前需要入队的这个message的执行时间是小于队列中这个任务的执行时间的,也就是说这个需要入队的message需要比队列中这个message先执行,则说明这个位置刚刚是适合这个message的,所以跳出循环。 如果上面的两个条件都不满足,则说明这个位置还不是放置这个需要入队的message,则继续跟链表中后面的元素,也就是继续跟消息队列中的下一个消息进行对比,直到满足条件或者到达队列的末尾。
    第11步骤、 因为没有满足条件,说明队列中还有消息,不需要唤醒。
    第12步骤、 跳出循环后主要做了两件事:事件A,将入队的这个消息的next指向循环中获取到的应该排在这个消息之后message。事件B,将msg前面的message.next指向了msg。这样就将一个message完成了入队。
    第13步骤、 如果需要唤醒,则唤醒,具体请看后面的Handler中的Native详解。
    第14步骤、 返回true,告知入队成功

    5. next()
        Message next() {
            // 如果消息循环已经退出了。则直接在这里return。因为调用disposed()方法后mPtr=0
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
            //记录空闲时处理的IdlerHandler的数量
            int pendingIdleHandlerCount = -1; 
            // native层用到的变量 ,如果消息尚未到达处理时间,则表示为距离该消息处理事件的总时长,
            // 表明Native Looper只需要block到消息需要处理的时间就行了。 所以nextPollTimeoutMillis>0表示还有消息待处理
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    //刷新下Binder命令,一般在阻塞前调用
                    Binder.flushPendingCommands();
                }
                // 调用native层进行消息标示,nextPollTimeoutMillis 为0立即返回,为-1则阻塞等待。
                nativePollOnce(ptr, nextPollTimeoutMillis);
                //加上同步锁
                synchronized (this) {
                    // 获取开机到现在的时间
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    // 获取MessageQueue的链表表头的第一个元素
                    Message msg = mMessages;
                     // 判断Message是否是障栅,如果是则执行循环,拦截所有同步消息,直到取到第一个异步消息为止
                    if (msg != null && msg.target == null) {
                         // 如果能进入这个if,则表面MessageQueue的第一个元素就是障栅(barrier)
                        // 循环遍历出第一个异步消息,这段代码可以看出障栅会拦截所有同步消息
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                           //如果msg==null或者msg是异步消息则退出循环,msg==null则意味着已经循环结束
                        } while (msg != null && !msg.isAsynchronous());
                    }
                     // 判断是否有可执行的Message
                    if (msg != null) {  
                        // 判断该Mesage是否到了被执行的时间。
                        if (now < msg.when) {
                            // 当Message还没有到被执行时间的时候,记录下一次要执行的Message的时间点
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Message的被执行时间已到
                            // 从队列中取出该Message,并重新构建原来队列的链接
                            // 刺客说明说有消息,所以不能阻塞
                            mBlocked = false;
                            // 如果还有上一个元素
                            if (prevMsg != null) {
                                //上一个元素的next(越过自己)直接指向下一个元素
                                prevMsg.next = msg.next;
                            } else {
                               //如果没有上一个元素,则说明是消息队列中的头元素,直接让第二个元素变成头元素
                                mMessages = msg.next;
                            }
                            // 因为要取出msg,所以msg的next不能指向链表的任何元素,所以next要置为null
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            // 标记该Message为正处于使用状态,然后返回Message
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // 没有任何可执行的Message,重置时间
                        nextPollTimeoutMillis = -1;
                    }
    
                    // 关闭消息队列,返回null,通知Looper停止循环
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    // 当第一次循环的时候才会在空闲的时候去执行IdleHandler,从代码可以看出所谓的空闲状态
                    // 指的就是当队列中没有任何可执行的Message,这里的可执行有两要求,
                    // 即该Message不会被障栅拦截,且Message.when到达了执行时间点
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    
                    // 这里是消息队列阻塞( 死循环) 的重点,消息队列在阻塞的标示是消息队列中没有任何消息,
                    // 并且所有的 IdleHandler 都已经执行过一次了
                    if (pendingIdleHandlerCount <= 0) {
                        mBlocked = true;
                        continue;
                    }
        
                    // 初始化要被执行的IdleHandler,最少4个
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // 开始循环执行所有的IdleHandler,并且根据返回值判断是否保留IdleHandler
                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);
                        }
                    }
                }
    
                // 重点代码,IdleHandler只会在消息队列阻塞之前执行一次,执行之后改标示设置为0,
                // 之后就不会再执行,一直到下一次调用MessageQueue.next() 方法。
                pendingIdleHandlerCount = 0;
    
                // 当执行了IdleHandler 的 处理之后,会消耗一段时间,这时候消息队列里的可能有消息已经到达 
                 // 可执行时间,所以重置该变量回去重新检查消息队列。
                nextPollTimeoutMillis = 0;
            }
        }
    

    首先、MessageQueue会先判断队列中是否有障栅的存在,如果有的话,只会返回异步消息,否则就逐个返回。
    其次、当MessageQueue没有任何消息可以处理的时候,它会进度阻塞状态等待新的消息到来(无线循环),在阻塞之前它会执行以便 IdleHandler,所谓的阻塞其实就是不断的循环查看是否有新的消息进入队列中。
    再次、当MessageQueue被关闭的时候,其成员变量mQuitting会被标记为true,然后在Looper视图从队列中取出Message的时候返回null,而Message==null就是告诉Looper消息队列已经关闭,应该停止循环了,这一点可以在Looper.loop()方法源码中看出。
    最后、如果大家细心一定会发现,Handler线程里面实际上有两个无线循环体,Looper循环体和MessageQueue循环体,真正阻塞的地方是MessageQueue的next()方法里。

    MessageQueue的核心代码就是enqueueMessage()和next(),还有就是一些概念性的理解。
    最重要的一点是要知道两个for循环是在哪里阻塞的:

    Looper循环体和MessageQueue循环体,真正阻塞的地方是MessageQueue的next()方法里.

    参考

    https://www.jianshu.com/p/c4cc31e4052f

    相关文章

      网友评论

          本文标题:Handler(五)--MessageQueue

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