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中。

相关推荐
ifanatic1 小时前
[面试]-golang基础面试题总结
面试·职场和发展·golang
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
长风清留扬4 小时前
一篇文章了解何为 “大数据治理“ 理论与实践
大数据·数据库·面试·数据治理
周三有雨15 小时前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
爱米的前端小笔记15 小时前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
好学近乎知o15 小时前
解决sql字符串
面试
我明天再来学Web渗透20 小时前
【SQL50】day 2
开发语言·数据结构·leetcode·面试
程序员奇奥21 小时前
京东面试题目分享
面试·职场和发展
理想不理想v1 天前
【经典】webpack和vite的区别?
java·前端·javascript·vue.js·面试
沈小农学编程1 天前
【LeetCode面试150】——202快乐数
c++·python·算法·leetcode·面试·职场和发展