全面剖析Android消息机制源码


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

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

Android消息机制的简单图解

消息的发送处处理能够大体分为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异常app

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

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

1.2 Looper的初始化工做

首先看下Looper类的构造方法async

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

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

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对象,若是是则抛异常:oop

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的构造方法,其中构造方法二、三、五、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消息机制源码分析基本上已经结束了,因为技术水平有限及时间仓促,不免会有错误之处,还恳请指点出来,共同窗习进步!

相关文章
相关标签/搜索