Handler机制

116 阅读19分钟

前言

Handler消息机制筑厦之基石!虽老生常谈,但每次学习都会有新的收获~

基本概念

1、Handler

Handler,主要用于发送处理消息。

(1)发送:

通过一系列sendXXXpostXXX方法来发送消息,最终通过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在哪里设置的呢?找到赋值的地方,发现postpostDelayed方法:

/**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方法。

否则调用HandlerhandleMessage方法,它是一个空方法,需要开发者重写来实现业务逻辑。

总结:消息的分发执行顺序就是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。