前言
Handler消息机制筑厦之基石!虽老生常谈,但每次学习都会有新的收获~
基本概念
1、Handler
Handler,主要用于发送和处理消息。
(1)发送:
通过一系列
sendXXX和postXXX方法来发送消息,最终通过enqueueMessage方法将Message对象发送到MessageQueue消息队列中去,同时将自己的引用赋值给 Message的target。
(2)处理:
通过实现
Handler.Callback接口或重写handleMessage方法处理消息。
Handler有多种重载的构造方法。
2、Message
消息,数据载体承载数据。可以使用what、arg1、arg2字段携带整型数据。obj字段携带Object对象。
Message维护了一个消息对象池,可以复用消息,避免创建太多消息对象占用过多内存,导致卡顿。
3、MessageQueue
消息队列,它是一个链表结构(单向链表),用以存放handler发送的消息。每个线程只有一个MessageQueue。
入队方法:
enqueueMessage()
出队方法:
next()
4、Looper
消息循环器,消息机制的灵魂,用以不断调度消息对象并且分发给handler处理。Looper是和线程绑定的,不同线程的Looper不一样,通过ThreadLocal实现线程隔离。
1.取出消息:loop()循环,queue.next()将 Message 对象从 MessageQueue 中取出来。
2.派发消息:将从消息队列取出来的消息通过msg.target.dispatchMessage(msg)方法派发给对应Handler,通过handleMessage处理消息。
主流程
1、发送消息
可以使用sendMessage(以及一系列 sendXXX的消息发送方法)和post方法发送即时同步消息,
或通过sendXXXDelayed和postDelayed发送延迟同步消息。
最终都会调用到sendMessageAtTime(@NonNull Message msg, long uptimeMillis)方法,然后调用enqueueMessage方法。
/** Handler#enqueueMessage */
// 该方法主要有3个作用,注释中的①②③分别说明了。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
msg.target = this;// ①设置处理该消息的handler对象
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// ②设置消息类型,同步或异步
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// ③交由消息队列的入队方法
return queue.enqueueMessage(msg, uptimeMillis);
}
2、消息入队
消息入队最终是靠消息队列的enqueueMessage方法完成,其代码如下:
/**MessageQueue#enqueueMessage*/
// 注释中的①②③④⑤分别说明了
boolean enqueueMessage(Message msg, long when) {
// ①消息对象必须指定`target`,也就是处理消息的handler对象;而且message对象的`flag`为`FLAG_IN_USE`。否则将抛出异常。
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;
}
// ②设置消息对象标志`FLAG_IN_USE`和时间,创建唤醒字段,用于标记是否需要唤醒消息队列。
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// ③如果当前消息队列没有消息或要入队的消息`when`值小于对列头消息`when`值,则将新消息插入到链表头部。设置`needWeak`,它又由`mBlocked`变量决定,`mBlocked`的设置是在`next()`方法中,简单来说消息队列仅有延时消息或空队列时,`mBlocked`为`true`。
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 {
// ④不满足③的情况下,从消息链表头开始遍历,将新消息插入到第一个when值大于新消息when值的消息节点前方。
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;
}
// ⑤是否需要唤醒,需要则调用native方法唤醒。
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
总之,入队方法就是让所有消息根据when的大小有序排列,when越小则越位于消息链表头部。
3、消息出队
Looper的loop()在一个死循环中不断获取消息,获取到消息就分发给handler处理,获取消息通过MessageQueue#next()方法,这个方法逻辑较多且都比较重要,下面会详细说明。
/**MessageQueue#next*/
// 注释①②③④⑤⑥⑦分别做了说明
Message next() {
......
// ①`pendingIdleHandlerCount`表示IdleHandler的数量。
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// ②`nextPollTimeoutMillis`表示消息队列休眠的时间,是个阻塞方法。 -1表示无限阻塞,0表示不阻塞。
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// ③实现阻塞的native方法,可通过`nativeWake`方法唤醒。
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;
// ④针对**同步屏障机制的处理**,前文已经说了普通消息在入队前一定会设置`target`属性,唯独有种方式不会,即`postSyncBarrier`方法发出的同步屏障消息是不会设置`target`属性的,同步屏障相关内容后面会详细介绍,这里只要了解普通的同步消息不会走到这步即可。
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());
}
// ⑤对于同步消息,从此步开始真正去获取消息对象。首先明确下代码里的几个对象含义:`mMessage`始终表示消息链表头部,`p`表示当前节点,`prevMsg`表示`p`节点的前一个节点。对于即时消息,设置`mBlocked=false`,表示不阻塞。同步消息的`prevMsg`始终为null,所以从头结点开始遍历,获取当前节点并返回。对于延时消息,计算延时时间,然后走到⑥,若`now < mMessages.when`表示还没到延时消息执行时间,然后会走到`if(pendingIdleHandlerCount <= 0)`中,设置`mBlocked=true`,然后开始下次循环,又走到③处,`nextPollTimeoutMillis`不等于0,于是阻塞。
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;
}
// 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.
// ⑥用于计算`IdleHandler`个数,初始化`IdleHandler`数组。`IdleHandler`是用于在消息队列空闲时处理一些任务,适用于一些不紧急非高优的任务,后面也会详细介绍。
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);
}
}
}
// ⑦重置`pendingIdleHandlerCount`和`nextPollTimeoutMillis`
// 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;
}
}
4、消息分发
上面说了Looper的loop方法不断获取消息并分发,分发的关键代码就是:
/**Looper#loop*/
// 注释①②③④分别做了说明
public static void loop() {
......
for (;;) {
// ①获取Looper对象,如果为空的话抛出异常。
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
// ②可以通过`Looper#setMessageLogging`方法设置打印器,用来输出一些开发者需要的信息,通常在性能监控上需要获取这些信息来评估优化效果。
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
......
try {
// ③分发消息给handler处理,`target`就是在消息入队时设置的handler对象。
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
// ④回收消息对象
msg.recycleUnchecked();
}
}
步骤③将消息分发给了对应handler,看下dispatchMessage方法的实现:
/**Handler#dispatchMessage*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {// ①如果消息设置了callback,就执行 handleCallback(msg)方法,这个callback其实就是一个Runnable。handleCallback(msg)其实就是调用这个Runnable的run方法。
handleCallback(msg);
} else {
if (mCallback != null) {// ②如果设置了全局的Callback,就调用该Callback的handleMessage(msg)去处理消息
if (mCallback.handleMessage(msg)) {
return;
}
}
// ③这个是创建Handler时我们重写的handleMessage方法
handleMessage(msg);
}
}
代码很清晰,首先如果设置msg.callback,就调用handleCallback方法。那么msg.callback在哪里设置的呢?找到赋值的地方,发现post和postDelayed方法:
/**Handler#post*/
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
/**Handler#postDelayed*/
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
传入了getPostMessage方法,继续看该方法:
/**Handler#getPostMessage*/
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;// 正是这里设置了msg.callback
return m;
}
现在清晰了,正是这里设置了msg.callback,并且值就是post的参数,一个runnable对象。
看下handleCallback代码:
/**Handler#handleCallback*/
private static void handleCallback(Message message) {
message.callback.run();
}
其实就是执行了post传入的runnable参数的run方法。
如果不是通过post方式发送的消息,就会走到else逻辑里。首先判断是否实现了Handler.Callback接口,可在handler的构造函数传入,设置了则调用Handler.Callback接口的handleMessage方法。
否则调用Handler的handleMessage方法,它是一个空方法,需要开发者重写来实现业务逻辑。
总结:消息的分发执行顺序就是post#run方法 -> Handler.Callback.handlerMessage方法 -> Handler#handlerMessage方法
同步屏障
消息分类
1、普通消息(同步消息)
平时通过sendXXX、postXXX发送的消息都是同步消息,即普通消息。
2、屏障消息(同步屏障)
屏障消息就是在消息队列中插入一个屏障。
在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。
屏障消息(同步屏障)有以下特征:
①没有给target赋值,即不用handler分发处理,后续也会根据target是否为空来判断消息是否为消息屏障。
②消息队列中可以插入多个消息屏障。
③消息屏障也是可以有时间戳的,插入的时候也是按时间排序的,它只会影响它后面的消息,前面的不受影响
④消息队列中插入消息屏障,是不会唤醒线程的(插入同步或异步消息会唤醒消息队列)。
⑤插入消息屏障后,会返回一个token,是消息屏障的序列号,后续撤除消息屏障时,需要根据token查找对 应的消息屏障。
⑥发送屏障消息的API被隐藏,需要通过反射调用postSyncBarrier方法。
3、异步消息
通过setAsynchronous(true)设置的消息。
Android系统中的异步消息就是专门解决消息处理延迟的问题,它需要配合同步屏障(SyncBarrier)一起工作,在发送异步消息的时候向消息队列插入同步屏障对象,消息队列会返回同步屏障的token,此时消息队列中的同步消息都会被暂停处理,优先执行异步消息处理,等异步消息处理完成再通过消息队列移除token对应的同步屏障,消息队列继续之前暂停的同步消息处理。
发送屏障消息
屏障消息(同步屏障)是通过MessageQueue的postSyncBarrier()方法插入到消息队列的。
/**MessageQueue#postSyncBarrier*/
/**
* @hide
*/
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++;
// ①屏障消息和普通消息的区别是屏障消息没有tartget。
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;
}
}
屏障消息初始化时并没有给target赋值,因此target==null的来源就找到了。这样就插入了一条target==null的消息,这个消息就是一个同步屏障。
屏障消息的工作原理
通过postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过的呢?我们知道MessageQueue是通过next方法来获取消息的。
/**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;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
//2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
//3.如果nextPollTimeoutMillis>0,最长阻塞nextPo TimeoutMi is毫秒(超时)
int nextPollTimeoutMillis = 0;
// next()也是一个无限循环
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// ①如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
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; // 当前链表的头结点
// ②关键:如果target==null,那么它就是屏障
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,
//表示nativePo Once方法要等待nextPollTimeoutMillis时长后返回
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {// 异步消息到了处理时间,就从链表移除,返回它。
// Got a message.
mBlocked = false;//获取到消息
// 链表操作,获取msg并且删除该节点
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;// 如果没有异步消息就一直休眠,等待被唤醒。
}
...
}
...
}
}
可以看到,在注释②如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if (msg != null){}中的代码。可以看到通过这种方式就挡住了所有的普通消息。
移除屏障消息
同步屏障的移除是在MessageQueue类的removeSyncBarrier()方法里。
/**MessageQueue#removeSyncBarrier*/
// 通过插入同步屏障时返回的token 来移除屏障
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
//找到token对应的同步屏障
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
// 从消息链表中移除
if (prev != null) {
prev.next = p.next; // next指向下一条消息
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked(); // 回收同步屏障消息
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);// 唤醒消息队列
}
}
}
发送异步消息
1、通过Handler
/**
* @hide
*/
public Handler(boolean async) {}
/**
* @hide
*/
public Handler(Callback callback, boolean async) { }
/**
* @hide
*/
public Handler(Looper looper, Callback callback, boolean async) {}
Handler有几个构造方法,可以传入async标志为true,这样构造的Handler发送的消息就是异步消息。不过可以看到,这些构造函数都是hide隐藏的不可直接调用,但我们可以通过反射方式达到我们的目的。
2、通过Message
Message message=Message.obtain();
message.setAsynchronous(true);
handler.sendMessage(message);
设置Message的setAsynchronous()为true。
同步屏障的应用
1、ViewRootImpl接收屏幕垂直同步信息事件用于驱动UI测绘
Android系统中UI更新相关的消息即为异步消息,需要优先处理。每16ms左右刷新一次UI,即1s刷新60次。Android4.1之后增加了Choreographer机制,用于同Vsync机制配合,统一动画、输入和绘制时机。
ViewRootImpl的requestLayout开启绘制流程:
public final class ViewRootImpl implements ViewParent, … {
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//检查是否在主线程
mLayoutRequested = true;
scheduleTraversals(); //重要函数
}
}
}
应用框架中为了更快的响应UI刷新事件,在ViewRootImpl.scheduleTraversals中使用了同步屏障:
/**ViewRootImpl#scheduleTraversals*/
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //设置同步障碍,确保下面发送的mTraversalRunnable优先被执行
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //内部通过Handler发送了一个异步消息(前面设置了同步屏障,所以这里发的异步消息会优先执行)
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mChoreographer.postCallback会执行到Choreographer类中:
public final class Choreographer {
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) {
postCallbackDelayedInternal( callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addC allbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else { // 发送异步消息
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//异步消息
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
}
mTraversalRunnable调用了performTraversals执行measure、layout、draw绘制UI。为了让mTraversalRunnable尽快被执行,在发消息之前调用MessageQueue.postSyncBarrier设置了同步屏障。
scheduleTraversals方法开启了同步屏障,并且发送了异步消息,由于UI相关的消息是优先级最高的,这样系统就会优先处理这些异步消息。
最后,要移除同步屏障的时候,会调用ViewRootImpl的unscheduleTraversals方法:
/**ViewRootImpl#unscheduleTraversals*/
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
在ViewRootImpl中的doTraversal()方法中会移除同步屏障,这里移除是因为requestLayout或者invalidate的时候,刷新之后,在doTraversal()中就会移除同步屏障,因为此时消息已经发送并且处理了。
总结一下,ViewRootImpl在处理UI事件之前,先发送一个屏障消息,告诉handler优先处理异步消息,然后Choreographer发送异步消息,异步消息处理完以后,然后再发送一个移除异步屏障的消息。Handler就通过这种机制保障了UI界面的流畅刷新。
- ActivityThread接收AMS的事件驱动生命周期
- InputMethodManager分发软键盘输入事件
- PhoneWindowManager分发电话页面各种事件
IdleHandler
IdleHandler提供了一种在消息队列空闲时执行的某些操作的手段,适用于执行一些不重要且低优先级的任务。它的使用也很简单调用MessageQueue#addIdleHandler方法将任务添加到消息队列,然后队列空闲时会自动执行,可通过removeIdleHandler方法或自动回收。
消息队列通过一个ArrayList来储存添加的IdleHandler任务。
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 临时储存IdleHandler任务
private IdleHandler[] mPendingIdleHandlers;
调用IdleHandler任务的位置在MessageQueue#next()方法中,无关代码已省略:
Message next() {
// ①每次Looper调用`next`方法时,先将IdleHandler临时数组的大小`pendingIdleHandlerCount`重置为 -1。
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
......
synchronized (this) {
// 省略部分为获取消息对象的过程
.....
// 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.
// ②首次运行时`pendingIdleHandlerCount < 0`肯定成立,如果当前消息队列为空或者只有延时消息时,认为此时队列空闲可以执行IdleHandler任务了。令`pendingIdleHandlerCount`为已添加的IdleHandler任务个数。
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;
}
// ③`mPendingIdleHandlers`是一个数组,首次执行时可定为空,所以初始化数组,数组大小最小为4。并且将`mIdleHandlers`列表中的任务复制到这个临时数组。
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.
// ④循环临时数组执行IdleHandler任务,任务从`mPendingIdleHandlers`数组中取出后,会置空,释放对handler对象的引用。然后调用`queueIdle()`真正执行IdleHandler任务。`queueIdle()`是一个接口方法,需要自己实现业务逻辑。另外它的返回值决定是否要自动删除该IdleHandler任务,返回true该任务执行后将不会被删除。
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);
}
}
}
// ⑤重置`mPendingIdleHandlers = 0`,开启下次循环。
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;
}
}
消息对象池
消息是数据的载体,我们知道创建消息对象是一般不提倡去new一个对象,而是调用Message的一系列obtain的重载方法,原因就是因为可以复用已创建的Message对象,避免创建过多对象占据大量内存。既然是复用,那么一定存在某种数据结构去保存对象,这就是消息对象池,使用的是链表结构。
1、创建Message对象
消息对象池有几个重要属性,分别是:
// 同步对象
public static final Object sPoolSync = new Object();
// 链表头节点
private static Message sPool;
// 消息池大小(链表长度,表示消息个数)
private static int sPoolSize = 0;
// 消息池最大容量
private static final int MAX_POOL_SIZE = 50;
看下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();
}
代码很简洁,获取消息对象是同步操作,从头节点开始,如果头结点不为空,取得头结点。然后指针后移,并且消息池大小减1。否则的才通过new方式创建新对象。
2、回收Message对象
可以调用recycle方法回收消息对象。
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");
}
return;
}
recycleUnchecked();
}
如果消息标记了FLAG_IN_USE标志,不可回收。然后真正回收的方法是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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
可见回收消息,首先就是将其成员变量全部重置为初始值,然后在消息池大小不超过限制容量时,让将要被回收节点的next指向头结点,再把头指针移到当前节点,容量加1。