您的位置:首页 > 产品设计 > UI/UE

Android Handler中的handleMessage方法和post方法之源码剖析 及UI更新方法

2016-08-05 15:18 471 查看
我们都知道,在子线程中进行UI操作(更新UI控件)包括以下四种方法:
1.Handler的handlerMessage()方法。
2.Handler的post()方法。
3.View的post()方法。
4.Activity的runOnUiThread()方法。
本文重点分析前两种方法,后面两种稍微说一下。在说第一个方法之前,让我们先来看张图片(图片来源于http://my.oschina.net/keeponmoving/blog/61129)



这个图片在很好的说明了Handler中的handleMessage方法的工作原理(至少说明了怎么回事)。但是这个图有个问题,作者把handleMessage画到了主线程的外面,其实应该是要在主线程里面,这样才能通过handleMessage来操作主线程的ui,接下来详细说明原因:
先看看如何创建handler吧,其构造函数如下:

[java]
view plain
copy





public Handler() {  
    if (FIND_POTENTIAL_LEAKS) {  
        final Class<? extends Handler> klass= getClass();  
        if ((klass.isAnonymousClass() ||klass.isMemberClass() || klass.isLocalClass()) &&  
                (klass.getModifiers() &Modifier.STATIC) == 0) {  
            Log.w(TAG, "The followingHandler class should be static or leaks might occur: " +  
                klass.getCanonicalName());  
        }  
    }  
    mLooper = Looper.myLooper();  
    if (mLooper == null) {  
        throw new RuntimeException(  
            "Can't create handler insidethread that has not called Looper.prepare()");  
    }  
    mQueue = mLooper.mQueue;  
    mCallback = null;  
}  

可以看到,mLooper = Looper.myLooper();创建了一个新的Looper对象,mLooper不能为空,如果为空则会抛出异常,我们来看下Looper.myLooper()这个方法:

[java]
view plain
copy





public static finalLooper myLooper() {  
    return (Looper)sThreadLocal.get();  
}  

这个方法功能很简单,就是从sThreadLocal线程中取出Looper对象。如果sThreadLocal线程有Looper对象则返回,如果没有则返回空。那什么时候给sThreadLocal线程设定的Looper对象呢?来看看Looper.prepare()方法吧:

[java]
view plain
copy





public static finalvoid prepare() {  
    if (sThreadLocal.get() != null) {  
        throw new RuntimeException("Onlyone Looper may be created per thread");  
    }  
    sThreadLocal.set(new Looper());  
}  

其实,我们在使用Handler之前需要调用Looper.prepare()方法,那为什么我们一般在主线程中根本没有调用这个方法啊,其实系统已经帮我们调用了的。ActivityThread中的main()方法源码:

[java]
view plain
copy





public static void main(String[] args) {  
    SamplingProfilerIntegration.start();  
    CloseGuard.setEnabled(false);  
    Environment.initForCurrentUser();  
    EventLogger.setReporter(newEventLoggingReporter());  
    Process.setArgV0("<pre-initialized>");  
    Looper.prepareMainLooper();  
    ActivityThread thread = new ActivityThread();  
    thread.attach(false);  
    if (sMainThreadHandler == null) {  
        sMainThreadHandler =thread.getHandler();  
    }  
    AsyncTask.init();  
    if (false) {  
        Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));  
    }  
    Looper.loop();  
    throw new RuntimeException("Mainthread loop unexpectedly exited");  
}  

上面代码的第7行就已经调用了Looper.prepare方法(读者可以继续查看Looper.prepareMainLooper()源代码)。

创建好了handler之后,我们再使用的时候需要调用sendMessage()方法,看代码sendMessage():

[java]
view plain
copy





Message message = newMessage();   
mHandler.sendMessage(message);  

从上面的代码可以看出,每次sendMessage的时候都需要先new一个新的message,比较浪费内存空间。那还有没有更好的办法呢?再来看一下obtainMessage():

[java]
view plain
copy





public final Message obtainMessage(int what, int arg1, int arg2, Object obj){  
      return Message.obtain(this, what, arg1,arg2, obj);  
}  

这个方法调用了Message的静态方法obtain,来一起看下obtain()方法的源代码:

[java]
view plain
copy





public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj) {  
        Message m = obtain();  
        m.target = h;  
        m.what = what;  
        m.arg1 = arg1;  
        m.arg2 = arg2;  
        m.obj = obj;  
        return m;  
}  

又调用了一个无参的obtain()方法,源代码如下:

[java]
view plain
copy





/* 
     * Return a new Message instance from theglobal pool. Allows us to 
     * avoid allocating new objects in manycases. 
*/  
public static Message obtain() {  
        synchronized (sPoolSync) {  
            if (sPool != null) {  
                Message m = sPool;  
                sPool = m.next;  
                m.next = null;  
                m.flags = 0; // clear in-useflag  
                sPoolSize--;  
                return m;  
            }  
        }  
        return new Message();  
    }  

原来这个是从消息池中返回的一个message对象,并没有重新创建一个,所以从内存开销角度来说,推荐使用obtainMessage()方法,而不是new一个Message对象。无论是使用new
Message()方法还是调用其obtainMessage()方法,都要调用sendMessage()方法,sendMessage()方法都会辗转调用sendMessageAtTime()方法中,其源代码如下所示:

[java]
view plain
copy





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

sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。这个MessageQueue又是什么东西呢?其实从名字上就可以看出了,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。而enqueueMessage()就是入队的方法了,源代码如下:

[java]
view plain
copy





final boolean enqueueMessage(Message msg, long when) {  
        if (msg.when != 0) {  
            throw new AndroidRuntimeException(msg +" This message is already in use.");  
        }  
        if (msg.target == null &&!mQuitAllowed) {  
            throw new RuntimeException("Mainthread not allowed to quit");  
        }  
        synchronized (this) {  
            if (mQuiting) {  
                RuntimeException e = newRuntimeException(msg.target + " sending message to a Handler on a deadthread");  
                Log.w("MessageQueue",e.getMessage(), e);  
                return false;  
            } else if (msg.target == null) {  
                mQuiting = true;  
            }  
            msg.when = when;  
            Message p = mMessages;  
            if (p == null || when == 0 || when <p.when) {  
                msg.next = p;  
                mMessages = msg;  
                this.notify();  
            } else {  
                Message prev = null;  
                while (p != null && p.when<= when) {  
                    prev = p;  
                    p = p.next;  
                }  
                msg.next = prev.next;  
                prev.next = msg;  
                this.notify();  
            }  
        }  
        return true;  
    }  

通过这个方法名可以看出来通过Handler发送消息实质就是把消息Message添加到MessageQueue消息队列中的过程而已。通过上面遍历等next操作可以看出来,MessageQueue消息队列对于消息排队是通过类似c语言的链表来存储这些有序的消息的。其中的mMessages对象表示当前待处理的消息,然后18到49行可以看出,消息插入队列的实质就是将所有的消息按时间进行排序。既然有入队,那么肯定会有出队的啊,那么出队又是什么鬼样呢?

[java]
view plain
copy





/** 
    * 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.recycleUnchecked();  
       }  
   }  

可以看到,这个方法从第17行开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。如下所示:

[java]
view plain
copy





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 (false) Log.v("MessageQueue", "Returning message: " + msg);  
                        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;  
        }  
    }  

它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法的第31行,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实这个msg.target就是上面分析Handler发送消息代码部分Handler的enqueueMessage方法中的msg.target
= this;语句,也就是当前Handler对象。所以接下来的重点自然就是回到Handler类看看我们熟悉的dispatchMessage()方法,如下:

[java]
view plain
copy





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

如果mCallback不为空,则会调用handleMessage函数,这个有没有很熟悉?没错,就是我们自己在主线程中写的那个handleMessage方法了,从这段代码中也可以看出,之前sendMessage的参数也被传到了handleMessage方法中了。所以,一个标准的异步消息处理的函数应该就是这样子的:

[java]
view plain
copy





class LooperThread extends Thread {  
      public Handler mHandler;  
  
      public void run() {  
          Looper.prepare();  
  
          mHandler = new Handler() {  
              public void handleMessage(Message msg) {  
                  // process incoming messages here  
              }  
          };  
  
          Looper.loop();  
      }  
  }  

好了,第一种方法已经说完了,接下来说说post方法,我们在使用的时候需要传进一个Runnable参数:

[java]
view plain
copy





handler.post(new Runnable() {  
           @Override  
           public void run() {  
               //do something  
           }  
       });  

我们来看下post的源码吧:

[java]
view plain
copy





public final boolean post(Runnable r){  
   return  sendMessageDelayed(getPostMessage(r), 0);  
}  

返回一个sendMessageDelayed方法,有没有觉得很熟悉?至少sendMessageAtTime(上面已经分析过这个源码了)很熟吧,其实sendMessageDelayed就是调用的sendMessageAtTime方法,源代码如下:

[java]
view plain
copy





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

那我们再来看看sendMessageDelayed的第一个参数getPostMessage(r)的原理,其实从上面两段代码就可以看出getPostMessage(r)就是将一个Runnable对象转换为一个Message对象,如下所示:

[java]
view plain
copy





private final Message getPostMessage(Runnable r) {  
    Message m = Message.obtain();  
    m.callback = r;  
    return m;  
}  

在这个方法中将消息的callback字段的值指定为传入的Runnable对象。咦?这个callback字段看起来有些眼熟啊,喔!在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:

[java]
view plain
copy





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

调用的run方法就是我们通过post来重写的Runnable对象的一个方法,WTF!!这也太简单了吧,好了,总结下:通过post()方法在Runnable对象的run()方法里更新UI的效果完全和在handleMessage()方法中更新UI原理完全相同,特别强调这个Runnable的run方法还在当前线程中阻塞执行,没有创建新的线程。
那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到Handler的handleMessage方法中或者handleCallback方法中,这时的handleMessage方法和handleCallback已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。最后来看下View的post方法:

[java]
view plain
copy





public boolean post(Runnable action) {  
    Handler handler;  
    if (mAttachInfo != null) {  
        handler = mAttachInfo.mHandler;  
    } else {  
        ViewRoot.getRunQueue().post(action);  
        return true;  
    }  
    return handler.post(action);  
}  

Activity中的runOnUiThread()方法,如下所示:

[java]
view plain
copy





public final void runOnUiThread(Runnable action) {  
    if (Thread.currentThread() != mUiThread) {  
        mHandler.post(action);  
    } else {  
        action.run();  
    }  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息