欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入理解android消息机制(四)——消息队列延时机制(很有趣)

程序员文章站 2022-07-14 18:24:24
...

sendMessge最终会保存在消息队列中,那么消息队列如何工作呢?本文就探究一下
message用来描述消息和包含数据,handler发送的都是这个对象,主要有一下字段:

 public int what;


    public int arg1; 


    public int arg2;


    public Object obj;


    public Messenger replyTo;


    public int sendingUid = -1;

    /*package*/ static final int FLAG_IN_USE = 1 << 0;


    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;


    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

    /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    /*package*/ Message next;

从上面可以看到,字段主要分为两类,一类公有的,包括what、arg1、arg2、obj和replyTo;一类访问为包权限,target、callback和next等。从这可以看出Message是一个链表中的节点,MessageQueue是一个单链表结构的队列。、
message内部维护了一个消息池

private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;
    //==========================================================
     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
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

上面的方法主要是从对象池中得到消息,如果对象池为空,那么就新建一个Message并返回。既然有对象的获取,那么自然有对象的回收,回收在recycle方法中,如下:


public void recycle() {
        //如果仍在使用中
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        //不再使用中了,可以进行回收
        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 = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

当一个handler在send消息时: 所有的handler的send方法 sendMessage(Message msg) ,sendEmptyMessageDelayed(int what, long delayMillis)
sendMessageDelayed(Message msg, long delayMillis)
都会走到sendMessageAtTime,在此方法内部有直接获取MessageQueue然后调用了enqueueMessage方法,我们再来看看此方法

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);  
   }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //赋值target属性
        msg.target = this;
        //如果设置了异步
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //调用MessageQueue的enqueueMessage方法
        return queue.enqueueMessage(msg, uptimeMillis);
    }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
boolean enqueueMessage(Message msg, long when) {
        //如果Handler为null,异常;因为处理时找不到谁该处理这条消息
        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;
            }

            //置Flag
            msg.markInUse();
            //消息执行的时间点,绝对时间
            msg.when = when;
            //队列的头结点
            Message p = mMessages;
            boolean needWake;
            //如果队列为空或消息需要立即执行或消息执行时间比头的执行时间还小,将这个消息作为头
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    //

上述代码就是将消息放入队列中,下面分析从消息中取出消息。

Looper的loop里面,有个取消息方法

Message msg = queue.next(); // might block
//================================================================================
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) {
                //轮询消息,如果有则返回
                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) {
                        // 设置轮询时间为消息剩余时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 得到一个消息
                        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;
                }

                // 如果退出了
                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;
        }
    }

从这个方法中可以看出,handler获取时间的方式是调用SystemClock.uptimeMillis(),并用它和消息的里包含的时间进行对比。
同时next()方法内部如果有阻塞,会把mBlocked设置true,在下一个Message进队列时会判断这个message的位置,如果在队首就会调用nativeWake()方法唤醒线程。
总结
1. 如果我sendEmptyMessageDelayed发送了消息A,延时为500ms,这时消息进入队列,触发了nativePollOnce,Looper阻塞,等待下一个消息,或者是Delayed时间结束,自动唤醒;
2. 在1的前提下,紧接着又sendEmptyMessage了消息B,消息进入队列,但这时A的阻塞时间还没有到,于是把B插入到A的前面,然后调用nativeWake()方法唤醒线程
3. 唤醒之后,会重新都取队列,这是B在A前面,有不需要等待,于是直接返回给Looper
4. Looper处理完该消息后,会再次调用next()方法,如果发现now大于msg.when则返回A消息,否则计算下一次该等待的时间

看到这里你可能想说,这和我的问题没什么关系啊,别急,答案就在SystemClock.uptimeMillis(),handler是通过它来获取时间的,但uptimeMillis()是不包括休眠的时间的,所以手机如果在休眠状态下(android 7.0在灭屏情况下很容易进入休眠),那时间就一直不变,至于中途又发送消息了,那是因为手机被唤醒了(android7.0会定时唤醒手机,接收消息),这时执行完delay操作,就可以发送消息了。

删除消息
Handler不止可以发出消息,还可以对已经发出且未执行的消息进行取消。可以调用removeXXX方法,有如下方法

  public final void removeCallbacks(Runnable r)
    {
        mQueue.removeMessages(this, r, null);
    }


    public final void removeCallbacks(Runnable r, Object token)
    {
        mQueue.removeMessages(this, r, token);
    }


    public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null);
    }


    public final void removeMessages(int what, Object object) {
        mQueue.removeMessages(this, what, object);
    }


    public final void removeCallbacksAndMessages(Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }

主要有上面五个方法,可以分别用于删除提交的任务,what字段等;而最终都是调用MessageQueue的removeMessages方法,下面是其实现:

void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.what == what
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

从上面的代码可以看出,主要根据Handler、what和object三个字段判断一个消息是否应该被移出队列,只有当三个条件都满足时,才会将消息移出队列。
如何退出消息循环
前面的分析中一旦调用了loop方法后,就进入了一个死循环,这也意味着创建Looper的那个线程一直运行着,那么如何退出消息循环呢?
退出循环,可以调用Looper的quit和quitSafely方法,如下:

public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

quit和quitSafely方法的区别在于:
- quit方法不会再执行消息队列中的任何消息
- quitSafely方法会执行消息队列中的消息;但是对于执行时间超过当前时间消息,则删除。

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            //安全退出
            if (safe) { 
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

//可以看到如果是安全退出,那么会调用removeAllFutureMessagesLocked方法,
//否则调用removeAllMessageLocked方法,首先看removeAllMessagesLocked方法删除所有消息:
private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

从代码可以看到,主要就是遍历队列,将每一个消息回收进消息对象池中,清空队列。
下面再看安全退出的情况:

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;
                    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);
            }
        }
    }

从上面的代码可以看到安全退出的处理逻辑,如果消息都是延迟的,那么全部删除;如果有不是延迟的,那么留给loop方法继续取出。
从上面的分析,我们知道了Handler控制MessageQueue的消息入队,Looper负责轮询MessageQueue,一旦发现需要执行的Message,就根据其target字段分开给发送的Handler,而分发就是dispatchMessage方法,Handler可以重写handleMessage来处理得到的消息。