Handler源码分析(同步屏障、异步消息和IdleHandler)

整个Handler源码分析分为两部分

Handler源码分析(基础流程)

Handler源码分析(同步屏障、异步消息和IdleHandler)

上一篇我们已经分析完了整体的基础流程,这一篇来分析一下同步屏障、异步消息以及IdleHandler。

消息分类

首先我们先来了解一下Message的种类。 Message 可以分为三种:普通消息(同步消息)、屏障消息和异步消息。

同步消息

我们平时默认使用的都是同步消息,即 Handler里的构造函数参数 async 默认为false。同步消息是按照 msg.when 来排的

异步消息

如果我们在创建Handler时 async 为 true,那么 这个handler发送的消息都是 异步消息。或者是在发送Message时,通过 msg.setAsynchronous(true) 来设置消息为异步消息。

异步消息需要配合屏障消息才有效果,否则跟同步消息一样

屏障消息(Barrier)

屏障消息是一种特殊的Message,它最大的特征是 target是null,且 arg1 属性被用作屏障的标识符来区别不同的屏障。 屏障的作用是用来拦截队列中的同步消息,放行异步消息。

只有屏障消息的target可以为null,如果我们自己设置 Message的target 为null,会抛出异常,在 MessageQueue 的 enqueueMessage 方法中我们可以看到 如果 msg.target为null 会抛出异常。

屏障消息的添加和删除

添加

kotlin 复制代码
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) {
        // 屏障消息的token,作为唯一标识,用于移除屏障消息
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        // 标记为正在使用,并设置 when 和 arg1
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        // 将屏障消息按时间排序插入到消息队列中
        Message prev = null;
        Message p = mMessages;
        // 找到两个相邻的消息,满足 prev.when < msg.when < p.when
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        // 将屏障消息插入到 prev 和 p之间
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

移除

kotlin 复制代码
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 和 target 来找到需要删除的屏障消息
            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;
            // 上面找到屏障消息的指针p后,把前一个消息指向屏障消息的后一个消息,这样就把屏障消息移除了
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            // 回收 p
            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);
            }
        }
    }

屏障消息的作用

让我们来看 MessageQueue 的next方法中

kotlin 复制代码
// 判断msg 是否是屏障消息
// 如果是屏障消息,找到屏障消息后边的异步消息,并执行,阻塞同步消息
if (msg != null && msg.target == null) {
    // Stalled by a barrier.  Find the next asynchronous message in the queue.
    do {
        prevMsg = msg;
        msg = msg.next;
        // 这里的isAsynchronous方法就是前面设置进msg的async参数,通过它判断如果是异步消息,则跳出循环,把该异步消息返回
        // 否则是同步消息。
    } while (msg != null && !msg.isAsynchronous());
}

从上面的代码我们可以知道,如果当前消息是屏障消息,那么后续会阻塞同步消息,优先执行异步消息。

实际应用

ViewRootImpl.java

kotlin 复制代码
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 发送屏障消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 在这个地方会有发送异步消息
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除屏障消息
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

这是View UI界面更新的逻辑,通过同步屏障+异步消息可以保证消息被优先处理,保证界面的迅速更新。

IdleHandler

IdleHandler 实际上是一个接口,在消息循环空闲的时候(没有消息)进行的回调方法。接口方法的返回值代表是否需要移除当前 IdleHandler。

kotlin 复制代码
public static interface IdleHandler {
    boolean queueIdle();
}

添加/移除 IdleHandler

kotlin 复制代码
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<>();

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

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

执行 IdleHandler

kotlin 复制代码
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
    int nextPollTimeoutMillis = 0;
    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;
            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 = (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.
            // pendingIdleHandlerCount < 0是说for循环只执行第一次
            // mMessages == null || now < mMessages.when) 是说当前消息队列没有消息或者要执行的消息晚于当前时间
            // 说明现在消息队列处于空闲。
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                // 获取 IdleHandler的个数
                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 {
                // 执行 IdleHandler
                // 如果queueIdle返回true,则该空闲消息不会被自动删除,在下次执行next的时候,如果还出现队列空闲,会再次执行。
                // 如果返回false,则该空闲消息会在执行完后,被自动删除掉。
                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.
        // 这里把空闲消息标志置为0,而不置为-1,就是说本次已经处理完,防止for循环反复执行,影响其他消息的执行
        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;
    }
}

从本质上来说,IdleHandler 的作用就是趁着消息队列空闲的时候干点事情,具体干啥,就要看 IdleHandler 的 queueIdle() 方法了。

实际应用

在ActivityThread中有一个GCIdleHandler,用于做GC的

kotlin 复制代码
class H extends Handler {
    ......
    public static final int GC_WHEN_IDLE = 120;
    ......

    public void handleMessage(Message msg) {
        switch (msg.what) {
            ......
            case GC_WHEN_IDLE:
                scheduleGcIdler();
                break;
            ......
        }
    }
}

当收到 GC_WHEN_IDLE 消息后,就会触发 scheduleGcIdler()

kotlin 复制代码
void scheduleGcIdler() {
    if (!mGcIdlerScheduled) {
        mGcIdlerScheduled = true;
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

void unscheduleGcIdler() {
    if (mGcIdlerScheduled) {
        mGcIdlerScheduled = false;
        Looper.myQueue().removeIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}


final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        purgePendingResources();
        return false;
    }
}

void doGcIfNeeded() {
    doGcIfNeeded("bg");
}

void doGcIfNeeded(String reason) {
    mGcIdlerScheduled = false;
    final long now = SystemClock.uptimeMillis();
    //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
    //        + "m now=" + now);
    if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
        //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
        BinderInternal.forceGc(reason);
    }
}

从上边的代码我们可以看出 线程空闲时会调用 mGCIdler,最终调用BinderInternal.forceGc(reason); 触发 GC,最小间隔是5秒。需要注意的是,合格 queueIdle 返回的false,所以会长期存在于主线程的 MessageQueue中。

相关推荐
熊猫钓鱼>_>15 分钟前
Java面向对象核心面试技术考点深度解析
java·开发语言·面试·面向对象··class·oop
大叔_爱编程2 小时前
基于Python的历届奥运会数据可视化分析系统-django+spider
python·django·毕业设计·源码·课程设计·spider·奥运会数据可视化
进击的野人2 小时前
CSS选择器与层叠机制
css·面试
T___T5 小时前
全方位解释 JavaScript 执行机制(从底层到实战)
前端·面试
9号达人5 小时前
普通公司对账系统的现实困境与解决方案
java·后端·面试
勤劳打代码5 小时前
条分缕析 —— 通过 Demo 深入浅出 Provider 原理
flutter·面试·dart
努力学算法的蒟蒻6 小时前
day10(11.7)——leetcode面试经典150
面试
进击的野人6 小时前
JavaScript 中的数组映射方法与面向对象特性深度解析
javascript·面试
南山安6 小时前
以腾讯面试题深度剖析JavaScript:从数组map方法到面向对象本质
javascript·面试
橘颂TA8 小时前
【剑斩OFFER】算法的暴力美学——二分查找
算法·leetcode·面试·职场和发展·c/c++