Handler源码分析之Message类型

问:大家知道Handler机制中发送的Message分为几种吗?

答:分为 同步消息、屏障消息(消息屏障)、异步消息 这三种

同步消息

就是我们平时最常用的消息。

屏障消息

Message.target = null 的消息

理论依据:

MessageQueue.java
 //发送消息屏障
 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++;
            //从message的复用池中获得一个message
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            //mMessages 就是消息队列,单向链表结构
            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;
            }
            //可以看到将msg放入队列的过程中并没有给msg.target赋值即:msg.target = null
            return token;
        }
    }

  而同步消息入队列时:
  Handler.java
  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //将发送消息的handler对象赋值给了msg.target
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

那么为什么这么设计呢?因为对于屏障消息来说这个消息本身没有任何意义,发送这个消息不是用来跨线程通信的,它是不会像同步消息一样被分发的,它的作用是用来标识说我(此屏障消息)后面有异步消息需要尽快处理。所以它的使用场景是在发出异步消息前先发一个屏障消息,至于为什么要这样我们下面会分析。

异步消息

Message调用了setAsynchronous(true),即设置了Message.flag = FLAG_ASYNCHRONOUS的消息

那么问题来了,同步消息我们可以用来进行线程间通信,异步消息被设计出来是干什么的呢?

了解过android屏幕刷新机制的小伙伴会知道,屏幕的刷新频率一般是60Hz,即1秒中刷新60次约16.7ms刷新一次,以保证视觉上的连续性,人眼就不会感觉到卡顿。UI刷新也是通过handler机制来完成的。及系统会每隔16.7ms发送一个异步消息用于屏幕UI刷新。

空口无凭我们来看下源码:

研究过view的绘制流程的小伙伴知道,view刷新最终会调用到ViewRootImpl的scheduleTraversals():

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //这里先发送了一个屏障消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //继续进入这个方法
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
}



最终来到这个方法:
Choreographer.java
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].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                //这里发送了一个异步消息 (action就是上面传入的mTraversalRunnable)
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);//设置这个消息为异步消息
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

OK,到此三种类型消息的差异及用途我们就分析清楚了。

在此也需要提及一下的是,虽然构建三种类型的消息的细节上有一点差异,但三种类型的消息在进入队列的逻辑是一致的,都是根据比较时间来讲message插入到合适的位置,如下图:

Handler源码分析之Message类型_第1张图片

但在取消息时有一定的差异,如下图:

Handler源码分析之Message类型_第2张图片

队头是同步消息时,就是正常取出,比如将m1取出了,m2就成了队头,就这样取当取到m3时发现这个msg.target=null,就知道m3是一个屏障消息,取到它代表后面跟着一个急需处理的异步消息(UI刷新消息),那么就会一直往后找直到遍历到m5时发现它是异步消息,那就赶紧把m5先取出并分发它去执行UI刷新的逻辑(这里对应MessageQueue的next()方法中的逻辑),还记得上面mTraversalRunnable吗?它被赋值给了异步消息中obj这个成员变量随着Message一起被分发出去,当执行到这个Runnable任务时:

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //走到这里
            doTraversal();
        }
 }


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

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //这个方法非常眼熟,是开始View绘制三部曲(测量、布局、绘制)的入口
            performTraversals();

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

所以,当分发了异步消息后,在取下一个消息前,这个屏障消息就被移除了。

你可能感兴趣的:(android源码探索,java,开发语言)