Android开发经验谈Android开发Android技术知识

回转寿司你一定吃过!——Android消息机制(分发)

2019-02-02  本文已影响6人  唐子玄

这是“Android消息机制”系列的第二篇文章,系列文章目录如下:

  1. 回转寿司你一定吃过!——Android消息机制(构造)
  2. 回转寿司你一定吃过!——Android消息机制(分发)

消息机制的故事


寿司陈放在寿司碟上,寿司碟按先后顺序被排成队列送上传送带传送带被启动后,寿司挨个呈现到你面前,你可以选择吃或者不吃。

将Android概念带入后,就变成了Android消息机制的故事:
寿司碟 ---> 消息(Message)
队列 ---> 消息队列(MessageQueue)
传送带 ---> 消息泵 (Looper)
寿司 ---> 你关心的数据

暂未找到 Handler 在此场景中对应的实体。它是一个更抽象的概念,它即可以生产寿司,又把寿司送上传送带,还定义了怎么享用寿司。暂且称它为消息处理器吧。

如果打算自己开一家回转寿司店,下面的问题很关键:

  1. 如何生产寿司(如何构造消息)
  2. 如何分发寿司(如何分发消息)

关于如何构造消息可以移步上一篇博客回转寿司你一定吃过!——Android消息机制(构造)。这一篇从源码角度分析下“如何分发消息”。

分发要解决的问题是如何将寿司从厨师运送到消费者。回转寿司系统是这样做的:将寿司挨个排好放在传送带上,然后让传送带滚动起来。对应的,在Android消息系统中也有类似的两个步骤:1. 消息入队 2. 消息泵
(ps: 下文中的 粗斜体字 表示引导源码阅读的内心戏)

1. 消息入队


关于入队需要提两个基本问题:(1)什么时候入队(2)怎么入队。第二个问题其实是在问“消息队列的数据结构是什么?”。特定数据结构对应特定插入方法。
对于消息队列一无所知的我完全没有了头绪,这源码该从哪里开始读起?没有思路的时候我们还可以YY(YY是人类特有的强大技能)。凭借着对数据结构残存的记忆,我隐约觉得“入队”应该是队列提供的基本操作,那就先从MessageQueue开始读吧~

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  “Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.”
 * <p>
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */
public final class MessageQueue
{
    ...
}

还好注释中的每个单词都看得懂,其中带双引号的那句话非常关键,它说“消息不是直接加到消息队列中的,而是通过Handler对象”。不急着去看Handler,先找一下MessageQueue是否有“入队操作”。

//省略了一些非关键代码    
boolean enqueueMessage(Message msg,
                           long when)
    {
        ...
        synchronized (this)
        {
            ...
            msg.markInUse();
            msg.when = when;
            //p指向消息队列头结点
            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
            {
                ...
                //从消息队列队头开始寻找合适的位置将消息插入
                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;
            }
        }
        ...
        return true;
    }
    /**
     * “Enqueue a message into the message queue after all pending messages
     * before the absolute time (in milliseconds) <var>uptimeMillis</var>.”
     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     * You will receive it in {@link #handleMessage}, in the thread attached
     * to this handler.
     * 
     * @param uptimeMillis “The absolute time at which the message should be
     *         delivered, using the
     *         {@link android.os.SystemClock#uptimeMillis} time-base.”
     *         ...
     */
    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);
    }
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

2. 消息泵


寿司已经按时间顺序排列好了,是时候按下按钮启动传送带让寿司循环起来了。对于Android消息机制来说,让消息循环起来就表现为不断从消息队列中拿消息。MessageQueue中有入队操作,必然有出队操作

//省略大量非关键代码
   Message next() {
        ...
        for (;;) {
            ...
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                //msg指向消息队列队头
                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) {
                        // 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;
                }
                ...
            }
        }
  }
    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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        //nandian
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        //取消息的无限循环
        for (; ; )
        {
            //从队头取出消息
            Message msg = queue.next(); // might block
            //没有消息则退出循环
            if (msg == null)
            {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null)
            {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                                        msg.callback + ": " + msg.what);
            }

            //分发消息
            msg.target.dispatchMessage(msg);

            if (logging != null)
            {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            //nandian
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent)
            {
                Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass()
                                                                                                                                                                        .getName() + " " + msg.callback + " what=" + msg.what);
            }

            //回收消息
            msg.recycleUnchecked();
        }
    
/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    @Override
    public void run() {
        mTid = Process.myTid();
        //1.准备Looper
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        //2. Looper开始循环
        Looper.loop();
        mTid = -1;
    }
}
/**
  * “Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.”
  */
public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    final MessageQueue mQueue;

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }
}

总结


Android消息机制中的“分发消息”部分讲完了,总结一下:发送消息时,消息按时间先后顺序插入到消息队列中,Looper遍历消息队列取出消息分发给对应的Handler处理

故事还没有结束,下一篇会继续讲解“处理消息”。

上一篇 下一篇

猜你喜欢

热点阅读