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

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去处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266