Android面试:Handler机制全面详解

289 阅读18分钟

前言

做Android开发一定绕不开Handler机制,关于Handler机制大家也是耳熟能详了,Looper+MessageQueue+Mesage+Handler组成,Handler将msg放入MessageQueue,looper循环从MessageQueue中取出msg交给handler执行。

本文就不详细讲解这些基本知识了,重点关注一下looper到底是怎么工作的?

我们知道一个普通线程如果想使用handler,官方给出了示例

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
              // process incoming messages here
            }
        };

        Looper.loop();
    }

那么就通过这几步,我们一点点来看looper到底是怎么回事。

创建looper

首先就是创建,执行Looper.prepare(),看一下源码:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}

可以看到创建了一个looper对象,并通过ThreadLocal进行存储。如果该线程已经创建了looper直接抛出异常。

为什么要使用ThreadLocal存储?

ThreadLocal的作用之一就是每个线程独立保存信息,方便该线程后续方法直接使用,类似全局变量,避免了各种传参。

目前我们还看不到它的作用,不要着急,讲到后面就明白了。

继续看Looper的构造函数:

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

可以看到创建了一个MessageQueue,同时持有当前线程。

所以一个线程只有一个looper和一个MessageQueue。

这里注意:Android主线程,即UI线程执行的是另外一个函数prepareMainLooper(),代码如下

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

可以看到有两处区别:

  • 增加了一个sMainLooper变量,方便在其他线程获取主线程的looper,即getMainLooper()
  • 执行prepare时参数是false,也就是不允许退出,所以主线程的looper没法手动退出。

创建handler

下面就是创建handler,这里直接使用了new Handler(),可以看到没有任何参数,那么这个handler如何与looper关联?

看一下构造函数:

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

可以看到直接调用了Looper的myLooper函数,这是个static函数,它是的代码很简单

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

这里就体现了ThreadLocal的作用,自动根据当前线程获取对应的looper,这样避免的传参,也更加安全(防止传错)。

这样handler就得到了Looper和MessageQueue的对象,就与looper关联上了。

启动looper

最后就是启动looper,执行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(); // 这里可能会阻塞
        if (msg == null) {
            // 注意,这里是线程结束。如果仅仅是MessageQueue为空,并不执行到这里,那种情况在queue.next()中,后面会讲。
            return;
        }
        ...
        try {
            //处理消息
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            ...
        } finally {
            ...
        }
        ...
    }
}

重点代码就是一个for循环,这里就是looper的关键。

可以看到一个for (;;)会一直循环,每次执行queue.next()获取消息,然后调用msg.target.dispatchMessage(msg);处理消息。这里msg的target就是handler对象,所以就执行到handler的dispatchMessage了。看一下这个代码:

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

可以看到如果msg有callback,先执行callback,这个callback就是Runnable。

当我们使用handler的post(Runnable)等函数,handler会自动将runnable对象包装成Message,并将runnable对象赋值给这个msg的callback。

如果没有callback则继续执行,最后会给handleMessage(msg)函数处理,这就是我们非常熟悉的了,我们创建handler会重写这个方法处理msg。

这么看looper好像很简单,但是这里要非常注意下面这段代码:

if (msg == null) {
    // 注意,这里是线程结束。如果仅仅是MessageQueue为空,并不执行到这里,那种情况在queue.next()中,后面会讲。
    return;
}

我也添加了注释,这里并不是如果队列中没有消息就退出,而且线程结束了(或手动关闭looper)时才会执行到这里跳出循环。

那么如果队列中没有消息是什么情况?这就涉及到looper的休眠问题了。

looper休眠问题 及 delay问题

上面看到loop()函数中是一个死循环,除非结束否则一直在循环,那样如果队列中没有消息,岂不是一直浪费资源么?

实际上looper是有休眠的,但是这个并不体现在looper的代码中,而是在MessageQueue的next()函数中。

先不着急看代码,因为这里还涉及另外一个问题:delay。

我们知道Handler可以send一个延时消息,那么这个延时是如何处理的呢?

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

可以看到会将当前时间+延长时间计算出消息的发送时间(为什么是发送时间不是执行,后面会讲解),我们知道handler其实还有postAtTime()函数,剩下的逻辑就是一样的了。

下面调用sendMessageAtTime,代码:

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    ...
    return enqueueMessage(queue, msg, uptimeMillis);
}

没什么,直接调用enqueueMessage:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
        msg.target = this;
        ...
    return queue.enqueueMessage(msg, uptimeMillis);
}

这里可以看到先将当前的handler赋值给msg的target,这就是上面提到的,所以每个message都持有handler对象,是一对一的关系。

然后调用了MessageQueue的enqueueMessage函数将msg入队。

boolean enqueueMessage(Message msg, long when) {
    ...
    synchronized (this) {
        ...
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

可以看到MessageQueue实际上是一个链表,每当加入一个msg时,会根据它的when插入到相应的位置,并不是每次都放到队尾。

最后执行了nativeWake(mPtr);记住这个函数,很重要,后会详细解释。

上面仅仅是得到了MessageQueue会根据时间排序的结论,但是如果looper取出一个msg发现还没到when的时间怎么办?

其实根据第3小节,我们应该可以知道,并不是looper来处理(因为loop()函数没有相关代码),那么就是MessageQueue的next()函数处理的。因为它是取出消息。代码:

Message next() {
    ...
    for (;;) {
        ...
        nativePollOnce(ptr, nextPollTimeoutMillis);

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

            // 如果线程关闭(退出looper)了才返回null
            if (mQuitting) {
                dispose();
                return null;
            }
            ...
        }
        ...
    }
}

可以看到这里面也有一个for (;;) {},也是一个循环。

先看if (now < msg.when) { } else {}这段代码,很明显将msg的when与当前时间进行比较,如果没到时间,则给nextPollTimeoutMillis设置为剩余的时间;否则返回当前msg(队列头部)。

没到时间设置nextPollTimeoutMillis之后呢?

因为是一个循环,所以开始下一轮,这样就执行到了nativePollOnce(ptr, nextPollTimeoutMillis);这里就是delay和休眠的关键。这个函数会阻塞当前线程一段时间nextPollTimeoutMillis,这样就释放了CPU资源,时间到后重新唤醒线程继续执行,所以讲解loop()函数时说next()会导致阻塞。

因为nativePollOnce(ptr, nextPollTimeoutMillis)的存在,delay得以实现,因为nextPollTimeoutMillis是剩余时间,所以唤醒时正好是当前msg的when。

那么如果队列中没有msg呢?

} else {
    // No more messages.
    nextPollTimeoutMillis = -1;
}

根据代码可以看到如果没有任何msg,那么nextPollTimeoutMillis = -1; 这时再执行nativePollOnce(ptr, nextPollTimeoutMillis);就会一直阻塞,没有超时时间,这样就不会一直占用CPU资源。

但是什么时候唤醒呢?

还记得上面讲解enqueueMessage提到的nativeWake(mPtr);么,这个就是唤醒线程的,所以当有新msg加入时就会唤醒继续执行,这样就保证了没msg时休眠,有msg时立刻执行。

这里还有另外需要注意的情况。即delay休眠过程中加入新消息的情况。

当我们队列头部的消息是一个延时msg,这时进入休眠。然后我们加入了一个新消息,是即时msg,那么它就会排在延时msg前面,需要立刻执行,但是现在正处于延时的休眠状态,这时候nativeWake(mPtr)就会立刻唤醒线程处理,这样就避免了因为delay导致即时消息无法立刻执行的问题。

MessageQueue就是通过这样的方式实现了delay,同时也实现了looper的休眠,防止持续占用CPU资源。

最后看下面的一段代码:

if (mQuitting) {
    dispose();
    return null;
}

只有当线程退出(或手动结束looper)时,才会返回null。根据loop()函数的代码可以知道,当next返回null,looper的循环就退出了,不会再处理任何消息了。

delay的到底是什么时间?

上面我们留了一个问题,delay通过计算当前时间+延长时间得到的是发送时间而不是执行时间,这又是什么意思?难道我们postDelay(runnable, 3000)并不是在3秒后执行?

实际上确实是这样,比如我们队列中有多个即时消息,这时我们发送一个即时消息,它的时间一定是now,但是因为前面有其他即时消息,所以轮到它执行的时候时间一定大于now了。

所以msg的when并不是执行时间,而是发送时间。

测试一下,有如下代码:

Handler handler = new Handler();
for(int i = 0; i < 10; i++){
    handler.post(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                Log.e("post", "run now");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}
Log.e("post", "send delay");
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            Log.e("post", "run delay");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, 3000);

循环向MessageQueue中放入10个耗时1秒的即时消息,然后放入一个延时3秒的消息,我们看一下执行的日志情况:

10-11 11:17:38.415 26225-26225/com.example.myapplication E/post: send delay
10-11 11:17:39.442 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:40.450 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:41.450 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:42.450 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:43.451 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:44.452 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:45.454 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:46.456 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:47.457 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:48.457 26225-26225/com.example.myapplication E/post: run now
10-11 11:17:49.740 26225-26225/com.example.myapplication E/post: run delay

可以看到在执行postDelay后,并不是3秒后执行了延时消息,而是前面10个即时消息都执行完才执行的。

而且也不是在第10个即时消息执行完等3秒执行,而且立刻就执行了延时消息。

其实这部分在上面MessageQueue的enqueueMessage函数代码中已经体现了。只是与我们平时的直观印象不一致,所以单独拿出来再说一下。

delay的是发送时间这个如何理解?

虽然在执行postDelay后消息是立刻放入MessageQueue的,但是与3秒后执行post的效果是一样的。 从入队角度来看,虽然先放入MessageQueue,但是如果后续的消息when小于它的时间,一样会排到前面。

从执行角度来看,如果它前面没有其他消息,此时未到时间,也不会执行它,会休眠到时间后再执行。

所以这与3秒后再发送的效果是一样,所以说delay的是发送时间,而真正的执行时间则不固定,会依赖更早的消息执行时间。

同步异步消息(同步屏障机制)

Handler机制中的消息其实是有分类的:同步消息和异步消息。

可以通过Message的isAsynchronous函数来获取是否是异步消息,如下:

public boolean isAsynchronous() {
    return (flags & FLAG_ASYNCHRONOUS) != 0;
}

可以通过setAsynchronous将消息设置为异步消息,如下:

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

那么为什么有这两种消息?它们的区别是什么?

通常我们使用的都是同步消息,而异步消息是由ViewRootImp发出的(requestLayout中),所有的UI绘制消息都是异步消息。也就说当要执行绘制(无论是否手动),都会发一个异步消息。

那么为什么UI绘制要使用异步消息?这是为了能优先处理这些消息,保证页面流畅。这部分逻辑也体现在MessageQueue的next()函数中,上面我们其实只解读这个函数的一部分代码,其实就是同步消息的处理过程,这次我们来看看被我们忽略的代码:

Message next() {
    ...
    for (;;) {
        ...
        nativePollOnce(ptr, nextPollTimeoutMillis);

        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) {
                // 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) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;   //注意这里,只是丢弃了一个消息(异步消息),但是mMessages没变
                    } 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;
            }
            ...
        }
        ...
    }
}

可以看到,在我们上面分析的代码前面还有一部分逻辑,单独列出来看看:

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

首先如果当前msg的target是null,那么就进入这个流程。但是我们知道,无论通过sendMessage还是postDelay(最终还是调用sendMessageXXX函数)方式,target都是handler对象,不可能为null。那么这里何时为null?

因为异步消息不是给开发者使用的,所以我们很少接触。上面也说过了它们的使用一般是在ViewRootImp的requestLayout中,最后会执行scheduleTraversals方法,如下:

void scheduleTraversals() {
        if (!mTraversalScheduled) {       
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }

首先看mHandler.getLooper().getQueue().postSyncBarrier(),这是MessageQueue的postSyncBarrier函数:

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

可以看到这里向消息队列加入了一个空消息,重点是这个msg的target没有赋值,是空的!

继续看scheduleTraversals函数的下一行,里面的细节就不一一细说了。实际上就是send了一个有关绘制的消息,但是这个消息通过setAsynchronous设置为了异步消息。

所以异步消息其实要与SyncBarrier搭配使用。

这个SyncBarrier就是同步屏障!Handler中的同步屏障机制就是通过它实现的。

回到最初的next函数,我们知道了target为null的时机了,当它为null的时候,进入了一个while循环,很简单就是直接找到下一个异步消息。

然后继续执行就进入我们上面分析过的流程,处理这个消息。

那么SyncBarrier和异步消息之间的消息怎么办?

我们来看下那段我们分析过的代码,其中有这样一段:

if (prevMsg != null) {
    prevMsg.next = msg.next;   //注意这里,只是丢弃了一个消息(异步消息),但是mMessages没变
} else {
    mMessages = msg.next;
}

这里prevMsg从上面可以看到,如果没有SyncBarrier和异步消息,它是null,如果有它就是异步消息的上一条消息。

所以,如果没有SyncBarrier和异步消息,mMessages正常指向下一条消息。而如果有的话,仅仅是将异步消息从链表上删除,mMessages并没变,所以后续还是从SyncBarrier后的消息开始执行。

所以消息并没有丢失,而是将异步消息优先执行,这样目的就是保证流畅性。

这里还有一个小问题,有异步消息的情况下mMessages并没变,其实还是指向SyncBarrier,这样下一次不是又进入查找异步消息的流程了么?

其实MessageQueue还有一个函数removeSyncBarrier,就是为了移除它的。至于何时调用,我们其实可以猜到,我也没有去源码里找,总之在执行下一次next前已经将这个SyncBarrier移除,mMessages指向了下一条消息,这样就可以正常执行消息了。

IdleHandler

Handler还有另外一个机制:IdleHandler。

IdleHandler的作用是只有在线程空闲的情况下才执行这些任务,即闲时机制。这个有点类似c#中协程的概念。

IdleHandler是一个回调接口,如下

public static interface IdleHandler {
    boolean queueIdle();
}

可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

通过代码可以看到,addIdleHandler其实就是将IdleHandler添加到一个集合mIdleHandlers中。

那么Looper是如何处理IdleHandler的?如何实现闲时机制?

其实实现代码还是在MessageQueue的next函数中,在这个函数的最后还有一部分代码我们之前没有分析,如下:

Message next() {
    ...
    for (;;) {
        ...
        synchronized (this) {
            ...
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    ...
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;
            }
            ...
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }
            ...
            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);
                }
            }
        }

        ...
        nextPollTimeoutMillis = 0;
    }
}

上面我们分析过,在next函数中,如果下一条消息当前可执行,则立刻return给looper执行;如果时间没到,或者没有消息了,则会通过nextPollTimeoutMillis进行休眠。

其实这并不准确,因为如果没有return的情况下,后续还有一个流程:IdleHandler。

通过上面的代码可以看到,如果没有return,说明目前没有要立刻执行的任务,所以线程是空闲的。这时候就可以处理IdleHandler。

首先判断pendingIdleHandlerCount,其实就是看是否有IdleHandler需要处理,如果没有直接continue,这样就进入了下次循环,进入了休眠,这就是我们之前分析的流程。

如果有IdleHandler需要处理,就向下执行,首先将mIdleHandlers转成mPendingIdleHandlers,然后遍历mPendingIdleHandlers依次执行idler.queueIdle()

这里注意queueIdle的返回值,如果是false则将这个IdleHandler从mIdleHandlers中移除,true则保留,这就是上面提到的。

最后很关键,将nextPollTimeoutMillis重新设置为0。因为这些IdleHandler执行不知道需要多少时间,很可能执行完已经过了下一个消息的时间,所以这里设为0使线程跳过休眠。在下个循环里,如果还没到下个消息的时候,那么可以重新计算时间休眠,如果到了时间直接执行。这样可以及时的执行消息,防止因为执行IdleHandler导致消息无法及时处理。

消息复用

Mesasge有复用机制,即obtain,代码如下:

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }

   
    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;
        return m;
    }
    
    ...

可以看到,除了无参函数,还有一系列有参函数,这些有参函数都是先执行无参函数得到一个复用的msg,然后重新设置部分属性而已。所以关注无参函数即可。

在obtain中可以看到,其实是从sPool即消息池中获取一个消息,并重置一下复用,如果池中没有则新建。而sPool实际上是一个链表。

那么sPool中的消息怎么来的?在Message中的回收函数recycleUnchecked中:

void recycleUnchecked() {
    ...
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

可以看到有一个消息池的限制MAX_POOL_SIZE(默认是50),如果未达到限制,则将其加到尾部;如果达到了则什么也不做。

所以在消息回收的时候,通过判断MAX_POOL_SIZE将其入列,而且消息池只保留50个(所有线程共有,所以需要加同步锁),且一直保持直到应用退出。

所以可以看到消息复用并没有太复杂的逻辑(不像线程池那样),仅仅保留50个留着复用即可。