安卓开发学习之消息机制源码阅读

背景

安卓里的消息机制,就是Handler-Message-Looper这些,也是安卓开发者应该熟悉的部分。


Looper

一切的开始都来源于Looper.prepare(),如果我们决定在某一个线程新建Handler来处理消息,那在发消息之前,一定要调用Looper.prepare(),这个方法的源代码如下

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

会先检查sThreadLocal.get()的返回值是不是空,是空的话,就抛出异常,表示一个线程只能有一个Looper,这个方法只能被调用一次;不是空的话,再调用sThreadLocal.set()方法,新建一个Looper对象,传入quitAllowed=true,整体set进sThreadLocal中

Looper对象的构造方法如下,构造了一个属于当前Looper对象的消息队列,并且保存的当前线程

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

那个sThreadLocal在哪里定义?就在Looper类刚开始,代码如下

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

上面一行说道:“sThreadLocal.get()会返回null,除非调用了prepare()”,我们点进去ThreaLocal类的set和get方法

先看set方法:

    public void set(T value) {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

调用了getMap()方法,如果返回null,就调用createMap()方法,否则把当前ThreadLocal和入参value设置到ThreadLocalMap对象中,此处的当前ThreadLocal就是Looper里的sThreadLocal静态final对象,value就是looper

我们看看getMap()方法对当前线程做了什么

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

返回的是当前线程的threadLocals对象,这个对象是null的话,返回后会调用createMap()方法,这个方法代码如下

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

就是把t.threadLocals实例化了一下,传入的参数this是当前的ThreadLocal对象,也就是Looper里的sThreadLocal对象,firstValue则是looper对象,这俩同时被绑定到了当前线程里的ThreadLocals对象中,至此,线程、sThreadLocals和looper三者被绑到了一起

我们再看看ThreadLocalMap的构造方法

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 根据sThreadLocal的哈希值(相当于当前线程的id),获取i,这个i,每个线程里应该是同一个数
            table[i] = new Entry(firstKey, firstValue); // 初始化的时候,把sThreadLocal和looper放到固定的首位
            size = 1;
            setThreshold(INITIAL_CAPACITY); // 把阈值长度缩小为2/3,如果当前size大于INITIAL_CAPACITY * 2 / 3,就扩容,重新计算哈希值
        }

其中table是Entry类型的数组,存放键值对(sThreadLocal和looper),看一眼它的构造方法

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k); // k是键,整个entry是一个ThreadLocal(也就是k)的弱引用
                value = v;
            }
        }

最终调用的Reference类的构造方法方法

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = queue;
    }

也就是说,entry是sThreadLocal的弱引用,而ThreadLocalMap保存了对Looper.sThreadLocal的弱引用,所以每个线程都保持了对Looper.sThreadLocal的弱引用。


回到ThreadLocal的set方法

    public void set(T value) {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

如果getMap()返回的t.threadLocals不是null,调用的是ThreadLocalMap.set()方法,代码如下

        private void set(ThreadLocal<?> key, Object value) { // 入参:Looper.sThreadLocal和looper对象

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); // 当前线程ThreadLocalMap的第一个位置

            for (Entry e = tab[i]; // 遍历Entry数组
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get(); // tab[i]对ThreadLocal对象的引用

                if (k == key) { // 找到Looper.sThreadLocal的引用
                    e.value = value; // 设置looper,返回
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value); // 如果没找到sThreadLocal的引用,考虑新建一个Entry,填补数组中的null
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash(); // 必要时数组扩容,重新计算哈希索引
        }

其实正常来说,由于Looper.sThreadLocal对应一个线程,而一个线程只有一个Looper,所以线程里的threadLocals中的Entry数组,里面最多就一项(第一项)不是null,这一项的键值对分别是Looper.sThreadLocal和此线程的Looper对象。故而上面代码段里的遍历循环,应该也会只执行一次


ThreadLocal.set()方法就是这样,接下来看ThreadLocal.get()方法,代码如下

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result; // 返回entry的value
            }
        }
        return setInitialValue(); // map或e是null,就创建一个新的map,把this和一个null分别作为键值对插入进去,键就是entry的引用
        // 同时返回null
    }

先是获取当前线程的ThreadLocalMap,再调用ThreadLocalMap.getEntry()方法,传入Looper.sThreadLocal静态对象,而后如果获取的Entry不是null,返回Entry的value,如果ThreadLocalMap和Entry有一个是null,就调用ThreadLocal.setInitialValue()方法。

重点就看ThreadLocalMap.getEntry()和ThreadLocal.setInitialValue()方法

先看前者,ThreadLocalMap.getEntry()源码如下

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1); // 根据key的哈希值获取目标entry的下标索引
            Entry e = table[i]; // 实际i是当前线程里ThreadLocalMap的第一个位置,因为key(Looper.sThreadLocal)是唯一的
            if (e != null && e.get() == key)
                return e; // 获取entry
            else
                return getEntryAfterMiss(key, i, e); // 或null
        }

e.get()方法代码如下

    public T get() {
        return getReferent();
    }

getReferent()方法是Reference类的native方法,应该是获取弱引用本身的对象,换句话说,结果应该是Reference的referent属性,其值则是在其构造方法中赋的,实际应该是Looper.sThreadLocal对象,详情请参加上文对Thread.threadLocals对象构造方法的解读。如果出了意外,e.get()获取到的对象不是Looper.sThreadLocal,就调用ThreadLocal.getEntryAfterMiss(),不过这种情况下,这个方法一般返回就是null了,它是遍历table,也就是遍历Entry数组,而Entry数组一个线程里一般就第一个可能有效,连第一个都不行了,应该就返回null了


然后回到ThreadLocalMap.getEntry()方法,e.get()按理说就是传进来的key(因为key就是Looper.sThreadLocal对象),所以就直接返回了对象e。之后返回ThreadLocal.get()方法,正常情况下就直接把e的value,也就是这个线程的looper返回出去,就完事儿了


ok,Looper.prepare()方法就是这样,它的作用就是给当前线程绑定一个静态常量sThreadLocal和一个new出来的Looper对象,其中sThreadLocal可以根据当前线程,获取保存在当前线程里ThreadLocalMap.Entry里的looper对象

然后看Looper.prepare()方法,这个方法是用来启动当前线程的消息轮询的,代码如下

    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(); // 从这个线程的消息队列的队首取出一个消息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ..

            try {
                msg.target.dispatchMessage(msg);
                ..
            } finally {
                ..
            }
            ..

            msg.recycleUnchecked();
        }
    }

去除无关的日志等代码后,其实就上面这几行。先是调用Looper.myLooper()方法获取looper对象,进去这个方法看看:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get(); // 此时ThreadMap的value是一个looper,这里就返回looper
    }

可见,就是从sThreadLocal中获取当前线程的looper,详情参见上面对sThreadLocal的分析。如果没有调用Loop.prepare()通过sThreadLocal给当前线程实例化looper的话,这里就会返回null,在loop()方法中抛出异常。


然后回到Looper.loop()方法,进入死循环,先是从此线程的消息队列队首获取一个消息,获取完后,调用msg.target.dispatchMessage()方法,这个msg.target就是一个handler对象,这一步就是让handler处理消息的,最后调用msg.recycleUnchecked()方法,回收这个消息到消息队列的队首

于是我们进入MessageQueue类的阅读


MessageQueue

MessageQueue的next()方法源码如下:

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr; // 如果队列内有消息,mPtr就不会是0
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ..

            nativePollOnce(ptr, nextPollTimeoutMillis); // 阻塞的native方法,会一直等到队列中有消息可获取

            synchronized (this) { // 同步
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages; // msg是要获取的队首Message
                if (msg != null && msg.target == null) {
                    .. // 一般msg.target不会是null
                }
                if (msg != null) { // 如果队列不为空
                    if (now < msg.when) {
                        // 还不到发送时间
                        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; // 老队首后继为null,就从队列中断开
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse(); // 设置标志位
                        return msg; // 返回
                    }
                } else { // 队列为空,啥也不做,重新循环,回到nativePollOnce()方法阻塞
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                .. // 其他处理
    
            }

            ..
        }
    }

主要是判断队首是否为空,为空的话,阻塞在死循环里;不为空的话,从队首获取消息,返回之


这个MessageQueue.next()方法是从消息队列的队首中获取消息,而相应的往队列里添加消息,调用的则是MessageQueue.enqueueMessage()方法,代码如下

    boolean enqueueMessage(Message msg, long when) {
        .. // 合法判断

        synchronized (this) {
            .. 

            msg.markInUse(); // 标志位
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 消息队列是空,或队首的when大于传入msg的when(也就是前者发送时间更晚),就把msg作为队首
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 否则
                // 如果采用默认的Handler构造方法,msg就不是异步的,needWake为false
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    ..
                } // 从队列开头遍历,遍历到队列末尾
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
                // 最终是把msg放到消息队列队尾
            }

            .. 
        }
        return true;
    }

可以看到,消息入队的时候,首先判断队列是否为空,或队首消息的发送时间是否比传入的msg发送时间晚,是的话,就把传入的msg放在队首;否则就放在队尾

故而,在looper进行消息循环的时候,从队首读取消息,从队尾添加消息。

从MessageQueue的这俩主要的方法可以看到,这个类主要维护了一个mMessages为队首的消息队列,这个队列是让Looper来从表头获取消息并进行处理的,而这个mMessages也是一个Message对象,所以,Message类本身就是的链表,MessageQueue则主要是维护一个消息的处理队列。

回到Looper.loop()方法,从消息队列队首获取消息后,如果不是null,就调用msg.target.dispatchMessage(msg)方法,target就是Handler对象,下面我们就看看Handler的这个方法


Handler

Handler.dispatchMessage()方法代码如下

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            .. // 回调
        } else {
            .. // 回调处理
            handleMessage(msg);
        }
    }

handleMessage()方法是在这儿调用的,这个方法需要我们自己实现

另外,我们在利用Handler发消息的时候,经常会用到handler.obtainMessage()来从消息队列中获取消息对象,这个方法代码如下

    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

调用了Message的静态方法obtain(),传入参数为handler类自身,我们进入Message类


Message

静态方法obtain()方法代码如下

    public static Message obtain(Handler h) {
        Message m = obtain(); // 从链表中获取表头
        m.target = h; // message.target指向handler自己,一般不会是null

        return m;
    }

私有方法obtain方法代码如下

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) { // 消息回收队首不是null
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m; // 从消息回收队首获取消息,队列长度-1
            }
        }
        return new Message(); // 队首是null,返回一个新的Message
    }

sPool是消息回收队的队首,静态对象,所以每个线程只有一个回收消息队列。

sPool作为队首,一开始是null,那何时不是null呢?在Message的recycleUnchecked()方法中会被赋值,而这个方法,也是Looper.loop()死循环的最后一步,表示消息队列的队首消息处理完后,会变成消息回收队列的队首。Message.recycleUnchecked()方法源码如下

    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        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++; // 插入到消息回收队列队首
            }
        }
    }

到此,Message中关于消息轮询机制的代码大致如此。可见Message中也维持了一个消息队列,但这个消息队列不是MessageQueue中的待处理的消息队列,而是一个回收在Looper中被处理过的消息的。


回到Looper

关于子线程的消息轮询大体如上所示,那么主线程呢?我们在主线程里new Handler的时候,并没有调用Looper.prepare()和Looper.loop(),依旧可以正常完成消息的处理,原因何在?

其实Looper里有一对专门给主线程准备的prepare()以及getMainLooper()方法,以及主线程的Looper对象,它们的相关代码如下

    private static Looper sMainLooper;  // guarded by Looper.class

    ....
    public static void prepareMainLooper() {
        prepare(false); // 主线程的allowQuit是false,意味不可退出
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

应该不用多解释,那么他们在哪儿被调用呢?在ActivityThread.main()方法里,这也是我们应用程序的入口

    public static void main(String[] args) {
        ....

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        ....
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
创建主线程的Looper后就进程活动线程(也就是主线程)和handler的创建,最后Looper.loop()进行循环。


结语

从阅读源码的过程来看,可以得出以下几个结论:

1、每个线程都最多有一个Looper、MessageQueue和消息回收队列sPool

2、消息处理流程大概是这样:Looper调用prepare()为当前线程创建looper对象,调用loop()方法轮询读取消息队列的队首消息,如果没有退出消息循环,而后利用handler进行消息处理,最后回收消息到此线程的消息回收队列队首中;至于Handler,它是往消息队列MessageQueue队尾塞进一个新消息,Looper则是在队首读取消息

3、一定要把自定义handler声明为静态的,因为比如在Activity中,Activity要销毁了,handler却还在处理消息,就导致Activity对象不能被销毁,导致内存泄漏,所以要把自定义handler定义为静态,让它的生命周期和activity等对象的生命周期脱离

4、主线程的自定义handler不用调用Looper.prepare()和Looper.loop()是因为ActivtiyThread在main()方法里已经调用了;而在子线程里如果定义handler要处理消息,就必须手动先后调用Looper.prepare()和Looper.loop()方法

猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/80705104