美文网首页安卓学习
一张图学习安卓的消息机制

一张图学习安卓的消息机制

作者: 小怪兽大作战 | 来源:发表于2019-03-03 00:00 被阅读9次

1 什么是消息机制

一句话概括消息机制:一个线程持续监听并处理其他线程交给该线程的任务。

结合实际开发中的例子来说:所有的UI更新操作都应该是在UI线程(ActivityThread)中完成。

因为安卓的UI操作是线程不安全的。如果并发地对UI进行操作,会产生线程安全问题。即使在并发中加入锁,也会导致某些线程得不到锁而阻塞,降低执行效率;也提高了安卓的学习门槛,对初学者不利。所以,安卓系统将UI操作设计为单线程模式,只有在UI线程才能对UI进行操作。

如果其他线程需要更新UI,则需要转换到UI线程中。怎么转换到UI线程呢?就需要用到消息机制。

其他线程通过所持有的UI线程中的handler引用,发出更新UI请求。MessageQueue使用一个单链表将所有请求保存起来。Looper(UI线程中)不断从MessageQueue中读取请求,将请求分发到目标handler中处理。
在该过程中,handler既是发送请求者也是处理请求者;MessageQueue是存储请求者;Looper不断监听请求,并将请求分发到指定的handler中处理。

首先我们学习一下安卓消息机制中的几个关键组成

2 MessageQueue

消息队列,在内部使用单链表存储着本线程中待执行的所有消息。一个线程只有一个MessageQueue
。MessageQueue提供插入消息(enqueueMessage(Message,long))和读取消息(Message next())的操作。

2.1 Boolean enqueueMessage(Message msg,long when)

boolean enqueueMessage(Message msg, long when) {
        // msg 必须有target也就是必须有handler
        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;
            }

            msg.markInUse();
            //when 表示这个消息执行的时间,队列是按照消息执行时间排序的
            //如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // p==null 表示当前消息队列没有消息
                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;
                //将消息放到队列的确切位置,队列是按照msg的when 排序的,链表操作自己看咯
                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;
            }

            // 如果需要唤醒Looper线程,这里调用native的方法实现epoll机制唤醒线程,我们就不在深入探讨了
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

enqueueMessage是一个线程同步的方法,首先判断是否需要唤醒主线程(因为主线程会不断读取消息队列中的消息,如果没有消息,主线程会暂停不占有CPU的时间片),如果现在消息队列中没有消息,并且新插入的消息需要立即执行的时候,需要唤醒主线程。然后,根据when将消息插入到链表中的适合位置。

2.2 Message next()

next方法是一个无限循环的方法,如果链表中存储有消息,返回链表头部的消息(最先插入的消息),如果链表中没有消息,调用Binder.flushPendingCommands()使线程阻塞。

Message next() 
       
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration

        //nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis 
        //才会返回
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行)
            //一种是等到有消息产生就会返回,
            //另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回
            nativePollOnce(ptr, nextPollTimeoutMillis);
            //nativePollOnce 返回之后才能往下执行
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 循环找到一条不是异步而且msg.target不为空的message
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                       // 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay,
                       //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                       //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获取到消息
                        mBlocked = false;
                       //链表一些操作,获取msg并且删除该节点 
                        if (prevMsg != null) 
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        //返回拿到的消息
                        return msg;
                    }
                } else {
                    //没有消息,nextPollTimeoutMillis复位
                    nextPollTimeoutMillis = -1;
                }
                .....
                .....
              
    }

Looper

Looper在消息机制中负责消息的分发,Looper会不断调用MessageQueue的next()方法获取消息(这时,线程也切换到了looper线程,完成了线程切换)。如果获得消息,根据该消息的目标handler(handler.target),调用Handler.dispatchMessage(msg)方法将消息分发出去。如果没有获得消息,则会一直阻塞(因为MessageQueue.next方法阻塞,导致looper也会阻塞)。

public static void loop() {
    final Looper me = myLooper();  //获取TLS存储的Looper对象,获取当前线程的Looper 
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
 
    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列
    ....

    for (;;) { //主线程开启无限循环模式
        Message msg = queue.next(); //获取队列中的下一条消息,可能会线程阻塞
        if (msg == null) { //没有消息,则退出循环,退出消息循环,那么你的程序也就可以退出了
            return;
        }
        ....
        //分发Message,msg.target 是一个Handler对象,哪个Handler把这个Message发到队列里,
        //这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写
        //的handler的handleMessage方法。
        msg.target.dispatchMessage(msg);
        ....
        ....
        msg.recycleUnchecked();  //将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。
    }
}

在Looper的loop方法中,会自动获取该线程的消息队列。
一个线程只有一个Looper,为了实现每个线程都有自己的Looper实例,安卓中使用了ThreadLocal。ThreadLocal可以为每个线程保存线程私有的对象。主线程的Looper是在ActivityThread.main方法中创建的(在安卓Application的启动中我们学习到了一个App的开始是ActivityThread的main方法,在main方法中会创建ActivityThread的实例,创建Looper,Handler,将自身绑定到AMS中)。

Handler

Handler在消息机制中负责消息的发送和消息的处理。分别提供一系列send和post方法来发送消息,dispatchMessage来处理消息。

发送消息

Handler虽然提供了很多发送消息的方法,但最后都会调用MessageQueue的enqueueMessage(msg)方法,而该方法就是将消息插入到消息队列中。

处理消息

在Looper的分析中我们知道,Looper获取到消息msg后,会调用msg.target.dispatchMessage方法来分发消息。msg.target就是获得该msg的目标handler。通过调用handler的dispatchMessage方法,Handler获得消息,并调用handlerMessage方法来处理消息。这样,就完成了消息的传递和处理。

ThreadLocal

经过前面的学习我们知道,一个线程中Looper和MessageQueue只有一个,而同一进程中多个线程是共享内存的,我们如何为每个线程保存一个Looper实例呢?这就用到了ThreadLocal。

ThreadLocal.set(T value)

    public void set(T value) {
        //获得当前线程
        Thread currentThread = Thread.currentThread();
        //根据当前线程获得Values 对象
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        //根据ThreadLocal对象和需要存储的value将value保存到适合的位置
        values.put(this, value);
    }

当我们调用ThreadLocal.set(T value)方法的时候,会首先获得当前线程,根据线程返回一个Values ,然后调用values.put(this, value);将对象保存到values中。继续看values.put(this, value)方法。

 void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

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

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

put方法中,根据ThreadLocal对象产生一个hash值,然后根据这个hash值将value保存到数组对应的下标中。
到这里我们知道了ThreadLocal的工作原理:每一个线程中,都有一个对应的Values对象,该对象中有一个数组,用于保存该线程的私有对象。当调用ThreadLocal.put方法的时候,首先会获得当前的线程,根据当前的线程来获得本线程的Values对象。然后根据ThreadLocal对象生成一个Hash值,将需要存储的对象保存到Values内部的数组中hash值对应的下标位置处。

T get()

    public T get() {
        // Optimized for the fast path.
        //获取当前线程
        Thread currentThread = Thread.currentThread();
        //根据线程来获得Values对象
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            //计算hash值
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                //返回对应下标的对象
                return (T) table[index + 1];   
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

知道了ThreadLocal的set方法,那么get方法就很容易理解了,我就不再多说了。

下面用一张图来总结一下。


image.png

在该图中,我们创建了两个线程线程1和线程2.在主线程中创建了两个handler:Handler1和Handler2。
1.线程1持有Handler1的引用,调用Handler1的sendMessage方法将msg1发送出去。Handler1获取本线程(ActivityThread)的MessageQueue,调用MessageQueue的enqueueMessage将msg1添加到链表中。
2.线程2持有Handler2的引用,调用Handler2的sendMessage方法将msg2发送出去。Handler2获取本线程(ActivityThread)的MessageQueue,调用MessageQueue的enqueueMessage将msg2添加到链表中。
3.主线程中,Looper不断调用MessageQueue的next方法,获取消息对象。
4.Looper获得了msg1对象,msg1的target中存储着目标Handler,Looper调用Handler1.dispatchMessage方法将msg1发送到Handler1中。Handler1调用handlerMessage方法来处理msg1。
5.Looper获得了msg2对象,msg2的target中存储着目标Handler,Looper调用Handler2.dispatchMessage方法将msg2发送到Handler1中。Handler2调用handlerMessage方法来处理msg2。

注意

1.我们在自己创建的线程中创建并使用Looper时需要注意,在不适用Looper的时候需要Looper.quit(立即退出)或者Looper.quitSafely(执行完消息队列中所有的消息后才会退出),否则looper会一直阻塞在这里,导致线程不能停止。因为Looper会不断读取MessageQueue的消息,如果读取不到则会阻塞在这里,如果我们不使用的时候不对其停止,Looper会一直阻塞,造成内存泄漏。
2.ActivityThread的main方法时一个APP的入口。在main方法中,会自动调用Looper.prepareMainLooper()方法来创建主线程的Looper以及MessageQueue。所以在主线程中创建Handler我们不用先创建Looper。但是在其他线程中默认是不创建Looper的,我们需要先创建Looper,然后创建Handler,否则会报错。
3.主线程(ActivityThread)的Looper在ActivityThread.main方法中创建后,还会创建一个Handler来消息队列进行交互,这个Handler就是H。这个H有着很重要的作用,在ActivityThread将自身绑定到AMS中(通过ApplicationThread),AMS会通过ApplicationThread和ActivityThread通信,发出一系列指令(如:启动acitivity,绑定Application等),这些命令最后都会加入ActivityThread的消息队列中,最终由H来处理。

相关文章

  • 一张图学习安卓的消息机制

    1 什么是消息机制 一句话概括消息机制:一个线程持续监听并处理其他线程交给该线程的任务。 结合实际开发中的例子来说...

  • Android——Handler机制

    什么是安卓消息处理机制? 消息机制本质:一个线程开启循环模式持续监听并依次处理其他线程给他发来的消息。 安卓消息机...

  • 安卓消息机制

    1:安卓系统通过handler来发送消息,主要作用是为了更新UI,或者执行一些逻辑操作 2:安卓消息机制通过han...

  • 安卓消息机制详解

    写在前面的话 提起安卓的消息机制,我们马上就会联想到Handler,而Handler在日常的开发中经常会用到,因此...

  • 解析安卓消息机制

    我们都知道,系统是不允许在子线程中访问UI的,但如果在主线程中进行耗时操作,又会极大地妨碍用户体验。所以,我们可以...

  • Handler机制原因,主线程looper.loop()为什么不

    Handler,Message,looper 和 MessageQueue 构成了安卓的消息机制,handler创...

  • Handler机制(一)

    安卓的异步消息处理机制就是handler机制。 主线程,ActivityThread被创建的时候就会创建Loope...

  • 通过一张图来学习安卓的binder机制

    一、什么是binder binder从不同的角度来说有不同的解释 1.首先从功能上来说,binder是安卓中特有的...

  • 谈谈ThreadLocal

    做安卓的同学想必,一提到ThreadLocal会首先想到安卓中大名鼎鼎的handler消息机制,或许也大概是从了解...

  • Android异步消息机制-深入理解Handler、Looper

    Android异步消息机制-深入理解Handler、Looper和MessageQueue之间的关系 相信做安卓的...

网友评论

    本文标题:一张图学习安卓的消息机制

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