全面剖析Android消息机制源码


在Android应用中,消息机制可谓是处于举足轻重的地步,因为UI是Android的整个门面展示,而UI的展示是交由消息机制来处理。Android不允许在子线程中进行UI处理,因为这样会引发多线程的安全问题,而解决这个问题则需要做加锁等操作,这样会导致效率低下,造成UI不流畅等问题,这是万万不可接受的。

说到Android消息机制的用途,你可能会想到子线程和主线程的通信、延迟发送一个消息或执行一个Runnable等,但你有没有想过,它是如何实现子线程和主线程的通信?子线程和子线程之间是否能通过消息机制来进行通信?延迟发送或执行的内部原理又是如何实现的?另外你可能听过这样的问题——主线程在Looper.loop()中开启了一个死循环,为什么不会造成ANR(Application Not Responding)?从MessageQueue中取出消息时可能会阻塞,为什么该阻塞也不会造成ANR?这些问题归根结底就是原理问题,在看完本篇文章后都会茅塞顿开,so follow me!

Android消息机制的简单图解

image

消息的发送到处理可以大致分为5个步骤,分别是初始化准备工作发送消息消息入队Looper循环和消息出队,以及消息处理,我们一步一步来看。

1. 初始化准备工作

平时我们在使用Handler发送消息时,只需要创建一个Handler对象,然后调用相应的发送方法即可,使用起来特别简单。但其实在创建Handler对象之前,主线程已经做了一些准备工作,其中就有MessageQueueLooper的创建初始化,并且将它们存放在主线程的私有内存中。接下来从源码中分析,首先来看Handler的构造方法:

1.1 Handler中的初始化工作

// 构造方法1
public Handler() {
    this(null, false);
}

// 构造方法2
public Handler(Callback callback) {
    this(callback, false);
}

// 构造方法3
public Handler(Callback callback, boolean async) {
    ...省略部分代码
   
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

// 构造方法4
public Handler(Looper looper) {
    this(looper, null, false);
}

// 构造方法5
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

// 构造方法6
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

有6个构造方法,我们先主要看构造方法1构造方法3,其余构造方法会在后面讲解。其中,构造方法1调用了构造方法3,然后在构造方法3中,注意mLooper = Looper.myLooper()这行代码,获取了一个Looper对象,然后接下来就对该Looper对象进行了null判断,如果为null则抛出RunTime异常

Can't create handler inside thread xxx that has not called Looper.prepare()
因为没有调用Looper.preapre()方法,所以在xxx这个线程中不能创建Handler对象

你会想哎这不对啊?我平时创建Handler时也没遇到过啊。其实前面说过了,主线程早已帮我们做了这些初始化的准备工作了,具体的代码需要去Looper类里看看。

1.2 Looper的初始化工作

首先看下Looper类的构造方法

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Looper的构造方法里,创建了一个MessageQueue对象,获取了当前的Thread对象。但该构造方法是私有的,如何创建Looper对象呢?其实在上一小结中的Runtime异常中已经告诉了答案,即调用Looper.prepare()方法:

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

其中prepare()方法调用了prepare(boolean quitAllowed)方法,而该方法里也只有3行代码。首先判断当前线程是否已经创建了Looper对象,如果是则抛异常:

Only one Looper may be created per thread

否则创建一个,并且将其存放到当前线程的私有内存中。如果你对ThreadLocal不太熟悉且想进一步了解的话,可以阅读 Java之ThreadLocal详解 这篇文章。

prepare()方法的作用就是在当前线程中创建一个Looper对象,并且创建关联一个MessageQueue对象,然后通过ThreadLocal将这个关联了MessageQueue对象的Looper对象存放到当前线程的私有内存中,请记住,这是实现线程间通信的根本。文章后面会将这块同整个消息机制串联起来,届时就会很清楚地理解了整个消息机制逻辑。

另外,主线程的初始化Looper对象的方法如下,基本上和prepare()方法大同小异:

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

该方法是在ActivityThread类中的main()方法中调用,这是应用的入口方法,启动时便会调用。所以说主线程的消息发送不需要手动调用Looper.prepare()方法,因为主线程早就做了这些准备工作。

// ActivityThread类,此方法为应用程序的入口方法
public static void main(String[] args) {
    ...省略部分代码
    // 创建初始化Looper
    Looper.prepareMainLooper();

    ...省略部分代码

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    ...省略部分代码
    // 开启消息循环
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

注意到该方法中倒数第二行调用了Looper.loop()方法,它是一个死循环,会一直调用消息队列MessageQueuenext()方法获取Message,然后交由Handler处理。此处先知道其作用即可,后面第4章节会详细介绍Looper.loop()方法。

1.3 消息机制的初始化准备工作小结

现在我们来整理下,一个完整的消息机制的初始化准备工作基本上有以下3个步骤:

  1. 调用Looper.prepare()方法,创建一个关联了MessageQueueLooper对象,并通过ThreadLocal将其存放在当前线程的私有内存中,这是保证多线程间通信的根本;
  2. 创建一个Handler对象;
  3. 调用Looper.loop()方法,开启死循环从MessageQueue中获取消息,该方法的调用时机也可以放在步骤2之前。

以上便是消息机制的初始化准备工作,接下来便可以进行发送消息的操作了。

2. 发送消息

初始化准备过程已经完成了,接下来就可以发送消息。在发送一条消息时,我们可以调用Handler的以下send方法来实现:

2.1 发送消息源码分析

// 发送方法1.发送一条消息
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

// 发送方法2.发送一条延迟处理的消息
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

// 发送方法3.发送一条空消息
public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

// 发送方法4.发送一条延迟处理的空消息
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

也可以调用post方法来投递一个Runnable,但其本质上也是发送了一条消息:

// 发送方法5.投递一个Runnable
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

// 发送方法6.投递一个延迟处理的Runnable
public final boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

// 将Runnable转为Message
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

方法5方法6虽然是投递一个Runnable,但实质上是通过getPostMessage(Runnable r)方法,将Runnable封装到了Messagecallback变量中,最终也是发送了一个Message

上面6种发送消息的方法,其中

方法1内部调用了方法2
方法3调用了方法4,而方法4内部也调用了方法2
方法5方法6内部也是调用了方法2

可以看到send方法或post方法最终都指向了方法2,那么接下来就分析方法2——sendMessageDelayed(Message msg, long delayMillis):

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

// 消息入队
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // MessageQueue的消息入队
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到,sendMessageDelayed(Message msg, long delayMillis)方法内部调用了sendMessageAtTime(Message msg, long uptimeMillis)方法,其中参数uptimeMillis是一个时间参考,用来表示什么时候该Message会被执行。

一条延迟处理的消息,其对应的执行时间uptimeMillis等于开机运行时间SystemClock.uptimeMillis()加上延迟执行的时间delayMillis(非延迟消息的delayMillis值为0),最终调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法。

接下来在enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法中,会将当前Handler对象封装至Messagetarget变量,注意此处,后面在第五章节消息处理时会再回顾这行代码。最后调用MessageQueueenqueueMessage(Message msg, long when)方法中,进行消息入队操作。至此,Handler中的消息发送过程已经完成了。

2.2 发送消息过程小结

发送消息的过程还是比较简单的,简单整理如下:

  1. 通过post系列方法或send系列方法发送一个消息Message
  2. 如果是延迟消息,则该消息的执行时间=开机运行时间+延迟执行时间,否则执行时间=开机运行时间
  3. 最后将当前Handler对象封装至Messagetarget中,再调用MessageQueueenqueueMessage(Message msg, long when)方法进行入队操作。

3. 消息入队

消息发送完毕,接下来就是消息入队操作,对应的代码是MessageQueueenqueueMessage()方法:

3.1 消息入队源码分析

boolean enqueueMessage(Message msg, long when) {
    ...省略部分代码

    synchronized (this) {
        ...省略部分代码

        msg.markInUse();
        msg.when = when;
        // 获取Message队列的头部
        // 注意:此队列实质上是一个单向链表,目的是为了更方便地插入和移除消息
        Message p = mMessages;
        boolean needWake;
        // 满足以下3个条件之一,便会将当前Message设为队列头:
        // 1.队列头为空,即该队列为空;
        // 2.when为0,该值可以手动赋值,一般我们用不到;
        // 3.当前要入队的消息执行的时间早于队列头
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 一个新的队列头,如果当前队列阻塞则唤醒,mBocked为true表示队列是阻塞状态
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            // 一般来说不需要唤醒队列的阻塞状态,除非队列头是一个同步屏障(barrier),且当前的Message是异步的,则根据阻塞状态决定是否需要唤醒队列
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            // 该循环的目的是按照when从小到大的顺序,找到Message的位置
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        // mPtr是native层的MessageQueue的引用地址,是在MessageQueue的构造方法里初始化的
        // 这样便可以将native层和java层的对象关联起来
        // 如果needWake=true,则通过nativeWake(mPtr)方法唤醒阻塞中的队列,唤醒之后的操作,将在下节消息出队中讲解
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

消息入队的操作还是相对来说比较简单的,即:

如果当前消息队列为空,或插入的Message执行时间when早于队列头的Message,则将其置为消息队列首部,并且将队列从阻塞状态中唤醒;
否则按照Message的执行时间排序,将该Message插入到队列中。

注意到needWake = mBlocked && p.target == null && msg.isAsynchronous()这行代码,涉及到消息机制的同步屏障,这里简单讲解一下。

3.2 同步屏障(Sync Barrier)

在UI线程中,其主要目的就是保证及时有效地刷新UI。假设现在需要刷新UI,但主线程的消息队列中还存在其它的消息,那么就需要保证优先执行UI刷新的消息,屏蔽其它非UI相关的,同步屏障就起到了这样的作用。

3.2.1 源码分析

一般来说我们发送消息时,最终会在HandlerenqueueMessage()方法中将当前Handler对象封装至Messagetarget中,但同步屏障消息是没有Handler的,可以调用MessageQueuepostSyncBarrier()来发送一个消息屏障:

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;
    }
}

可以看到内部并没有设置给Message设置Handler,而且依旧是按照消息的执行时间when来排序插入到队列中。移除同步屏障调用MessageQueueremoveSyncBarrier(int token)方法即可,其内部源码就不贴出来了,感兴趣可自行查看。

3.2.1 同步屏障和同步、异步消息

一般我们发送的消息是同步(synchronous)的,有两种方式可以设置发送异步消息:

  • 一是通过Handler构造方法3构造方法6,将构造参数async设为true即可。通过这种方式,发送的所有消息都是异步的。
  • 另一种是调用MessagesetAsynchronous(boolean async)方法设置为true。通过这种方式,当前发送的消息是异步的。

同步屏障的作用就是屏蔽消息队列中该同步屏障之后的所有同步消息,只处理异步消息,保证异步消息优先执行,其具体代码逻辑见4.2 消息出队

3.2.3 同步屏障的应用

同步屏障用于UI绘制,在ViewRootImpl类的scheduleTraversals()方法中调用:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // UI绘制之前设置一个同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 发送绘制的消息,保证优先执行mTraversalRunnable
        // 最终会将该Runnable对象封装至Message中,并设置该Message为异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

当优先执行了mTraversalRunnable,调用其run()方法后,run()方法内部会调用doTraversal()方法,该方法内移除了之前设置的同步屏障,然后执行UI绘制操作方法performTraversals()

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除之前设置的同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

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

        // 进行UI绘制
        performTraversals();

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

4. Looper循环和消息出队

在1.3小节的消息机制初始化准备小节中,我们提到了Looper.loop()调用,其作用是开启一个消息循环,然后从MessageQueue队列中取出消息交由Handler处理。把它放到现在来讲是因为loop()方法和消息出队next()操作紧密相连,我们先看loop()方法内的实现:

4.1 Looper循环

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    ...省略部分代码

    for (;;) {
        // 当消息队列中没有消息或延迟执行消息时,MessageQueue的next()方法会阻塞
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...省略部分代码

        try {
            // 进行消息处理
            // 此target便是Handler#enqueueMessage(MessageQueue, Message, long)方法中第一行代码 msg.target = this
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        ...省略部分代码
        
        // Message回收
        msg.recycleUnchecked();
    }
}

该方法内部实现还是比较简单的:首先做了一些检验工作,然后开启一个死循环。在死循环中调用MessageQueuenext()方法获取消息,如果有则交由其封装的Handler处理(其处理逻辑见5. 消息处理),没有则阻塞。具体的阻塞和消息出队,都在MessageQueuenext()方法实现,进去看一看吧。

4.2 消息出队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.
    // 在3.1 消息入队源码分析章节中,我们知道了mPtr是native层的MessageQueue的引用地址
    // 通过这个引用地址,可以将native层和java层关联起来
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    // 用于统计当前闲置Handler数量
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 阻塞的时长
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 实现阻塞,阻塞时长为nextPollTimeoutMillis,Looper.loop()方法中的might block就是来自这里
        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;
            // msg.target == null表示该Message是一个屏障(barrier)。
            // 如果是屏障,则跳过该屏障之后所有的同步消息,只执行异步消息
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // 从队列中找出下一个异步Message
                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.
                    // 该Message执行时间还未到,所以需要设置阻塞时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    // 取出需要执行的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
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // 消息队列为空或Message未到执行时间时,则开始处理IdleHandler
            // 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.
            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 {
                // 执行IdleHandler中的queueIdle()方法
                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.
        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;
    }
}

next()方法代码内部也是一个死循环,代码比较长,我们分成两部分逻辑来分析:一部分是前半段查找获取消息的逻辑,另一部分是为后半段处理IdleHandler的逻辑。

4.2.1 查找获取消息

在死循环内,首先判断队列头是否为消息屏障,是则找出下一个异步的消息,否则取队列头消息。然后判断取出的消息执行时间when

如果执行时间没到,则设置阻塞时长,等下次循环时进行阻塞,否则取出该消息并立刻返回。

阻塞的代码为nativePollOnce(ptr, nextPollTimeoutMillis),这是一个native方法,nextPollTimeoutMillis表示延迟时长:

  • nextPollTimeoutMillis=0:首次执行next()方法的死循环时,调用nativePollOnce(ptr, nextPollTimeoutMillis)方法,会立刻返回不会阻塞,然后继续执行后面的代码;
  • nextPollTimeoutMillis=-1:当队列为空时,nativePollOnce(ptr, nextPollTimeoutMillis)会一直阻塞,除非有消息入队则触发唤醒;
  • nextPollTimeoutMillis>0:阻塞nextPollTimeoutMillis毫秒,在这期间如果有新的消息入队则可能触发唤醒(新的消息执行时间早于nextPollTimeoutMillis则会唤醒)。

唤醒的操作由第3节消息入队的nativeWake(mPtr)方法实现。入队唤醒和出队阻塞的方法都是native方法,由Linuxepoll机制实现,感兴趣可阅读《深入理解Android 卷III》第二章 深入理解Java Binder和MessageQueue 这篇文章中的2.3小节。

4.2.2 处理IdleHandler

当消息队列为空或Message未到执行时间时,则处理IdleHandlerIdleHandler可用于消息队列闲置时的处理,例如ActivityThread中的GcIdler,用于触发主线程中的GC垃圾回收,当主线程没有消息处理时,就会有可能触发GC

// ActivityThread类中的GcIdler内部类
final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}

4.3 Looper循环和消息出队小结

在这一章节中,Looper.loop()循环方法主要是调用MessageQueuenext()方法获取Message,然后交由对应的Hanlder处理。

MessageQueue队列如果为空,则一直阻塞,等待下次消息入队唤醒队列;不为空时,当消息的执行时间未到,则进行nextPollTimeoutMillis>0时长的阻塞,直到阻塞时间结束,或有新的消息入队,且其执行时间早于当前阻塞的消息执行时间,则唤醒队列。

接下来则看最后一个步骤,关于消息的处理逻辑。

5. 消息处理

Looper.loop()方法中,从MessageQueue中获取到一条不为空的消息时,调用了msg.target.dispatchMessage(msg)进行消息分发处理,此时又回到了Handler中,看下dispatchMessage(Message msg)方法:

// Handler.java

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

首先会判断msg.callback是否为null,这个callback就是封装的Runnable对象,即Hanlder.post系列方法投递的Runnable。如果不为空,则执行Runnablerun()方法。

否则,则判断mCallback是否为null。这个mCallback是什么东西呢?可以回顾下Handler的构造方法,其中构造方法2、3、5、6都有一个构造参数Callback,这个一个接口:

public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

如果Callback接口的方法handleMessage(Message msg)返回为true,则不再继续分发消息,否则调用HandlerhandlerMessage(Message msg)方法,这是一个空方法,一般选择在Handler的子类实现:

public void handleMessage(Message msg) {
}

一句话总结消息处理的逻辑:

  • 如果是post系列方法,则执行其Runnablerun()方法;
  • 否则判断Handler构造方法里传入的Callback是否返回为ture
    • true则消息处理结束;
    • false则继续分发给HandlerhandleMessage(Message msg),然后结束。

6. Handler移除消息源码分析

当需要移除一个MessageRunnable时,调用Handler对应的remove方法即可,其内部调用的是MessageQueue对应的remove方法。我们选择Handler.removeMessages(int what)这个方法来分析,其它移除逻辑基本一致。

// Handler.java

// 移除消息队列中所有满足Message.what=what的消息
public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

// MessageQueue.java

// 上面Handler的remove方法调用的是该方法
void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        // 此while循环移除回收了从队列头开始,连续满足移除条件的消息
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        // 否则在此while循环中移除回收之后的消息
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

主要是用了两个while循环来移除消息,第一个移除前面连续满足移除条件的消息,后面则依次判断移除满足条件的消息。

注意这里面有个暗坑,如果队列里有延迟执行的消息,其中有通过sendDelay发送的what=0的消息,也有通过postDelay投递的Runnable,如果调用Handler.removeMessages(0)方法来移除what=0的所有消息,很不幸你会发现,队列中的所有Runnable封装的消息也会被移除。原因是封装RunnableMessage,其what默认为0,正好满足移除what=0消息的逻辑,所以定义what时需要注意,避免定义为0。

7. 消息发送到处理完整过程

一个完整的消息机制从开始到结束的细节差不多就分析完了,现在我们将整个过程串起来,简要回顾一番:

  1. 初始化准备:手动调用Looper.prepare()方法初始化创建一个Looper对象,其内部会同时创建了一个与之关联的MessageQueue对象,然后通过ThreadLocal将该Looper对象存放至当前线程的私有内存中。接着手动创建一个Handler,用于发送和处理消息,可以通过构造方法传入之前创建的Looper;也可以不传,则会使用当前线程私有内存中存放的Looper对象。接着手动调用Looper.loop()方法,开启一个死循环,会一直调用MessageQueuenext()方法获取消息。
  2. 发送消息:手动调用send系列方法或post方法,最终会将消息的延迟时间加上当前开机后的时长,作为该消息的执行时间;进入sendMessageAtTime()方法,将当前Handler封装至Message中,然后调用MessageQueueenqueueMessage()方法进行入队操作。
  3. 消息入队:按照上步中的执行时间排序,将消息插入到MessageQueue队列中,如果队列为空,或者该消息的执行时间早于队列中的所有消息执行时间,则唤醒队列的阻塞状态
  4. 消息出队:由于初始化准备工作中已经开启了Looper循环,所以当MessageQueue中有消息到了需要执行的时候,则会通过next()方法返回一个Message进行消息分发。在next()方法中,如果队列为空或者队列中的消息执行时间都未到,则会导致死循环进入阻塞状态。
  5. 消息处理:如果是post系列的方法,则调用其Runnable对象的run()方法,否则判断Handler构造方法传入的Callback接口实现方法handleMessage()是否返回true,是则结束消息处理,否则再交由HandlerdispatchMessage()方法进行最后的处理。

8. Q & A

现在可以回答文章开头的问题了:

  1. Q: 主线程和子线程之间是如何实现通信的?

    A: 在主线程创建的Handler关联了主线程私有的LooperMessageQueue,然后Handler在子线程发送的Message进入到了主线程的MessageQueue,最终在主线程里通过Looper.loop()方法从MessageQueue中获取Message,交由Handler处理。

  2. Q: 子线程和子线程之间能否通过消息机制来通信?

    A: 能。需要在接收消息的子线程里,创建Handler之前需要手动调用Looper.prepare(),之后调用Looper.loop()方法,这样便可以在另一个子线程中发送消息到该子线程了。

  3. Q: 延迟发送或执行的内部原理又是如何实现的?

    A: 延迟的消息会将开机运行时间加上延迟时间所得到的时间作为消息的执行时间,进入消息队列后按照执行时间来排序插入队列中,出队时会通过nativePollOnce()方法在底层实现阻塞状态,阻塞时长为消息执行时间减去当前开机时长的差值,待阻塞状态结束后便会让该消息出队,并且交由Handler来分发处理。

  4. Q: 主线程在Looper.loop()中开启了一个死循环,为什么不会造成ANR?从MessageQueue中取出消息时可能会阻塞,为什么该阻塞也不会造成ANR

    A: 首先,ANR是因为输入事件得不到及时处理,此外还有ServeiceBroadcast等,我们统一称之为消息事件。当消息事件发送了却在规定的时间内无法得到处理,就会产生ANR现象。主线程调用Looper.loop()方法开启一个死循环,其目的就是用于分发处理这些消息事件,所以自然不会造成ANR,除非有其它消息事件做了耗时操作,才会有可能导致ANR发生。

    MessageQueue中取出消息时可能会阻塞,什么情况下会阻塞呢?队列为空或没有需要及时处理的消息时,才会发生阻塞,这是为了节约CUP资源不让它空转。如果你此时输入一个消息时间,阻塞状态就会被唤醒,该事件会进行入队出队分发处理操作,也就谈不上不及时处理,自然不会导致ANR发生。

9. 结束语

Android消息机制源码分析基本上已经结束了,由于技术水平有限及时间仓促,难免会有错误之处,还恳请指点出来,共同学习进步!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,724评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,104评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,142评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,086评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,076评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,914评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,220评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,871评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,318评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,834评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,951评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,574评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,162评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,162评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,383评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,349评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,652评论 2 343

推荐阅读更多精彩内容