Android Handler源码解析-消息机制

前言

网上Handler机制的文章很多,大部分只涉及Jave层部分,本系列文章会从Java层源码逐步分析引入到Native层源码,让你彻底了解Handler机制。
关于Handler的使用

一. Handler机制介绍

Handler机制作为一个老生常谈的东西我也不多做介绍,其不外呼就四个类。
Handler:发送、接收消息
Message:消息载体,内部保存了发送消息的Handler和指向下一条Message的引用
MessageQueue:消息队列,实际是一个基于时间的优先级队列,内部保存了一个Message链表的头结点,通过对这个头结点的遍历实现了Message链表的插入、删除等操作。
Looper:负责创建MessageQueue、开启消息循环、从MessageQueue中取出消息

二. 创建Message

发送Message首先要创建Message,有人说创建Message还不简单,直接new一个不就行了吗,正常情况下这样是可行的。但是某些情况下,例如开启了一个线程,里面在做循环计算,每次计算完毕后都要通知Handler去更新UI,那么如果每次发送消息都去new一个Message,这样会造成很大的内存浪费。
Android内部为我们实现了Message的缓存机制,通过Message.obtain()或者Handler.obtain()可从消息缓存池获取到Message。
由于Handler.obtain()内部也是调用的Message.obtain(),所以只要分析Message.obtain()即可。

废话不多说,上源码。

public final class Message implements Parcelable {
    //#1
    //延迟执行的时间
    long when;
    //发送消息的那个Handler
    Handler target;
    //Runnable回调,保存的就是Handler.post()里的那个Runnable
    Runnable callback;
    //指向下一个message
    Message next;

    //静态的对象锁(关于这里为什么要使用静态的对象锁而不使用Message.class可以自己研究下)
    public static final Object sPoolSync = new Object();
    //消息缓存池的头结点
    private static Message sPool;
    //当前缓存池里Message的个数
    private static int sPoolSize = 0;
    //缓存池最大容量
    private static final int MAX_POOL_SIZE = 50;

    //取出缓存消息
    public static Message obtain() {
        synchronized (sPoolSync) {
            //#2
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;//将下一个节点设为新的头结点
                m.next = null;//断开下一个节点的链接
                m.flags = 0;
                sPoolSize--;//缓存池计数减一
                return m;
            }
        }
        return new Message();
    }

    //暴露给用户回收消息
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    //回收消息
    void recycleUnchecked() {
        //一系列的重置操作
        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) {
            //#3
            if (sPoolSize < MAX_POOL_SIZE) {//缓存池未满,将消息加入链表
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

以上是Message的部分源码,有些地方加了注释,看着很长,其实只要关注#1、#2和#3处代码即可。
#1部分,是Message保存的变量,发送Message时需要携带这些参数,具体作用已经添加了注释。
#2#3是Message缓存池机制的代码,可以看到,这部分源码都是静态的,所谓的缓存池就是一个静态单向链表,通过储存静态变量sPool作为缓存池链表的头结点,sPoolSize表示当前缓存池链表的长度,存取缓存消息只需要通过sPool头结点操作链表即可,实际上缓存池就是一个栈的数据结构,遵循先进后出的原则。

obtain()

obtain方法用来从缓存池取消息。方法很简单,若缓存池有消息,就将缓存池链表的头结点sPool返回,并将sPool的下一个节点设为新头结点,然后进行sPoolSize--

recycleUnchecked()

系统通过recycleUnchecked方法来回收Message,当调用此方法,会重置当前Message的所有状态和变量,并将当前Message设为缓存池链表的头结点。

Q1:什么时候回收Message

我们知道了发送消息的时候需要使用obtain()方法进行Message复用,知道了recycleUnchecked()方法能回收当前Message,那么Message在什么时候回收呢,回收需要我们去调用Message.recycle()方法吗?

三. 创建Handler

Message已经创建好了,要发送这个Message还得通过Handler,创建Handler先就要创建Looper。Android主线程里已经默认创建好了Looper,所以在主线程里使用Handler是不需要我们再去创建Looper的。
主线程创建Handler的过程

public final class ActivityThread extends ClientTransactionHandler {
    public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();//创建主线程Looper
        ......
        ActivityThread thread = new ActivityThread();//这里面会创建主线程Handler
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();//获取主线程Handler
        }
        ......
        Looper.loop();//开启消息循环
        //后面的代码不会执行到,除非当前Lopper的循环退出了,在主线程就代表当前程序结束运行
    }
}
//#Looper
    public static void prepareMainLooper() {
        prepare(false);//也是调用的prepare()方法
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

可以看到主线程使用Handler也是先通过prepare()创建Looper,之后创建Handler,最后调用Looper.loop()开启消息循环。

Q2:Handler创建流程为什么一定要Looper.prepare()->创建Handler->Looper.loop()
1. 先看第一步,创建Looper
//#Looper
static final ThreadLocal sThreadLocal = new ThreadLocal();
    //通过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));
    }

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

Looper的创建过程非常简单,先调用Looper的构造函数,Looper的构造函数里创建了一个MessageQueue,随后将创建的Looper设置为ThreadLocal变量,通过ThreadLocal可以确保一个线程对应一个Looper(若对ThreadLocal的用法不太了解可以去网上寻找资料)

2. 创建Handler
public class Handler {
    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;
    final boolean mAsynchronous;

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();//获取当前线程Looper
        if (mLooper == null) {//如果当前线程没有创建过Looper就抛出异常
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//获取当前线程的消息队列
        mCallback = callback;//Handler Callback写法的回调
        mAsynchronous = async;//是否异步消息
    }
}
//#Looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

Handler构造函数主要做了一件事,先去拿当前线程的Looper,通过Looper再拿到MessageQueue。

Q2的第一点:创建Handler之前要调用Looper.prepare()
看源码可知Handler需要拿到MessageQueue,而MessageQueue是在Looper里创建的,所以创建Handler前必须先创建Looper才能拿到MessageQueue。

3. Looper.loop()开启消息循环
//#Looper
    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;//由于MessageQueue是Looper里的对象,所以MessageQueue里取出的Message都是在Looper线程里,即不管在哪个线程发送消息,接收消息都是在调用了Looper.prepare()方法的那个线程接收。

        for (;;) {//所谓的消息循环其实就是一个死循环,作用是不断的从MessageQueue里面读取消息
            Message msg = queue.next(); //这是一个阻塞方法,当MessageQueue里没有下一条消息或者下一条消息是延迟消息时会进入阻塞状态
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);//内部会回调Handler的handlerMessage()方法或者Runnable的run()方法
            } catch (Exception exception) {
                throw exception;
            } finally {
            }
            msg.recycleUnchecked();//回收Message
        }
    }

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

以上代码是精简过的,loop()方法里开启了一个无限for循环,不断的调用MessageQueue的next()方法尝试从消息队列里面读取Message,而next()方法是一个阻塞方法,当没有可读消息时会进入阻塞状态且不占用cpu资源,这样尽管loop中是一个死循环也不会造成卡死的情况。
在拿到Message后,会调用msg.target.dispatchMessage(msg),而target其实就是Handler,来看Handler的dispatchMessage(msg)方法

//#Handler
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }

很简单,先判断msg是否有设置Runnable回调,若有设置就去调用Runnable的run(),若没有设置Runnable,则根据构造Handler的方式去回调Callback的handleMessage(msg)或者回调Handler的handleMessage(msg)
当通过handler.post()设置了Runnable回调时,只回调Runnable的run方法,不会去执行handler.handleMessage方法

Looper构造函数要创建MessageQueue、Handler构造函数要拿到Looper里的MessageQueue、Looper.loop()开启消息循环也是从MessageQueue里取消息,可见消息的传递应该都是围绕MessageQueue进行的,那么MessageQueue是何方大佬竟然这么重要,接下来就要分析它。

Q1:什么时候回收Message。
通过以上源码可知,在系统调用handler.dispatchMessag()后就会调用msg.recycleUnchecked()方法将Message回收,所以一般情况下,并不需要我们去特意回收Message。
当然,在调用messageQueue.removeMessages等方法移除消息队列中的消息时也会将Message回收。

Q2的第二点:为什么要先创建Handler才能去Looper.loop()。
由于loop()方法里是一个死循环,所以循环后面的代码都不会执行到,所以必须先创建Handler才能去调用Looper.loop()

四. 发送Message

发送消息有发送Message和发送Runnable两种方式,其实发送Runnable也是发送Message,只不通过getPostMessage()方法过把Runnable变成Message的一个callback参数

//#Handler
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

无论通过Handler的哪种方式发送,最后都会调用MessageQueue的enqueueMessage(msg, uptimeMillis)方法。
借用下其它博客的一张图片


五. MessageQueue

1. 先看enqueueMessage()方法
//#MessageQueue
    boolean enqueueMessage(Message msg, long when) {
        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();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {//#1,插入头结点
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {//#2,根据when插入到合适的位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }
            if (needWake) {//#3
                nativeWake(mPtr);//nativa方法,作用是唤醒nativePollOnce方法的阻塞
            }
        }
        return true;
    }

首先了解下when是什么:when=SystemClock.uptimeMillis() + delayMillis,SystemClock.uptimeMillis()是开机到现在的毫秒数,那么when就是一个时间节点
#1:将新消息msg的when和头结点mMessages的when作比较,若msg是早于mMessages执行的,就将mMessages设为msg的next,然后将msg设为新的mMessages,这样msg就插入到了链表的头结点。
#2:若msg不能插入链表头结点,则开启循环,不断比较链表下一个元素的when和msg的when大小,直到msg的when小于某个节点的when,就将msg插入此处。
#3:调用nativeWake(mPtr),唤醒nativePollOnce(ptr, nextPollTimeoutMillis)。
简单来说,enqueueMessage()方法的作用就是通过比较when的大小,将新msg消息插入消息链表对应的位置(即msg的when小于链表某节点的when时对应的那个位置)

2. MessageQueue.next()

知道了消息是怎么存到MessageQueue里的,那么怎么从MessageQueue里取消息呢?答案就是之前在Looper.loop()里用到的next()方法,我们知道loop()会不断循调用MessageQueue.next(),那么MessageQueue.next()应该就是用来取出MessageQueue里链表下一条消息的,具体怎么取呢?请看源码。

//#MessageQueue
    @UnsupportedAppUsage
    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //#1
            //nextPollTimeoutMillis<0 一直阻塞
            //nextPollTimeoutMillis=0 不阻塞
            //nextPollTimeoutMillis>0 阻塞nextPollTimeoutMillis时间
            nativePollOnce(ptr, nextPollTimeoutMillis);//nativa的阻塞方法

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //#2
                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) {
                    //#3
                    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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 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()方法里也开启了一个for循环。进入循环后会调用nativePollOnce(ptr, nextPollTimeoutMillis)方法进入阻塞状态,该方法是一个nativa的阻塞方法,唤醒该方法的方式有两种
第一种方式:nextPollTimeoutMillis指定的延迟时间已到,会自动唤醒该方法。
第二种方式:当有新消息插入后,会调用nativeWake方法唤醒nativePollOnce,nativeWake方法在之前分析enqueueMessage已经说明。

#2处代码暂时不分析,先看#3处代码。
#3:判断当前时间和msg的时间大小,若当前时间小于msg时间,则说明这条新消息需要延迟返回,设置nextPollTimeoutMillis 延迟的时间为msg.when - now,之后进入下一次循环并进入阻塞状态,等待延迟时间到或者有新消息加入唤醒阻塞。
若当前时间大于等于msg时间,说明这条新消息需要立即返回,取出消息链表的头结点返回并将头结点的next节点设为新的头结点。

这时有人会问loop()里面也是一个for循环,取个消息需要两个for循环吗?这就要提到我之前说的那句话了,MessageQueue不是一个队列,按照正常的逻辑,若MessageQueue是一个队列,那么根据先进先出的原则,next()方法应该立即返回最早push进去的那条消息,实际上并不一定。因为Message中有一个很重要的变量when存在,假设next()里面没有循环,使得即使在loop()里调用next()方法,代码运行到nativePollOnce方法进入阻塞状态,当有新延迟消息加入唤醒nativePollOnce方法时,由于旧消息延迟时间还未到,而新加入的消息也是延迟消息,这时next()方法便取不到消息只能返回null,而MessageQueue里明显是有消息的,显然这不合理。
loop的循环负责从MessageQueue里不断地取出消息,next()里的循环负责从消息链表正确的取出对应的那条消息

Handler机制大致原理分析完了,不过肯定还有很多小伙伴云里雾里,我们先来总结一下。
  1. 一个线程对应一个Looper,一个Looper对应一个MessageQueue,创建Looper时会顺带创建了MessageQueue
  2. Handler机制消息轮询的三个重要方法,MessageQueue.enqueueMessage、MessageQueue.next、Looper.loop
    MessageQueue.enqueueMessage:往链表插入一条消息
    MessageQueue.next:阻塞方法,从链表取出一条消息
    Looper.loop:从MessageQueue里循环读取消息
  3. 发送消息时,Runnable也会被封装成一个Message,最终会调用MessageQueue的enqueueMessage(msg, uptimeMillis)方法
这里再说两个常见的问题

Q1:主线程中loop死循环为什么不会造成程序卡死
A:Android是消息驱动的,每一次事件、更新UI等响应都是在loop里面进行,正是由于loop中是一个死循环,这才可以保证程序运行后不会结束,且在没有响应事件时,loop会进入阻塞状态释放CPU资源,以上原因保证了程序的正常运行。
Q2:Handler是怎么切换线程的
A:线程切换的原理就是线程间的资源共享。因为Looper是一个ThreadLocal变量且属于创建Handler时的线程,所以可以保证无论发送消息在哪个线程,通过Looper从MessageQueue取出的消息都是在Handler线程。

下一篇讲讲Handler屏障机制。

你可能感兴趣的:(Android Handler源码解析-消息机制)