Looper,MessageQueue,Handler,异步消息线程

异步消息线程:

         正常的线程应该是执行run()方法后线程就结束,而异步消息处理线程就是指:在线程启动后,线程维护一个内部的消息队列,线程外部不断有消息进入这个消息队列,而此线程进入了无限循环的过程,在这过程中,每循环一次,就从线程内部的消息队列中提取一个消息进行处理,处理结束后继续进行下一次循环。如果消息队列为空,则线程暂停,直到消息队列中有新的消息。

         那么要实现异步消息线程必须需要一下三种条件:

         一:需要一个消息队列,采用先进先出的排队方式,哪个消息先进入队列哪个先被处理。(android里的实现这个功能的类:MessageQueue.java)

         二:需要在线程中执行一个无限循环的语句(while(true))来读取消息,读取完后进行消息的处理。(android里实现这个功能的类:Looper.java)

         三:需要在外部实现:可以向线程的消息队列中插入新消息。(android里实现这个功能的类是:Handler.java)

         消息是由Handler(sendMessage()方法)加入到MessageQueue中进行排队,等待Looper处理,如果轮到某个消息的处理,Looper会回调相应的Handler中的handleMessage()进行处理。之所以叫异步,是因为线程外部A在把消息加入队列后,并不在这个时候等待消息的处理,它可以干别的事情,直到有人通知外部A:轮到这个消息了,这时候A才处理这个消息。这个机制一般用线程之间的互相通信上,比如:子线程通知主线程更新UI。所以我们在使用Handler的时候,看上去只是重写了Handler并用sendMessage()发送了消息,但其实android在背后做了很多工作。接下来我们看看它具体是怎么异步实现的。

首先先来分析一下MessageQueue.java

       1 . 我们知道MessageQueue是用来实现消息队列的,那么这个消息队列是怎么构建的呢。先来看看消息的组成:

        消息是由Message.java存储的,下面是Message的部分源码:

public final class Message implements Parcelable {
	.....
     /*package*/ Handler target;   //处理这个消息的handler
     /*package*/ Message next;    //形成链表<pre class="brush:bash;toolbar:false;">     /*package*/ long when;   //消息应该被处理的时间 
     /*package*/ Runnable callback;    //回调函数
     private static final Object sPoolSync = new Object();   //对象锁
     private static Message sPool;   //静态变量,指向头链表
     private static int sPoolSize = 0;   //当前对象池中的个数

     private static final int MAX_POOL_SIZE = 50;	//对象池中对象的最大个数
     ......
     public static Message obtain() {
         synchronized (sPoolSync) {
             if (sPool != null) {
                 Message m = sPool;
                 sPool = m.next;
                 m.next = null;
                 sPoolSize--;
                 return m;
             }
         }
         return new Message();
     }
     public void recycle() {
         clearForRecycle();

         synchronized (sPoolSync) {
             if (sPoolSize < MAX_POOL_SIZE) {
                 next = sPool;
                 sPool = this;
                 sPoolSize++;
             }
         }
     }
     /*package*/ void clearForRecycle() {
         flags = 0;
         what = 0;
         arg1 = 0;
         arg2 = 0;
         obj = null;
         replyTo = null;
         when = 0;
         target = null;
         callback = null;
         data = null;
     }     
}
 
 

Handler  target:每一个Message对象都对应了一个唯一的Handler对象,表明这个消息是由这个Handler进行处理得,后面Looper的回调就是获取了这个target来执行Handler中的handleMessage()方法。

Message next:用来保存下一个Message对象,这样就形成了一个单向的链表结构。

sPoolSync,sPool,sPoolSize,MAX_POOL_SIZE,这几个主要用来构建一个对象池,让我们来看看这个对象池的构建:

           如果不知道Message.java中提供了obtain()方法来获取Message对象的话,我们一般会直接new一个对象来使用,那么在这个new对象使用完之后,这个对象会不会就进入了垃圾回收队列中等待被系统回收呢?答案是不一定的。因为Message.java中维持了一个对象池,一开始对象池是没有对象的,每当有消息被加到消息队列中,并且消息被处理结束后,在Looper的无限循环中方法loop()中会调用recycle()方法,这个方法有什么用呢?从代码中,我们可以看到,它把每个Message对象的属性都置为null,并且把处理完的消息对象链接成一个链表,对象池维持的对象最大数量是50个,这个消息池中的对象只有在本线程销毁时才会加入到垃圾回收队列中等待被系统回收。那么,我们来看看obtain()方法,sPool指向的永远是对象池中可用的消息对象,如果没有则sPool指向的是null,这时候obtain()方法返回的是一个自己new的对象。至于这个对象链表是怎么排列并且sPool是怎么指向的,待日后有时间再看看。现在只要知道Message.java中提供了obtain()方法来获取Message对象就可以了。


      2. 消息是有Message.java封装的,那么真正的消息队列是怎么么构成的呢?答案就在MessageQueue.java中。

          我们先来看看MessageQueue.java的部分代码:

private final boolean mQuitAllowed; // 如果为true,则此消息队列可以退出
Message mMessages;      //在构建消息队列时,用来临时保存消息对象
private boolean mQuitting;          //如果为true,则表明这个消息队列已经退出
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        // We can assume mPtr != 0 because the loop is obviously still running.
        // The looper will not call this method after the loop quits.
        nativePollOnce(mPtr, 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 (false) Log.v("MessageQueue", "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("MessageQueue", "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;
    }
}

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new RuntimeException("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);
    }
}
boolean enqueueMessage(Message msg, long when) {
    if (msg.isInUse()) {
        throw new AndroidRuntimeException(msg + " This message is already in use.");
    }
    if (msg.target == null) {
        throw new AndroidRuntimeException("Message must have a target.");
    }

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

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

可以看到,在MessageQueue.java的构造方法里,有个方法是nativeInit(),这个方法是干嘛的呢?原来在MessageQueue类的内部本身,它并没有真正的保存一个消息队列,真正的消息队列数据其实是保存在JNI的c代码中,这个nativeInit()方法才是真正构造了一个消息队列,具体是怎么构造的,我们以后再研究。现在清楚的是,每一个MessageQueue对象在创建时就初始化好了消息队列,如果在使用Looper时,MessageQueue对象没有被创建,那么将会报错,这会在Looper.java中讲到。那么MessageQueue.java作为一个消息队列的实现类,它必须要有两个方法:取出消息,添加消息。如上面的代码中所示,取出消息的方法是:next(),添加消息的方法是:enqueueMessage(Message msg, long when),对于这两个方法感兴趣的同学可以仔细看一遍,无论是取出消息还是添加消息,最后面总有nativeWake(mPtr);nativePollOnce(mPtr, nextPollTimeoutMillis)这些JNI的身影,更加说明了这个消息队列的维护是在c代码中进行的。next()方法中,值得注意的地方是:我们在往消息队列中发送消息时可以在使用sendEmptyMessageDelayed( int what, long delayMillis)这个方法来延迟发送,那么,在next()方法中是怎么处理以达到延迟的效果的呢?

                    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 (false) Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
如果这个消息带有延迟的时间,在这个时间没到之前,会继续下一个消息的处理,直到遍历完这一次的消息队列。在Looper中,会无线循环调用这个next()方法,所以,这里会一直执行直到指定的时间返回这个消息。MessageQueue.java中还有一个比较重要的方法就是quit()方法,它用来退出当前的消息队列。


     3.然后来看看Looper.java。在看这个类之前,我们先来回顾变量的种类,并了解一下“线程局部变量”这个概念。

        变量的常见作用域:

          函数内部的变量:其作用域是在此函数内,每次调用此函数时,该变量都会重新回到初始化。

          类内部的变量:其作用域是该类所产生的所有对象。只要该对象没有被销毁,则此对象内的变量会一直保持。

          类内部的静态变量:其作用域是整个进程。即只要在该进程内,此变量的值就一直保持,无论这个类构造了多少个对象,该变量只有一个赋值。

对于类内部的静态变量,无论是哪个进程里的线程引用该变量,其值总是相同,因为静态变量有一个单独的存储空间。但是有时我们却希望,当从同一个线程中引用该变量时,其值总是相同,而从不同的线程引用该变量时,其值应该不同,即我们需要一种作用域为线程的变量定义,这就是“线程局部存储”。

          ThreadLocal就是被用来提供这个功能的类,



你可能感兴趣的:(Looper,MessageQueue,Handler,异步消息线程)