美文网首页Android知识Android开发经验谈Android开发
Android消息机制-Handler、Looper、Messa

Android消息机制-Handler、Looper、Messa

作者: andChen | 来源:发表于2017-08-08 00:41 被阅读0次

    由于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者的关系:

    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<T> 我们来看看它。

    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 总结

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

    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、Messa

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