Android Handler源码分析(二)

945 阅读8分钟

前言

上篇文章通过分析Handler源码,相信对Handler机制已经有了更进一步的了解。本篇文章将会通过源码来分析两个很少被提起的概念:消息同步屏障和IdelHandler

消息同步屏障

概念

我们平时在使用Handler时一般发送的有两种消息:同步消息、异步消息。从“消息同步屏障”字面意思我们可以看出,它主要影响的是同步消息。发送同步消息屏障的方法在MessageQueue中,我们通过源码来看一下“消息同步屏障”究竟是什么。

    /**
     * Posts a synchronization barrier to the Looper's message queue.
     *
     * Message processing occurs as usual until the message queue encounters the
     * synchronization barrier that has been posted.  When the barrier is encountered,
     * later synchronous messages in the queue are stalled (prevented from being executed)
     * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
     * the token that identifies the synchronization barrier.
     *....
     */
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;
        }
}

/**
* 移除消息同步屏障.
*/
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;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        //将这条消息在回收到message池中
        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); //唤醒消息队列
        }
    }
}

那么让我来翻译一下这两段注释(这里只是部分注释,有兴趣的小伙伴自行查看):

这个方法将会发送一个同步的屏障到Looper的MessageQueue中。
消息队列在遇到同步屏障消息之前都会正常运行。但是当遇到消息同步屏障时,队列中后面的同步消息将会被暂停(禁止被执行)直到执行 
removeSyncBarrier() 方法和确定消息同步屏障的token时,它将会被释放释放掉。

关于消息同步屏障的作用,在注释中已经说的很明白了。我们从上述源码中也可以看到:消息同步屏障其实就是在初始化 Message 对象时,将其target属性设置为 null,也就是说消息同步屏障是没有对应的Handler进行接收的。

源码分析

我们都知道,Looper会通过调用MessageQueue对象的 next()方法来轮询消息队列发送消息,那么让我们看一下这个方法。

Message next() { //省略了很多不必要代码
        ......
    for (;;) {
        ......
        synchronized (this) {
            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());
            }
        }
        ......
    }
}

从上面的源码中我们可以看出:消息同步屏障影响的是在它之后插入的同步消息,在轮询消息队列时跳过同步消息只处理异步消息。但是此时发送的同步消息还是存到了消息队列中,当消息队列被唤醒后,还是会发送这些同步消息。

用法

private void Handler() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case 1:
                                Log.i("MainActivity", msg.obj.toString());
                                break;
                        }
                    }
                };
                Looper.loop();
            }
        }).start();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.send_syn_message:
                Log.i("MainActivity", "点击了发送同步消息的按钮");
                sendSynMesage();
                break;
            case R.id.send_asy_message:
                Log.i("MainActivity", "点击了发送异步消息的按钮");
                sendSayMessage();
                break;
            case R.id.send_syn_barrier_message:
                Log.i("MainActivity", "点击了发送同步屏障消息的按钮");
                sendSynBarrierMessage();
                break;
            case R.id.remove_syn_barrier_message:
                Log.i("MainActivity", "点击了移除同步屏障消息的按钮");
                removeSynBarrierMessage();
                break;
        }
    }

    private void sendSynMesage() {
        Message message = Message.obtain();
        message.what = 1;
        message.obj = "发送同步消息";
        handler.sendMessageDelayed(message, 500);
    }

    private void sendSayMessage() {
        Message message = Message.obtain();
        message.what = 1;
        message.obj = "发送异步步消息";
        message.setAsynchronous(true);
        handler.sendMessageDelayed(message, 500);
    }

    //插入同步屏障消息
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void sendSynBarrierMessage() {
        try {
            MessageQueue queue = handler.getLooper().getQueue();
            Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
            method.setAccessible(true);
            token = (int) method.invoke(queue);
            Log.i("MainActivity", "发送同步屏障消息");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //移除同步屏障消息
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void removeSynBarrierMessage() {
        try {
            MessageQueue queue = handler.getLooper().getQueue();
            Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier", int.class);
            method.setAccessible(true);
            method.invoke(queue, token);
            Log.i("MainActivity", "移除同步屏障消息");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

界面很简单,四个按钮,分别对应发送的事件。我们看一下点击之后的效果。

注意看图中框出来的两个部分,点击发送同步屏障消息之前,同步消息和异步消息都能正常处理;在点击发送同步屏障之后,异步消息正常处理,同步屏障不被处理;在移除同步屏障之后,之前发送几次同步消息,都会被处理。

总结

当我们设置了同步屏障消息后,Handler将会跳过同步消息,只对异步消息进行处理。我们也可以理解成,同步屏障为Handler消息机制增添了一种简单的优先级机制,异步消息的优先级比同步消息高。

IdelHandler

概念

其实在接触IdelHandler之前我也不知道它具体是什么概念,但是我们可以通过源码中的注释看一下其定义和相关说明,这就要求小伙伴们有一定的英文能力。废话少说直接看源码。

/**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

又轮到我来翻(zhuang)译(b)了

当一个线程将要阻塞以等待更多消息时的接口回调
当MessageQueue中的消息被分发完毕并且当前等待更多消息时被调用。当queueIdele方法返回值为true时,IdelHandler将被激活;
当返回返回false时,IdelHandler将会被移除。当消息队列中仍然存在延时消息时,这个方法也有可能被调用,但是这些消息将在当前之间之后被发送。
总之,可以浓缩为依据话:当消息队列处于阻塞状态时将会调用IdelHandler的queueIdel方法。

源码分析

从上面可以看出,消息队列阻塞时会调用IdelHandlerqueue()方法,我们来看一下MessageQueue.next()方法。

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            
            //1、判断当前消pendingIdleHandlerCount是否小于零,因为其初始值为-1
            //2、判断当前消息队列中消息是否为空
            //3、将获取IdleHandler数量
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            //如果IdleHandler数量小于零,将会阻塞消息队列
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            //获取IdleHandler数量
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            //遍历pendingIdleHandlerCount集合
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // 将IdelHandler进行置空
                boolean keep = false;
                try {
                    keep = idler.queueIdle(); //调用queueIdle方法
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) { //如果queueIdel方法返回的是false,将IdleHandler进行移除
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            //将IdelHandler数量置零
            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;
        }
    }
}

从源码中我们可以看出,在消息分发时的执行过程与IdelHandler的注解说明一致。
除了执行流程我们看一下IdleHandler的添加和移除过程。

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在平时的开发中很少使用(或许是本人在日常开发中没有用过所产生的错觉,惭愧惭愧),让我们我们看一下系统源码中哪些地方用到了,

我们找一个我们比较熟悉的ActivityThread类,这个类是程序的入口,看一下其中是怎么使用的,有兴趣的小伙伴可以自己查阅源码。

void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true; //执行GC操作
            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);
    }

总结

IdleHandler会在消息队列为空进入阻塞时调用,这样我们就可以利用IdleHandler进行延时操作。
1、IdelHandler可以用于在Activity启动时延时执行一些不耗时的操作,从而起到优化加载速度。
2、源码中的使用,比如如上所示的ActivityThread中,强制开启GC操作。
3、Glide图片加载框架等,小伙伴们请自行查阅。

通过两篇文章分析了关于Handler机制,不知道各位看官感觉如何,如有任何不妥之处,还请大家及时提出。这里给大家准备了关于Handler机制面试题,希望对大家能有所帮助。
最后预祝大家国庆快乐。