Android消息机制

Posted 爱搬砖的摄影师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android消息机制相关的知识,希望对你有一定的参考价值。

主线程的初始化

android的消息机制总的来说,是由Handler、Looper和MessageQueue这三者组成。平时我们只用关心Handler,是因为在主线程中,其他两者都由系统处理好了。在之前的Activity工作流程一文中已经提到过,为启动Activity而创建新进程的时候,系统会执行ActivityThread的main方法,在该方法中,会创建主线程,并为主线程初始化Looper、MessageQueue。

// ActivityThread
public static void main(String[] args) 
    ...
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

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

    if (false) 
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

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

可以看到,在该方法中调用Looper.prepareMainLooper();为主线程实例化了一个Looper,而在Looper的实例化中又会实例化一个MessageQueue。最后会调用Looper.loop();开始消息的循环。那么消息机制的三大部分分别有什么作用呢?下面来具体看一下。

Handler、Looper和MessageQueue的关系

Handler我们比较熟悉,实例化Handler时主要就是实现一下handleMessage方法,对消息的处理进行定制。
MessageQueue,顾名思义,就是一个消息队列,这个队列是用链表实现的,用于存储一个线程所对应的各种消息。
Looper,同样顾名思义,就是一个循环,用来不断循环地从MessageQueue中取出消息然后分发给对应的Handler进行处理。
Looper是整个消息机制的核心,先来看下这个类是怎么实现的。

ThreadLocal

在分析Looper之前,有必要先了解下ThreadLocal是什么。我们知道,同一个进程的不同线程会共享资源,也就是说某个线程对某一变量的修改会影响到其他线程,它们访问的是同一份资源。但是现在每个线程都想拥有自己独立的消息队列和循环处理,主线程用主线程的,工作线程用工作线程的,互不干扰,这个特性非常重要,每个线程拥有自己独立的消息队列,这样工作线程就无法对主线程的消息处理产生干扰,影响UI的处理。如果不用ThreadLocal而又想每个线程有自己独立的队列,方式无非有两种:一种是维护一个数组,每个线程对应着数组里的一个元素,存取都只对当前线程多对应的数组元素进行操作;另一种是维护多个变量,每个线程只能对其中之一进行操作,有多少个线程就维护多少的变量,但是线程数量实际上不可预知,除非规定最大线程数。这两种方式思路相似,只是存储的方式不同。幸运的是,java提供了ThreadLocal的方式,用ThreadLocal定义的变量,每个线程都会拥有自己独立的一份,互不影响。实际上在ThreadLocal的实现中,就是采用的第一种方式,维护了一个数组,这里我们只需要了解ThreadLocal的特性,不再分析其具体实现,以后有机会再做深入探讨。

Looper

Looper之前已经说过,用来不断从消息队列中取出消息,然后进行分发。

public final class Looper 
    ...
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class
    ...

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

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

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

    ...

任何时候,Handler要处理消息,都需要在所处线程中实例化一个Looper,主线程中Looper的实例化已经由系统调用prepareMainLooper完成,在工作线程中,则需要我们自己主动去调Looper.prepare()。可以看到prepare有两个重载方法,一个带参数,一个不带参数。带参数的prepare方法是private的,我们自己主动在工作线程中进行实例化时,只能调用不带参数的prepare,也就是这个Looper必须是能够退出的。而主线程中prepareMainLooper实际调的是prepare(false),也就是说主线程Looper是无法退出的。
prepare方法很简单,首先判断该线程中的Looper有没有实例化过,如果实例化过,就会直接抛异常;如果没有实例化,就会new一个Looper出来放到ThreadLocal中。在new一个Looper时,实际上就会实例化MessageQueue。
现在看到的只是Looper和MessageQueue的创建,如果要开始消息的循环,就需要调用Looper.loop()

public final class 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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) 
            Message msg = queue.next(); // might block
            if (msg == null) 
                // No message indicates that the message queue is quitting.
                return;
            

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) 
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            

            msg.target.dispatchMessage(msg);

            if (logging != null) 
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) 
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            

            msg.recycleUnchecked();
        
    

    ...

在loop方法中,实际上是一个无限循环,只有在queue.next()返回null的时候才会中断。queue.next()方法通常情况下,要么直接返回一个消息,要么会阻塞,只有在queue正在退出的时候才会返回null。从消息队列中取到一个消息后,调用msg.target.dispatchMessage(msg);来对消息进行处理,Message的target字段实际就是一个Handler,也就是我们平时sendMessage时所指定的Handler对象,这样,从消息队列中取出的消息就顺利地由Handler进行处理了。对于dispatchMessage方法,我们之后看Handler时再进行分析。消息处理完成后,会调用msg.recycleUnchecked();对消息进行回收,以便于重复利用,实际上是将这个消息清掉内容之后放入Message类维护的消息池中。
除了主线程不需要停止loop,其他工作线程中,我们处理完了所有消息之后,需要主动调quit方法退出消息的循环。quit方法有两种:quit()quitSafely()。前者忽略所有未处理的消息,直接强制退出消息循环;后者只是给一个标记,只有等到消息队列中所有非悬挂状态的消息(悬挂的消息是指待处理时间在当前时间之后的消息,实际上是在发送消息时定义了延迟时间的,当前还未达到规定的时间)处理完成,才真正退出。在此过程中,不再允许插入任何消息,包括sendMessage在内的所有插入消息的方法都会失败。

public final class Looper 
    ...

    public void quit() 
        mQueue.quit(false);
    

    public void quitSafely() 
        mQueue.quit(true);
    

    ...

MessageQueue

看MessageQueue,我们主要关注其添加消息(enqueueMessage)、取出消息(next)和退出(quit)方法。

public final class MessageQueue 
    ...

    boolean enqueueMessage(Message msg, long when) 
        if (msg.target == null) 
            throw new IllegalArgumentException("Message must have a target.");
        
        if (msg.isInUse()) 
            throw new IllegalStateException(msg + " This message is already in use.");
        

        synchronized (this) 
            if (mQuitting) 
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) 
                // New head, wake up the event queue if blocked.
                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.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                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.
            if (needWake) 
                nativeWake(mPtr);
            
        
        return true;
    

    ...

消息入队列的操作非常简单,实际就是通过当前message的时间(message相关的时间都是通过SystemClock.uptimeMillis()取得,即从系统启动开始不包括深度睡眠在内的所有时间)在链表中找到合适的插入位置,保证链表是按时间顺序排好序的。非常简单的单向链表操作,不用多看。

public final class MessageQueue 
    ...

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

    ...

出队列操作也非常简单,在不退出(没有调用quit)的情况下,如果没取到消息,这个操作一直循环,每隔一段时间由native代码轮询一下,至于这个轮询的时间间隔,实际上是由消息队列中第一个消息的处理时间与当前时间的时间差决定。正常情况下,1~2次轮询即可取出消息,因为第一次轮询取不到消息的时候,就计算了时间差,第二次轮询在这个时间差过去之后进行,就可以取到消息了。取到消息后,就像这个消息从链表中移除。

public final class MessageQueue 
    ...

    void quit(boolean safe) 
        if (!mQuitAllowed) 
            throw new IllegalStateException("Main thread not allowed to quit.");
        

        synchronized (this) 
            if (mQuitting) 
                return;
            
            mQuitting = true;

            if (safe) 
                removeAllFutureMessagesLocked();
             else 
                removeAllMessagesLocked();
            

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        
    

    private void removeAllMessagesLocked() 
        Message p = mMessages;
        while (p != null) 
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        
        mMessages = null;
    

    private void removeAllFutureMessagesLocked() 
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) 
            if (p.when > now) 
                removeAllMessagesLocked();
             else 
                Message n;
                for (;;) 
                    n = p.next;
                    if (n == null) 
                        return;
                    
                    if (n.when > now) 
                        break;
                    
                    p = n;
                
                p.next = null;
                do 
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                 while (n != null);
            
        
    

    ...

quit操作如之前看到的一样,可以传入一个boolean参数表示是否是安全退出。如果是安全退出,则只移除未来时间点才会触发的消息;如果非安全退出,那么就会直接移除所有消息。

Handler与sendMessage

在给指定Handler发送消息时,可以直接new一个message进行发送,也可以通过调用Message.obtain得到一个未使用的消息进行发送。推荐使用后一种方式,因为Message会维护一个消息池,obtain方法啊会从消息池中取出未使用的Message对象,如果全都正在使用,才会new一个Message出来,避免了资源的浪费。

public final class Message implements Parcelable 
    ...

    public static Message obtain() 
        synchronized (sPoolSync) 
            if (sPool != null) 
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            
        
        return new Message();
    

    void recycleUnchecked() 
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) 
            if (sPoolSize < MAX_POOL_SIZE) 
                next = sPool;
                sPool = this;
                sPoolSize++;
            
        
    

    ...

可以发现,之前我们看到的recycleUnchecked实际上就是将用完的消息全都清空,然后放入消息池中以便重复利用。这个思路在平时的开发中是非常值得学习的,很多需要频繁new出来的对象都可以用这种方式进行处理,但是也要注意设置一个合理的池子大小,避免长期占用无用资源导致内存无法回收。obtain有很多重载方法,大同小异,就不一一看了。

public final class Message implements Parcelable 
    ...

    public void sendToTarget() 
        target.sendMessage(this);
    

    ...

得到一个空的消息实例并填充了相应的数据之后,就可以通过sendToTarget将消息发送出去了,实际上是调到了Handler的sendMessage方法。

public class Handler 
    ...

    public final boolean sendMessage(Message msg)
    
        return sendMessageDelayed(msg, 0);
    

    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);
        
        return queue.enqueueMessage(msg, uptimeMillis);
    

    ...

可以看到,调来调去,最后只不过是调到了MessageQueue的enqueueMessage方法。现在,整个消息机制基本已经全都串联起来:Handler通过sendMessage将消息放入消息队列中,而Looper则通过loop方法一直循环地从消息队列中取出消息,并将消息分发给相应的Handler进行处理,直到调用消息队列的quit方法之后,Looper才会停止循环。至于Looper将消息交给Handler处理,之前我们只是看到了调用Handler的dispatchMessage方法,具体怎么处理的,可以再来细看一下。

public class Handler 
    ...

    public Handler(Callback callback) 
        this(callback, false);
    

    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();
    

    ...

可以看到,dispatchMessage实际优先调用message的callback,其次是构造方法传进来的mCallback的handleMessage方法,最后才轮到new一个Handler时所实现的handleMessage方法。
至此,整个消息机制分析完成。

以上是关于Android消息机制的主要内容,如果未能解决你的问题,请参考以下文章

[Android] 消息处理机制

[Android] 消息处理机制

Android消息传递之Handler消息机制

Android消息机制

Android消息机制

Android消息机制之Handler