美文网首页Android
Handler消息机制:消息机制的组成

Handler消息机制:消息机制的组成

作者: sjandroid | 来源:发表于2019-05-13 14:38 被阅读0次

    Android消息机制的组成

    • Message
    • MessageQueue
    • Looper
    • Handler

    参考:

    老罗关于Handler的讲解:Android应用程序消息处理机制(Looper、Handler)分析


    Message:消息。

    1:Message采用单链表的形式来“存储代处理消息”。
    2:此单链表是一个“有序链表”,其中存储的待处理消息是根据when(消息的处理时间)来从小到大排序的。
    重要属性

    1.1.long when:用于指定此消息的“处理时间”(也就是说“此消息在将来何时会被处理”)。

    1.2.Handler target:此消息绑定的“消息处理器”。

    1.3.Message next:next指针指向此消息的“后继节点”指定的代处理消息。

    1.4.Message sPool:
    1.4.1:“可用消息链表”,其实也还是一个Message构成的单链表。
    1.4.2:使用完毕的消息会 被添加到以sPool为首节点的单链表的表头,使用的时候就把此链表的首节点,也就是sPool指向的消息返回。
    1.4.3:当消息被其绑定的Handler处理完毕后,用于缓存recycle()使用完毕的消息。这样做的目的是为了“消息复用”。

    1.4.4:该“可用消息链表”被声明为static,这表示此“可用消息链表”缓存的可用消息是“公用”的,整个App进程的可用消息都缓存在这里。

    1.5.int MAX_POOL_SIZE:缓存池的最大容量,该值为50(就是以sPool为首节点的,单链表的最大长度)。


    重要方法说明:
    1.1.obtain()
    总结:从“可用消息链表”中获取一个可用的消息。
    1:如果“可用消息链表”中有可用消息的话,则把 “sPool存储的可用消息链表的首节点表示的消息”作为可用消息。
    2:如果“可用消息链表”是空的,则新创建一个消息来作为那个可用消息。

    源码

    public static Message obtain() {
            synchronized (sPoolSync) {
                //如果“可用消息链表”有可用的消息的话
                if (sPool != null) {
                    //选择“可用消息链表的首节点表示的消息”为 可用消息
                    Message m = sPool;
                    //移动“消息链表的首节点至下一位”
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    //可用消息数减1
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }
    

    1.2.recycle()
    总结:把一个“使用完毕的消息”添加至“可用消息链表”的首节点处。

    源码

    public void recycle() {
            if (isInUse()) {
                //当Message被标记位"FLAG_IN_USE"时需要检查“gCheckRecycle”属性值是否为true。
                //为true,则会抛出“IllegalStateException”。为false,则不会抛出此异常。
                //gCheckRecycle属性默认是true,只有在sdk版本号小于21才会通过“updateCheckRecycle()”关闭。也就是是sdk大于21会通过IllegalStateException提醒用户当前消息不能被回收,它正在被使用。
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            //见小节[1.3]
            recycleUnchecked();
        }
    

    1.3.recycleUnchecked()
    总结:将“使用完毕的消息”插入到“可用消息链表”的首节点处。

    源码

    void recycleUnchecked() {
            //标记此消息为“正在被使用”
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    //将“使用完毕的消息”插入到“可用消息链表”的首节点处。
                    //当前节点的next指针指向 缓存队列的首节点。
                    next = sPool;
                    //更新首节点:首节点向前移动一位,指向当前节点。
                    sPool = this;
                    //缓存消息队列的大小+1
                    sPoolSize++;
                }
            }
        }
    

    1.4.updateCheckRecycle()
    总结:

    1:更新“gCheckRecycle”属性。在执行recycle()回收已使用消息时,用于是否提醒“如果代回收的消息正在被使用,是否需要通过抛出IllegalStateException来提醒用户”。
    2:该方法会在ActivityThread.handleBindApplication()(该方法会在App冷启动时调用)调用,用于设置gCheckRecycle属性。
    3:只有Android手机的系统的版本号小于21才会被设置为false,默认为true。

    源码

    public static void updateCheckRecycle(int targetSdkVersion) {
            if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
                gCheckRecycle = false;
            }
        }
    

    1.5.sendToTarget()
    总结:通过与该Message绑定的Handler把该消息发送至MessageQueue中。

    源码

    public void sendToTarget() {
            target.sendMessage(this);
        }
    

    1.6.isAsynchronous()
    总结:查看此消息是否是“异步消息”。

    通过位运算符“&”来查找是否通过“setAsynchronous()”设置了“异步消息”标记。
    关于“同步、异步消息”的问题请查看 《Handler消息机制:相关问题汇总》

    源码

    public boolean isAsynchronous() {
            return (flags & FLAG_ASYNCHRONOUS) != 0;
        }
    

    1.7.setAsynchronous()
    总结:设置待发送消息是否是 “异步消息”

    1:待发送消息默认是“同步消息”。
    2:如果是异步消息,则会通过位运算符“|”,把flags相应二进制位的值重置为 1。
    3:如果是同步消息,则会通过相应位运算符,把flags相应二进制位的值重置为 0。

    源码

    ublic void setAsynchronous(boolean async) {
            if (async) {
                flags |= FLAG_ASYNCHRONOUS;
            } else {
                flags &= ~FLAG_ASYNCHRONOUS;
            }
        }
    

    1.8.isInUse()

    总结:检查此消息“是否正在被使用”。这里的使用指的是“是否正在被消息处理机制使用。”

    1:添加该标记。在以下3中情况下,此消息的该标记位会被设置。
    1.1:通过MessageQueue.enqueueMessage()添加消息至消息队列。
    1.2:通过MessageQueue.next()获得待处理消息。
    1.3:当消息被相应的Handler处理之后,通过Message.recycleUnchecked()添加该标记位。
    2:清除该标记。只有通过 “new新创建一个消息” 或者 “通过Message.obtain()从 可用消息链表 中获取一个消息”的时候,才会把该标记清除。

    源码

    /*package*/ boolean isInUse() {
            return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
        }
    

    1.9.markInUse()

    总结:添加“正在被使用”标记位,用于标记“此消息正在被使用”。

    源码

    /*package*/ void markInUse() {
            flags |= FLAG_IN_USE;
        }
    

    MessageQueue:

    定义:消息队列,其内部通过Message构建的一个“单链表”来保存那些待处理的消息。
    作用:
    1:添加消息:通过“enqueueMessage()” 添加 代处理消息 至队列中的合适位置。
    2:获取消息:通过“next()” 获取 待处理消息。


    问题:

    哪些操作会唤醒MessageQueue初始化所在的线程?

    1.1:quite():
    1.2:removeSyncBarrier():
    1.3:enqueueMessage():


    重要属性说明:

    Message mMessages:用于存储待处理消息的“单链表”。
    ArrayList<IdleHandler> mIdleHandlers:用于存储通过addIdleHandler()添加的“空闲消息处理器”。

    int mNextBarrierToken:屏障标记。

    1:是一个int型变量。
    2:当调用postSyncBarrier()添加“同步屏障”(同步屏障是一个特殊的消息,该消息没有消息处理器(Handler)),其arg1参数指向的就是此“屏障标记”。

    long mPtr:

    1:该属性表示“C++层通过reinterpret_cast(强制类型转换符)转换之后的与java层MessageQueue一一对应的NativeMessageQueue对象的一个long类型表示”。
    2:该属性被保存在java层的MessageQueue中,在执行native方法(nativePollOnce()、nativeWake()等)时,需要使用此long类型参数,找到在native层声明的与java层MessageQueue一一绑定的NativeMessageQueue对象。

    reinterpret_cast
    作用:reinterpret_cast,可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。
    参考:https://baike.baidu.com/item/reinterpret_cast/9303204?fr=aladdin

    boolean mBlocked:标记当前线程是否在执行next()中nativePollOnce()时被阻塞住了。true为阻塞,false未阻塞。
    boolean mQuitting:标记是否执行了“退出”操作。该标记在执行quit()操作时被设置为true。

    重要方法说明:

    2.1.isIdle()

    总结:检查当前消息队列是否处于“空闲状态”。

    这个空闲状态是指:
    2.1:“消息队列”内没有要处理的消息(具体的说是:消息队列内部通过mMessages缓存的待处理消息链表为无数据)。
    2.2:“待处理消息链表的首结点表示的消息的处理时间比当前时间点要晚” ,还没有到要处理消息的时间。

    源码

    public boolean isIdle() {
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                //如果“消息队列为null” 或者 “待处理消息的处理时间比当前时间点要晚” 则返回true。
                return mMessages == null || now < mMessages.when;
            }
        }
    

    2.2.addIdleHandler()

    总结:添加“空闲消息处理器”。

    作用:在next()内部,如果消息队列中没有要处理的消息之后,当前线程不是马上进入阻塞状态,而是先调用通过此方法注册的“空闲消息处理器”,这样做是为了“在线程空闲时,可以有机会执行这些空闲消息处理程序内部定义的一些操作。”

    源码

    public void addIdleHandler(@NonNull IdleHandler handler) {
            if (handler == null) {
                throw new NullPointerException("Can't add a null IdleHandler");
            }
            synchronized (this) {
                //添加到存储IdleHandler的List列表中
                mIdleHandlers.add(handler);
            }
        }
    

    2.3.isPolling()
    总结:是否正在从消息队列中取消息(true表示正在取消息,false表示消息已分配,目前正在等待Handler处理消息)。

    官方解释: “返回此循环器的线程当前是否正在轮询以进行更多工作。 这是一个很好的信号,表明循环仍然存在而不是被卡住处理回调。”

    理解: “当前是否正在从消息队列中取消息,而不是取得代处理消息之后,等待Handler处理消息。该方法可以验证“当前MessageQueue是否正在等待消息的处理。”

    验证方式:只需要在处理消息的时候,添加一个延迟(比如sleep几秒钟),然后开启一个线程反射执行其isPolling(该方法被@hide注解修饰,不能直接调用到),此时会发现该方法返回的是 false。

    @Override
    public void handleMessage(Message msg) {
           Log.e(TAG, "handleMessage()---Start---What:" + msg.what);
    
           try {
               TimeUnit.SECONDS.sleep(5);
           } catch (InterruptedException e) {
                  e.printStackTrace();
            }
    
           Log.e(TAG, "handleMessage()---End---What:" + msg.what);
        }
    
    private void checkMessageQueueState(){
            new Thread(){
                @Override
                public void run() {
                    while (true){
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
                            boolean isIdle = msgQueue.isIdle();
                            boolean isPolling = false;
    
                            try {
                                Method method = msgQueue.getClass().getMethod("isPolling");
                                isPolling = (boolean) method.invoke(msgQueue);
                            } catch (NoSuchMethodException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
    
                            Log.e(TAG, "checkMessageQueueState()---isIdle:" + isIdle + ", isPolling:" + isPolling);
    
                            try {
                                TimeUnit.MILLISECONDS.sleep(5);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }.start();
        }
    

    2.4.quite(boolean safe)

    总结:退出。

    该方法内部做了3件事,它们分别是:

    1:根据safe值,采用相应的策略删除此消息队列中还未被处理的消息。

    1.1:如果safe为true,则该方法内部会调用“removeAllFutureMessagesLocked()”,此方法内部所做的事情为:以当前时间为节点,从消息队列中移除该节点之后的消息,保留该节点之前的消息能够继续运行。
    1.2:如果safe是false,则该方法内部会调用“removeAllMessagesLocked()”,此方法内部所做的事情为:不管消息队列中是否还有消息未执行,把该消息队列清空。

    2:为mQuitting属性赋值为true,标记“当前消息队列”执行了退出操作。
    3:调用nativeWake()唤醒可能处于阻塞状态的此消息队列初始化所在的线程。

    如何验证“总结”中给出的结果?
    需要在Handler.handleMessage()中做一个耗时操作(sleep几秒),然后看看log信息。
    具体的可以这样做

    //在Handler中添加耗时操作。
    handler = new Handler(ht.getLooper()){
                @Override
                public void handleMessage(Message msg) {
                    Log.e(TAG, "handleMessage()---Start---What:" + msg.what);
    
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    Log.e(TAG, "handleMessage()---End---What:" + msg.what);
                }
            };
    
    private void test(){
            this.handler.sendEmptyMessageDelayed(1000, 1000 * 1);
            this.handler.sendEmptyMessageDelayed(2000, 1000 * 2);
            this.handler.sendEmptyMessageDelayed(3000, 1000 * 3);
    
            this.getWindow().getDecorView().postDelayed(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, "清空消息");
                    looper.quitSafely();
                }
            }, (long) (1000 * 5));
        }
    

    源码

    void quit(boolean safe) {
            //已经退出了,再次调用此方法会抛出异常,用于提示用户。
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
    
            synchronized (this) {
                //正在执行“退出”操作,则不再继续执行。
                if (mQuitting) {
                    return;
                }
                //标记正在执行退出操作。
                mQuitting = true;
    
                if (safe) {
                    //以当前时间为节点,从消息队列中移除该节点之后的消息,保留该节点之前的消息能够继续运行。
                    //见小节[2.5]
                    removeAllFutureMessagesLocked();
                } else {
                    //不管消息队列中是否还有消息未执行,把该消息队列清空。
                    //见小节[2.6]
                    removeAllMessagesLocked();
                }
    
                // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
            }
        }  
    

    2.5.removeAllFutureMessagesLocked()

    总结:从消息链表中移除将来时间点要处理的消息。

    1:以当前时间为节点,移除该节点之后的消息。
    2:当前时间节点之前的消息还会执行。

    源码

        private void removeAllFutureMessagesLocked() {
            final long now = SystemClock.uptimeMillis();
            Message p = mMessages;
            if (p != null) {
                if (p.when > now) { //在当前时间节点之后如果消息队列中还有消息要执行,全部删除
                    removeAllMessagesLocked();
                } else { //如果当前时间节点之前有未处理的消息(消息队列是一个按照“代处理时间”从小到大排序的链表)
                    Message n;
                    for (;;) {
                        n = p.next;
                        //遍历了整个消息队列,没有一个消息的待处理时间大于当前时间节点。则直接return。
                        if (n == null) {
                            return;
                        }
                        //如果当前时间节点之前的消息中,有某个消息的执行时间大于当前节点,那么从这个消息开始之后的消息都需要从此队列中移除。
                        if (n.when > now) {
                            break;
                        }
                        p = n;
                    }
                    p.next = null;
                    do {
                        p = n;
                        n = p.next;
                        p.recycleUnchecked();
                    } while (n != null);
                }
            }
        }
    

    2.6.removeAllMessagesLocked()

    总结:清空整个消息队列,不管队列中还有没有代处理消息。

    源码

    private void removeAllMessagesLocked() {
            Message p = mMessages;
            while (p != null) {
                Message n = p.next;
                p.recycleUnchecked();
                p = n;
            }
            mMessages = null;
        }
    

    2.7.postSyncBarrier()

    总结:设置“同步屏障”,阻塞此时间点之后的同步消息的执行。

    1:通过Message.obtain() 从“可用消息链表”获取一个可用的消息。
    2:通过Message.markInUse() 标记此消息“正在被使用”。
    3:把该消息的 “处理时间” 设置为 当前时间点。
    4:该消息的 arg1参数存储此 “屏障标记”。
    5:遍历MessageQueue中存储的“消息链表”,根据when把此“同步屏障”消息,插入到根据“消息处理时间”从小到大排序的消息链表的合适位置。

    关于同步屏障的详细介绍,请查看 《Handler消息机制:相关问题汇总--同步屏障》

    源码

    public int postSyncBarrier() {
            return postSyncBarrier(SystemClock.uptimeMillis());
        }
    
        private int postSyncBarrier(long when) {
            synchronized (this) {
                //“屏障标记”在使用完后,执行自增操作。
                final int token = mNextBarrierToken++;
                //从“可用消息链表”获取一个可用的消息
                final Message msg = Message.obtain();
                //标记此消息“正在被使用”
                msg.markInUse();
                //把该消息的“处理时间”设置为当前时间点。
                msg.when = when;
                //该消息的arg1参数存储此“屏障标记”
                msg.arg1 = token;
    
                Message prev = null;
                Message p = mMessages;
                if (when != 0) {
                //遍历MessageQueue中存储的“消息链表”
                //根据when把此“同步屏障”插入到根据“消息处理时间”从小到大排序的消息链表的合适位置。
                    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;
            }
        }
    

    2.8.removeSyncBarrier(int token)

    总结:

    1:从消息链表中删除“token”指定的“同步屏障”(如果消息链表中有此token指向的同步屏障消息,则删除)。
    2:如果有必要,需要执行nativeWake()唤醒当前线程。
    执行唤醒操作的前提条件是什么?
    1:如果此同步屏障消息位于消息链表的表头,且“只有该同步屏障消息一个消息”的话,则需要执行唤醒MessageQueue初始化所在线程的操作。
    2:如果此同步屏障消息位于消息链表的表头,且“该屏障消息的后继结点指向的待处理消息绑定了Handler”的话,也需要执行唤醒MessageQueue初始化所在线程的操作。

    源码

    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,arg1的值存储的是“屏障标记”
                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;
    
                //如果消息链表中有此token指向的同步屏障消息 且 该同步屏障消息不在整个消息链表的表头位置的话。
                //只是删除此同步屏障消息,不需要执行唤醒MessageQueue初始化所在的线程操作。
                if (prev != null) {
                    prev.next = p.next;
                    needWake = false;
                } else {
                    //如果消息链表中有此token指向的同步屏障消息 且 该同步屏障消息在整个消息链表的表头位置的话。
                    mMessages = p.next;
                    //如果此时整个消息链表中只有“同步屏障消息”或者 该屏障消息之后存在其他消息的话。
                    //需要执行唤醒当前线程操作
                    needWake = mMessages == null || mMessages.target != null;
                }
                //将“使用完毕的消息”插入到“可用消息链表”的首节点处
                p.recycleUnchecked();
    
                //如果需要执行唤醒操作,且当前消息队列没有在执行“退出”操作的话。则执行nativeWake()唤醒当前线程
                if (needWake && !mQuitting) {
                    nativeWake(mPtr);
                }
            }
        }
    

    2.9.dispose()

    总结:

    1. 通知native层NativeMessageQueue停止native层的消息模型的运行。
    2. 断开java层MessageQueue与native层NativeMessageQueue的联系。

    通知native层NativeMessageQueue停止native层的消息模型的运行。
    在“手动执行消息队列quit()(quie()操作内部会把mQuitting标记为true,在next()会对该标记做检查,如果为true则会调用dispose()。所以说quit()操作是触发执行dispose()的前提)” 或者 “消息队列对象被GC回收时(GC操作会调用待回收对象实现的finalize(),消息队列实现了此方法,用于在该方法内做资源回收的工作)”,通过执行“nativeDestory()用于通知native层的NativeMessageQueue执行销毁操作”。

    断开java层MessageQueue与native层NativeMessageQueue的联系。
    通过把mPtr重置为0,用于“关闭java层MessageQueue与native层NativeMessageQueue的绑定关系”。

    源码

    private void dispose() {
            if (mPtr != 0) {
                //通知native层的NativeMessageQueue执行销毁操作。
                nativeDestroy(mPtr);
                //把mPtr重置为0,用于“关闭java层MessageQueue与native层NativeMessageQueue的绑定关系”。
                mPtr = 0;
            }
        }
    
    @Override
        protected void finalize() throws Throwable {
            try {
                dispose();
            } finally {
                super.finalize();
            }
        }
    

    2.10.enqueueMessage(Message msg, long when)

    总结:往消息队列内部的消息链表中插入消息。

    1:在往此消息队列内部插入消息之前,需要对待插入消息做验证。具体的:验证待处理消息有没有绑定Handler、验证待处理消息是否正在被使用(通过isInUse())。

    2:判断此消息队列是否执行了quit()操作
    2.1:如果此消息队列已经执行了quit()操作的话,则通过Message.recycle()把此消息缓存至“全局可用消息链表”。
    2.2:返回false通知此次插入消息操作执行失败。

    3:如果代插入消息通过了验证 且 当前消息队列没有执行退出操作的话,则对待插入消息进行一些 “插入前的设置操作”。
    3.1:通过 Message.markInUse() 标记此消息正在被使用。
    3.2:设置此消息的处理时间(when)。

    4:等这些插入前的设置操作执行完毕后,则需要执行 “消息的插入” 与 “是否需要唤醒此消息队列初始化所在的线程” 这2步操作。
    4.1:待处理消息的插入。
    4.1.1:把待处理消息插入到此消息队列内部的消息链表的 首节点 处。 条件是:“如果当前消息队列为空(消息队列内部的存储的消息链表中没消息)” 且“如果此待处理消息when指向的 消息处理时间为0 或者 消息处理时间比消息链表的首节点指向的消息处理时间要靠前。”
    4.1.2:非4.1.1描述的情况外把待处理消息插入到此消息队列内部的消息链表的 中间或者链表尾部。

    4.2:是否需要唤醒此消息队列初始化所在的线程。
    4.2.1:如果是4.1.1描述的情况,具体是否需要唤醒此线程,需要根据“mBlocked()”参数来决定。如果此线程已经被阻塞住了则需要唤醒,否则不需要执行唤醒操作。
    4.2.2:这个情况描述的场景暂时还没想明白,这里只是写出了“此操作做的事情”。(4.2.1操作会在此操作之前执行)如果4.2.1操作决定需要唤醒此线程,那么此操作会判断 “在未找到待插入点之前,只要此待处理消息链表中有一个消息是异步消息的话,则不需要执行唤醒操作。”

    源码

    boolean enqueueMessage(Message msg, long when) {
            //待处理消息没有绑定Handler的话,则抛出IllegalArgumentException提醒用户
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            //如果待处理消息被标识为“正在被使用”,则抛出IllegalStateException提醒用户
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
        
            synchronized (this) {
                //如果此消息队列已经执行了quit()操作的话,则通过Message.recycle()把此消息缓存至“全局可用消息链表”中。
                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;
                //是否需要唤醒MessageQueue初始化时所在的线程。
                boolean needWake;
            
                //1:如果当前消息队列为空(消息队列内部的存储的消息链表中没消息)。
                //2:如果此待处理消息when指向的 消息处理时间为0 或者 消息处理时间比消息链表的首节点指向的消息处理时间要靠前。
                //满足以上2点,则把该消息插入到此消息队列的“首节点”处。
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
            
                    //mBlocked决定MessageQueue初始化所在的线程是否被阻塞,该变量的赋值在next()中。
                    //具体见小节[2.11]。
                    needWake = mBlocked;
                } else {
                    
                    //如果执行插入消息之前,MessageQueue初始化所在的线程已经被阻塞 且 在消息链表的首节点的处的消息是“同步屏障” 且 要插入的消息是一个异步消息的话。
                    //如果满足以上3点,则“需要执行唤醒操作”,这是因为:异步消息的分发执行不受同步屏障的影响。同步屏障只是影响其之后的同步消息的分发与执行。
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        //Message消息链表是一个“以消息处理时间(when)从小到大排序好的有序链表”。
                        //找到该消息所在的消息链表的合适位置。具体的是:根据消息的处理时间,把此消息插入到消息链表的中间或者链表尾部。
                        if (p == null || when < p.when) {
                            break;
                        }
                        //????????????????????????????????//
                        //FIXME 以下描述的场景,还没想明白。
                        //如果需要唤醒MessageQueue初始化所在的线程 且 在未找到待插入点之前,只要此待处理消息链表中有一个消息是异步消息的话,则不需要执行唤醒操作。
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                        //????????????????????????????????//
                    }
                    //执行插入操作。
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // 如果需要执行唤醒操作的话,则通过nativeWake()执行当前线程的唤醒操作。
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    2.11.next()

    总结:从此消息队列内部的消息链表中获取消息。

    1:通过nativePollOnce()把nextPollTimeoutMillis回传给native层的NativeMessageQueue。用于“执行native层的消息” 并 “根据nextPollTimeoutMillis变量的值,在native层决定是否阻塞当前线程”。(此时的当前线程是 MessageQueue初始化所在的线程,因为next()只运行在初始化该消息队列的线程内)。

    1.1:当前线程的阻塞、休眠、唤醒操作全部都是通过native层去做的,而native则是通过“管道”这种IPC方式来控制(关于管道的理解,请查看《Handler消息机制:相关问题汇总--管道》

    2:从消息队列中获取待处理的消息。

    2.1:如果消息队列的首节点是一个 “同步屏障消息” 的话,则从该首节点处往后遍历此消息队列内部的消息链表,找到第一个 “异步消息”(异步消息的分发执行不受同步屏障的影响)。
    2.2:如果消息队列的首节点不是“同步屏障”,则这个首节点处的消息就是要被拿来处理的消息。

    关于同步屏障的简单介绍,请查看小节-2.72.8《Handler消息机制:相关问题汇总--同步屏障》

    3:获得待处理的消息后,对此消息就行后续判断。

    3.1:待处理消息不为空。
    3.1.1:如果待处理消息的执行时间<=当前时间点,则该消息需要被交给其绑定的Handler去处理。退出此循环
    3.1.2:如果待处理消息的执行时间 大于当前时间点,则赋值nextPollTimeoutMillis为两个时间点的差值。在下次for()循环中用于通过nativePollOnce()决定当前线程的休眠时间。
    3.2:待处理消息为空。则设置nextPollTimeoutMillis为-1,表示在下次for()循环中用于通过nativePollOnce()阻塞当前线程。

    4(可能执行):如果手动调用了quit()执行了退出操作的话,则需要:

    4.1:执行dispose()“断开与native层NativeMessageQueue的联系、停止native层的消息模型的执行”。
    4.2:返回一个空消息用于结束java层的消息模型的运行。

    5:执行注册的 空闲消息处理器。
    当前消息队列没有要处理的消息时,表示“此时的消息队列是空闲的”。在这种情况下当前线程并不是立马会被阻塞或者休眠,而是先先看看有没有注册 “空闲消息处理器”。如果注册了则把空闲消息处理器走一遍,如果没注册才会被阻塞或者休眠。这样就体现出了“空闲消息处理器设计的目的”,那就是“在当前消息队列空闲时,才会执行的操作”。

    5.1:空闲消息处理器只有在“消息队列为空” 或者 “还没到代处理消息执行的时间点” 满足以上2点之一才会被执行。
    5.2:在一个next()操作中,注册的空闲消息处理器只会被全部执行一次。
    5.3:执行完注册的空闲消息处理器后,需要重置nextPollTimeoutMillis 为0,这很重要,因为“在执行空闲消息处理器过程中,此消息队列中可能已经添加了新的消息,则不需要阻塞当前线程”。

    问题1: nextPollTimeoutMillis局部变量的作用是什么?
    作用:该变量通过nativePollOnce()被传入到native层的NativeMessageQueue,用于判断“是否需要阻塞或者休眠当前线程”。

    取值:
    0:不阻塞当前线程。
    -1:阻塞。
    大于0:指定当前线程的会在休眠多长时间后被唤醒。

    问题2: pendingIdleHandlerCount的作用是什么?何时执行“注册的空闲消息处理器”?
    定义:表示“注册的空闲消息处理器的个数”。

    取值&作用:
    小于等于0:避免在一次next()操作中多次执行注册的空闲消息处理器。这是为什么“空闲消息处理器在一个next()操作中只执行一遍的原因”。
    大于0:大于0 才会执行注册的空闲消息处理器。

    何时执行?
    空闲消息处理器只有在“消息队列为空” 或者 “还没到代处理消息执行的时间点” 满足以上2点之一才会被执行。

    源码

    Message next() {
            final long ptr = mPtr;
            //mPtr在dispose()会被重置为0,表示“取消与native层NativeMessageQueue的绑定关系”。
            //如果判断mPtr为0,则直接return null。此时整个消息模型就会退出运行。
            if (ptr == 0) {
                return null;
            }
            //注册的空闲消息处理器个数
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            //通过nativePollOnce()回传给native层NativeMessageQueue,用于决定是否阻塞当前线程(此时的当前线程是 MessageQueue初始化所在的线程,因为next()只运行在初始化该消息队列的线程内)。
            //0:不阻塞当前线程。
            //-1:阻塞。
            //>0:指定当前线程的会在休眠多长时间后被唤醒。
            int nextPollTimeoutMillis = 0;
            for (;;) {
                //?????????????????????????//
                //这个不清楚是干什么的,可以查看老罗的关于Handler的讲解,传送门在文章开头处。
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                //?????????????????????????//          
                
                //通过native函数nativePollOnce()决定是否阻塞当前线程。
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    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) {
                        //如果代处理消息的执行时间比当前时间要靠后,则需要更新nextPollTimeoutMillis,用于通知当前线程“处理消息的时间还没到,需要休眠多长时间后再来处理消息”。
                        if (now < msg.when) {
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            //如果待处理消息的执行时间<=当前时间点,则该消息需要被交给其注册的Handler去处理。
                            //标记当前线程没有被“阻塞”。
                            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 {
                        //如果没有待处理的消息,则更新nextPollTimeoutMillis为-1,表示“此时没有要处理的消息,需要阻塞当前线程。”具体的阻塞操作为会在下一次for()循环中执行。
                        nextPollTimeoutMillis = -1;
                    }
    
                    // 如果手动调用了quit()执行了退出操作的话,则需要:
                    //1:执行dispose()“断开与native层NativeMessageQueue的联系、停止native层的消息模型的执行”。
                    //2:返回一个空消息用于结束java层的消息模型的运行。
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
                    
                     //前提:在next()内部的for()中只会对“注册的空闲消息处理进行一次执行”。
                     //pendingIdleHandlerCount < 0表示“还没有执行注册的空闲消息处理器”。
                     //如果此消息队列空了,或者 还没到消息链表首节点消息的处理时间的话。
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        //查看到底注册了多少个 空闲消息处理器。
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    //如果已经把注册的 空闲消息处理器全部执行了一遍。则标记当前线程为阻塞了。
                    if (pendingIdleHandlerCount <= 0) {
                        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);
                    }
                    //如果queueIdle()返回false,则该消息处理器将会从注册的列表中移除。
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                //重置pendingIdleHandlerCount为0,避免在下次for()中再次执行注册的空闲消息处理器。
                pendingIdleHandlerCount = 0;
    
                //在执行空闲消息处理器过程中,此消息队列中可能已经添加了新的消息,则不需要阻塞当前线程。
                nextPollTimeoutMillis = 0;
            }
        }
    

    Looper
    • 定义:消息循环器。
    • 作用:
      1:取消息。 从内部声明的MessageQueue中调用其next()取出代处理的消息。
      2:分发消息。 通过代处理的Message中声明的Handler把待处理消息发送至具体的Handler去处理。

    Handler
    • 定义:消息处理器。具体的待处理消息是要交给Handler来处理的。
    • 作用:
      1:添加消息。通过其内部持有的MessageQueue.enqueueMessage()把代处理消息添加至“消息队列”中,等待在何时的时机处理此消息。
      2:处理消息。调用Looper.loop()从MessageQueue中取得待处理的消息后,会把该消息交给与该消息“绑定”的Handler去处理。

    相关文章

      网友评论

        本文标题:Handler消息机制:消息机制的组成

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