android 消息机制
一、Android应用程序的主线程主要用于更新UI界面,并且主线程不能做耗时操作,否则会引起ANR;这种情况下需要开一个子线程来进行耗时操作,动作完成之后,子线程发消息给主线程通知其更新UI显示,常见方法有:
- Activity.runOnUiThread(Runnable);
- View.post(Runnable);
- View.postDelayed(Runnable, long);
- Handler消息机制。
注:经过看源码,会发现Activity.runOnUiThread(Runnable),View.post(Runnable),View.postDelayed(Runnable, long)最终本质上会调用到Handler发送消息的方法,如下代码:
Activity.runOnUiThread(Runnable);
1 /** 2 * Runs the specified action on the UI thread. If the current thread is the UI 3 * thread, then the action is executed immediately. If the current thread is 4 * not the UI thread, the action is posted to the event queue of the UI thread. 5 * 6 * @param action the action to run on the UI thread 7 */ 8 public final void runOnUiThread(Runnable action) { 9 if (Thread.currentThread() != mUiThread) { 10 mHandler.post(action); 11 } else { 12 action.run(); 13 } 14 }
View.Post(Runnable),View.PostDelayed(Runnable); 请忽略attachInfo判断。
1 public boolean post(Runnable action) { 2 final AttachInfo attachInfo = mAttachInfo; 3 if (attachInfo != null) { 4 return attachInfo.mHandler.post(action); 5 } 6 // Assume that post will succeed later 7 ViewRootImpl.getRunQueue().post(action); 8 return true; 9 } 10 11 public boolean postDelayed(Runnable action, long delayMillis) { 12 final AttachInfo attachInfo = mAttachInfo; 13 if (attachInfo != null) { 14 return attachInfo.mHandler.postDelayed(action, delayMillis); 15 } 16 // Assume that post will succeed later 17 ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); 18 return true; 19 }
二、弄清消息机制之前,提一个问题:如何在子线程实例化一个Handler发送message呢?代码很简单,如下
1 new Thread(){ 2 3 @Override 4 public void run() { 5 super.run(); 6 Looper.prepare(); 7 new Handler().sendEmptyMessage(1); 8 Looper.loop(); 9 } 10 11 }.start();
接下来,问题又来了:
1.发消息的时候为什么要调用Looper.prepare();
2.消息发送后为什么要调用Looper.loop();
3.为什么在主线程里发送信息,我们没有调用 这两个方法呢?
在解答这三个问题之前,先看一张图,大致了解一下消息机制是如何运行的?如下图:
总结一下上面那个图片:在UI线程中有一个消息队列MessageQueue,其它线程do something之后,在UI线程中的消息队列MessageQueue插入Message,而Looper负责轮循消息队列MessageQueue。然后来回答上面第1个问题,看代码:
1 public static void prepare() { 2 prepare(true); 3 } 4 5 private static void prepare(boolean quitAllowed) { 6 if (sThreadLocal.get() != null) { 7 throw new RuntimeException("Only one Looper may be created per thread"); 8 } 9 sThreadLocal.set(new Looper(quitAllowed)); 10 } 11 12 //初始化Looper,并且初始化消息队列 13 private Looper(boolean quitAllowed) { 14 mQueue = new MessageQueue(quitAllowed); 15 mThread = Thread.currentThread(); 16 }
通过上图可得知,接收一个消息的前提是,该线程得拥有自己的消息队列MessageQueue,而第1个问题的答案就是创建一个该线程接收消息的一个消息队列。然后第二个问题请看以下代码:
1 public static void loop() { 2 final Looper me = myLooper(); 3 if (me == null) { 4 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 5 } 6 final MessageQueue queue = me.mQueue; 7 8 // Make sure the identity of this thread is that of the local process, 9 // and keep track of what that identity token actually is. 10 Binder.clearCallingIdentity(); 11 final long ident = Binder.clearCallingIdentity(); 12 13 for (;;) { 14 Message msg = queue.next(); // might block 15 if (msg == null) { 16 // No message indicates that the message queue is quitting. 17 return; 18 } 19 20 // This must be in a local variable, in case a UI event sets the logger 21 Printer logging = me.mLogging; 22 if (logging != null) { 23 logging.println(">>>>> Dispatching to " + msg.target + " " + 24 msg.callback + ": " + msg.what); 25 } 26 27 msg.target.dispatchMessage(msg); 28 29 if (logging != null) { 30 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 31 } 32 33 // Make sure that during the course of dispatching the 34 // identity of the thread wasn't corrupted. 35 final long newIdent = Binder.clearCallingIdentity(); 36 if (ident != newIdent) { 37 Log.wtf(TAG, "Thread identity changed from 0x" 38 + Long.toHexString(ident) + " to 0x" 39 + Long.toHexString(newIdent) + " while dispatching to " 40 + msg.target.getClass().getName() + " " 41 + msg.callback + " what=" + msg.what); 42 } 43 44 msg.recycleUnchecked(); 45 } 46 }
通过上图可知消息机制中除了消息队列MessageQueue,还得有轮循,第二个问题就是执行轮循的操作。第三个问题请往下看代码:
1 public final class ActivityThread { 2 ...... 3 4 public static final void main(String[] args) { 5 ...... 6 //将当前线程初始化为Looper线程。最终会调用Looper.prepare() 7 Looper.prepareMainLooper(); 8 9 ...... 10 // 开始循环处理消息队列 11 Looper.loop(); 12 13 ...... 14 } 15 }
第三个问题的答案:android应用程序启动过程中,会在进程中执行ActivityThread中main方法,来初始化该应用中UI线程的Looper。这也就是为什么主线程里可以直接调用Handler,而子线程不能直接调用Handler发送消息。
参考:http://3dobe.com/archives/74/