Android消息机制-Handler、Looper、MessageQueue

由于Andoird的UI元素采用的单一线程模型,只能在UI线程进行更新,于是我们用来更新UI,常用Handler进行更新,用法如下:

   mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
       public boolean handleMessage(Message msg) {
           //do ui update
           return false;
       }
   });
   Message message = new Message();
   mHandler.sendMessage(message);

用法简单明了,接着我们直接来体会一下 消息是如何被传送到主线程的。

0x00 有些什么?

Handler: 我们通过它来发送消息。
Looper: 构造Handler时,需要传入一个 Looper对象。例子中我们传入了主线程的Looper。

MessageQueue:当我们使用 Handler将一条Message sendMessageDelayed 出去,在Handler源码中追踪,调用路径如下:

Handler.sendMessage
   sendEmptyMessage
       sendEmptyMessageDelayed
           sendMessageAtTime
               enqueueMessage
                   msg.target = handler;// set target -> Handler
                   MessageQueue.enqueueMessage(msg, uptimeMillis)

这边出现了我们的新的对象 MessageQueue。很明显的,sendMessage的操作,其实是将Message 放入了MessageQueue中。

我们先来大致梳理一下3者的关系:

Android消息机制-Handler、Looper、MessageQueue_第1张图片
img

简言之,我们在子线程,生成了一个持有主线程 Looper的Handler。

  1. 我们通过Handler将Message 传递到了主线程,加到了对应的MessageQueue中。
  2. 主线程的Looper读取Message,并且处理。

其中,有几个关键点:

  1. 如何找到 目标线程?
  2. MessageQueue如何对新增Message进行处理的?
  3. Message如何被消费的?

我们一个一个看。

0x01 Handler如何找到目标线程,并且发送Message?

从上面的sendXXX调用堆栈可以看出,最终调用到MessageQueue.enqueueMessage(msg, uptimeMillis)。那么,Handler中的MessageQueue 是怎么来的呢?

我们可以从构造器中找到答案:

public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

来自Looper,看记得那个Looper么?-> Looper.getMainLooper().
我们来看看Looper:

 public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
 ...
 sMainLooper = myLooper();
 ...
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

这里出现了一个很重要的对象:sThreadLocal->ThreadLocal 我们来看看它。

ThreadLocal 原理

这是一个用来做线程内部存储的类,通过它可以再指定的线程中获取、存储数据,并且只有在该线程里才能获取,其他线程无法获取。而我们的不同线程的Looper 就是放在ThreadLocal中。对外提供的方法很简单:

  1. public T get()
  2. public void set(T value)

我们大致看看实现:

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = current.localValues;
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = current.localValues;
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
    
    class Values {
        ...
        void put(ThreadLocal key, Object value) {
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }
                ...
            }
        }
        ...
    }

主要思路是将数据结构放在了Thread.localValues,用某种算法进行了下标计算,得出 put、get的index。

Ok,那么现在,我们的Looper、以及它的MessageQueue都非常eazy的获取到了。

0x02 MessageQueue如何对新增Message进行处理的?

首先,我们需要了解一下MessageQueue的结构。最重要的一个变量是:Message mMessages;

而在Message也有几个重要的变量:

  1. Message next; 标志下一个Message
  2. Handler target 目标Handler是什么,用于做消息派发(还记得上面代码部分,有设置过么? msg.target)。

从中我们可以看出,MessageQueue是个单链表结构。

此时,我们再回到那个函数:MessageQueue.enqueueMessage(msg, uptimeMillis)

boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            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 {
                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;
            }
            ...
        }
        return true;
    }

可以很明显看出,这是一个根据 msg.when 为依据,做的链表插入。这样,我们的Message就能按照正确时间的顺序,进行排列。

之外:

MessageQueue还有其他的操作,都是对单链表的操作。如:

  1. private void removeAllFutureMessagesLocked()
  2. void removeMessages(Handler h, Runnable r, Object object)

0x03 Message如何被消费的?

我们有一块很重要的东西,那就是Looper,来看看前面那张图,Looper 会从Message中,不断的读取 Message。是如何做到的呢? 我们试着来看看源码。

现在我们没有了头绪,转过来,我们先来看看主线程是怎么启动的。

主线程的启动过程中对Looper的处理。

我们知道,主线程启动,是通过ActivityThead.main(String[] args) 方法进行启动,我们一起看看:

public static void main(String[] args) {
        ..
        Looper.prepareMainLooper();
        ...
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();
        ...
}

里面有几行对于looper的处理,有2个关键的操作:

  1. Looper.prepareMainLooper
  2. Looper.loop();

Looper.prepareMainLooper

prepareMainLooper会接着调用到prepare(boolean quitAllowed)

prepare的过程比较简单。我们大致看一眼Looper中的部分:

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

还记得 上面的 ThreadLocal吗?prepare的过程,其实就是生成一个Looper,往ThreadLocal 中塞。

Looper.loop();

这个是最核心的部分,我们依然上源码:

 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;
       ...
       for (;;) {
           Message msg = queue.next(); // might block
           ...
           msg.target.dispatchMessage(msg);
           ...
       }
   }

第一行获取myLooper()的过程,也就解释了为什么要先做“prepare”操作。

接着,会是一个死循环,核心步骤2步:

  1. 不断的从MessageQueue.next 中读取Message。
  2. 并且将消息进行派发-msg.target.dispatchMessage(msg);

我们接着看这2步:

MessageQueue.next

这个方法会返回一个Message,但是是一个阻塞方法,在适当的时候才会返回,这也就是为什么我们可以使用sendxxxDelay(...)在一段时间后才执行的原因。

我们来看源码:

Message next() {
        ...
        for (;;) {
            ...
            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) {
                        // 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;
                }
                ...
            }
            ...
            // 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;
        }
    }

为了好看,缩减了一些片段。next() 方法,里面也有一个死循环,当message.when到了之后,才会返回。

消息派发:msg.target.dispatchMessage(msg);

这步能够找到正确的target,完全依赖于在 Handler.sendMessage() 做 enqueueMessage 时设置的:msg.target = handler;

所以,能够用正确的Handler进行派发,Handler派发逻辑:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

注意哦,这里进行消息处理,是Looper调用过来的,因此是在 目标线程。

0x04 总结

我们再来回忆一下这张图:

Android消息机制-Handler、Looper、MessageQueue_第2张图片
img

发送消息流程:

  1. 新建Handler,指定Looper。
  2. 调用sendXXXX方法。
  3. Handler将消息发送到 Looper.MessageQueue中。

注意几个细节:

  1. Looper通过ThreadLocal找到。
  2. 当一个线程需要用Looper时,要先调用Looper.prepare(),目的就是为了在ThreadLocal中创建当前线程的Looper。

消息处理过程:

  1. 每个线程调用Looper.loop(),是个死循环,会不断的从MessageQueue.next读消息进行处理。
  2. MessageQueue.next也是个阻塞函数,会等到有可处理的消息时返回Message对象。
  3. 对Message消息,会调用message.target.dispatchMessage进行消息传递。

注意几个细节:

  1. 主线程已经进行了Looper.prepareMainLooper,子线程也可以用handler进行消息传递,但是得先调用 Looper.prepare() 为当前线程创建Looper。

你可能感兴趣的:(Android消息机制-Handler、Looper、MessageQueue)