Android异步消息机制及源码分析

Posted yian_

tags:

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

【yian_ http://blog.csdn.net/yianemail/article/details/50233373 转载烦请注明出处,尊重分享成果】

一直对android中的消息处理机制比较模糊,恰好昨天做了一次Team知识分享,也就借此机会配合源码了解下消息处理机制,仅以博客记录。

1 前言

一:在分析别人的源码,一直都在workThread配合looper ,handler与uiThread交互互通Message。
二:对于很多初学者老说,大都是知其然而不知道其所以然的。
三:workThread与uiThread中间是怎样具体处理message的。

2 代码展示

你可能在刚开始接触Android开发时就会知道如下问题:

Android的UI时线程不安全的,如果在线程中更新UI会出现异常,导致程序崩溃;同时如果UI中做耗时操作又会导致臭名昭著的ANR异常。

为了解决如上这些问题,我们怎办呢?很简单,通常最经典常用的做法就是使用Android的异步消息机制实现即可(创建一个Message对象,使用Handler发送出去,然后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作)。所以说还是很有必要了解异步消息机制的Looper , Handler , Message等原理的。

一般我们在workThread通过handler发送meeesgae一般来说是这样的,首先是在workThread中发送消息,以及在uiThread中接受处理,于是,代码是这样的:

/**
 * Created by luhuanju on 15/12/8.
 */
public class TwoActivity extends Activity 
   static Handler handler=new Handler()
       @Override
       public void dispatchMessage(Message msg) 
           super.dispatchMessage(msg);
           switch (msg.what=0)
              //msg
           


       
   ;
    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) 
        super.onCreate(savedInstanceState, persistentState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() 
            @Override
            public void run() 
                Message message=handler.obtainMessage(0);
                message.obj="";//Object
                handler.sendMessage(message);

            
        ).start();

    

这就是我们平时用的Handler的过程,对于Handler异步处理的简单基础示例先说到这,接下来依据上面示例的写法分析原因与源代码原理。

3 分析Android异步消息机制源码

3-1异步消息概念流程图

handler.sendMessage —>MQ(looper不断循环取消息,一旦有新消息) —>handler(dispatchMessage);

3-2异步消息机制相关重要概念

3-2-1 Handler的工作原理

由例子代码很容易看出Handler 的工作主要包含发送消息以及处理消息,典型的发送一条消息snedMessage的源码如下:

    /**
     * Pushes a message onto the end of the message queue after all pending messages
     * before the current time. It will be received in @link #handleMessage,
     * in the thread attached to this handler.
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean sendMessage(Message msg)
    
        return sendMessageDelayed(msg, 0);//sendMessage继续调用了sendMessageDelayed
    

可以看到sendMessage继续调用sendMessageDelayed,继续查看

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

sendMessageDelayed 又继续调用了sendMessageAtTime,
sendMessageAtTime(Message msg, long uptimeMillis)方法有两个参数;msg是我们发送的Message对象,uptimeMillis表示发送消息的时间,uptimeMillis的值等于从系统开机到当前时间的毫秒数再加上延迟时间。

继续

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

又继续调用了enqueueMessage()方法,不同的是参数中带有一个mq,也就是消息队列(稍后讲)。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 
        msg.target = this;
        if (mAsynchronous) 
            msg.setAsynchronous(true);
        
        return queue.enqueueMessage(msg, uptimeMillis);
    

你可能不太了解上边的方法是什么意思。

msg.target = this;      这是什么?
queue.enqueueMessage(msg, uptimeMillis);  这个又是干嘛用的?

这个方法就是首先将我们要发送的消息Message的target属性设置为当前Handler对象(进行关联);接着将msg与uptimeMillis这两个参数都传递到MessageQueue(消息队列)的enqueueMessage()方法中。
至此,handler发送消息任务算是已经结束了,它仅仅是向mq中添加一条message而已。
到这边,就不得不继续解释一下MessageQuenu是什么了

3-2-1 MessageQueue的工作原理

什么是MessageQueue?

这里引用Android任玉刚书开发与艺术探索中对mq的一段描述:

消息队列在android中指的是MessageQueue,主要包含两个操作,插入和读取,读取本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next。enqueueMessage的作用是网消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将该消息将其从消息队列中移除。

<

MessageQueue要实例化吗?跟线程的关系是什么?跟looper关系又是什么?

MessageQueue要实例化,只不过实例化的位置有些特殊,它是通过looper实例化的。也就是说MQ跟looper是对应关系。

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

我们继续分析mq的enqueueMessage方法

    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消息队列对于消息排队是通过类似c语言的链表来存储这些有序的消息的。其中的mMessages对象表示当前待处理的消息;就是将所有的消息按时间(uptimeMillis参数,上面有介绍)进行排序。实质就是把消息添加到MessageQueue消息队列的头部。

到此Handler的发送消息及发送的消息如何存入到MessageQueue消息队列的逻辑分析完成。

继续看下next方法

  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;
        
    

可以看出来,这个next方法就是消息队列的出队方法(与上面分析的MessageQueue消息队列的enqueueMessage方法对比)。可以看见上面代码就是如果当前MessageQueue中存在待处理的消息mMessages就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态(在上面Looper类的loop方法上面也有英文注释,明确说到了阻塞特性),一直等到有新的消息入队。

那么问题来了,既然消息都存入到了MessageQueue消息队列,当然要取出来消息吧,不然存半天有啥意义呢?我们知道MessageQueue的对象在Looper构造函数中实例化的;一个Looper对应一个MessageQueue,所以说Handler发送消息是通过Handler构造函数里拿到的Looper对象的成员MessageQueue的enqueueMessage方法将消息插入队列,也就是说出队列一定也与Handler和Looper和MessageQueue有关系。

3-2-1 Looper的工作原理

looper字面意思“循环者”,更像是一个调度器,具体的来讲他就不断的从mq中查看是否存在新msg,若有则取出交由handler处理。若无则阻塞等待。

Looer是在那边实例化的?

通过查看源码可知,looper构造函数是private修饰的

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

实例化looper的方法为prepare();


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

细心的会发现,我们在uiThread创建hanlder时候并不会调用looper.prepare();然后我们在自线程中创建Handler必须调用looper.prepare();
这是由于在UI线程(Activity等)启动的时候系统已经帮我们自动调用了Looper.prepare()方法。
Looper.prepare()用于为一个workThread创建一个Looper,但是最重要的是要调用Looper.loop()才可以真正的开始消息循环。
looper.loop()源码:


    /**
     * Run the message queue in this thread. Be sure to call
     * @link #quit() to end the loop.
     */
    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.recycle();
        
    

第一个判断,知道为什么不调用looper.prepare()为什么会抛异常了吧。

 final Looper me = myLooper();
        if (me == null) 
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        

Looper.loop()方法是一个死循环,

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

上面这段代码眼熟吗?没错,他就是调用了mq.next()方法,前边已经分析过messageQueue的next() 工作原理,它不断去mq中查找是否有新消息。如果存在新消息,那么

    msg.target.dispatchMessage(msg);

每当有一个消息出队就将它传递到msg.target的dispatchMessage()方法中。其中这个msg.target其实就是上面分析Handler发送消息代码部分Handler的enqueueMessage方法中的msg.target = this;语句,也就是当前Handler对象。

所以接下来的重点自然就是回到Handler类看看我们熟悉的dispatchMessage()方法,如下:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) 
        if (msg.callback != null) 
            handleCallback(msg);
         else 
            if (mCallback != null) 
                if (mCallback.handleMessage(msg)) 
                    return;
                
            
            handleMessage(msg);
        
    

可以看见dispatchMessage方法中的逻辑比较简单,具体就是如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。

这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

到此整个Android的一次完整异步消息机制分析使用流程结束。接下来进行一些总结提升与拓展。

4 小结Handler整个使用过程与内在原理

这里先借用一张图

首先handler.sendMessage(message),此时会向当前handler所关联的looper的MessageQueue消息队列插入一条消息;

queue.enqueueMessage(msg, uptimeMillis);

MessageQueue内部是一个无限循环,当有新消息next()方法会返出新消息;

Message next() 
'''

与此同时,looper.loop方法内部利用死循环会不断去mq查找是否有新消息。

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

查找到则交由handler

   msg.target.dispatchMessage(msg);

处理消息

   static Handler handler=new Handler()
       @Override
       public void dispatchMessage(Message msg) 
           super.dispatchMessage(msg);
           switch (msg.what=0)
              //msg
           
       
   ;

5 结语

至此,handler消息机制已经分析的差不多了。

相信通过这一篇文章你对Android的Handler使用还是原理都会有一个质的飞跃。

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

Android 源码分析 Handler 异步消息机制

Android异步消息传递机制源码分析&&相关知识常被问的面试题

Android异步消息机制

Android 消息机制源码分析

AndroidAsyncTask源码分析

AndroidAsyncTask源码分析