代码分析Android消息机制
我们知道在编程时许多操作(如更新UI)需要在主线程中完成,而且,耗时操作(如网络连接)需要放在子线程中,否则会引起ANR。所以我们常使用Handler来实现线程间的消息传递,这里讨论的也就是Handler的运行机制。
Handler的运行主要由两个类来支撑:Looper与MessageQueue。熟悉开发的朋友都知道在子线程中默认是无法创建Handler的,这是因为子线程中不存在消息队列。当需要创建一个与子线程绑定的Handler时,标准代码如下:
classLooperThreadextendsThread{ publicHandlermHandler; publicvoidrun(){ Looper.prepare(); mHandler=newHandler(){ publicvoidhandleMessage(Messagemsg){ //processincomingmessageshere } }; Looper.loop(); } }
在创建Handler前,需要先调用Looper.prepare()方法,之后再调用Looper.loop()方法。也就是说Handler的功能实现建立在Looper之上。
staticfinalThreadLocalsThreadLocal=newThreadLocal (); finalMessageQueuemQueue; finalThreadmThread; privatestaticvoidprepare(booleanquitAllowed){ if(sThreadLocal.get()!=null){ thrownewRuntimeException("OnlyoneLoopermaybecreatedperthread"); } sThreadLocal.set(newLooper(quitAllowed)); } privateLooper(booleanquitAllowed){ mQueue=newMessageQueue(quitAllowed); mThread=Thread.currentThread(); }
由于Looper的消息循环是一个死循环,一个线程最多只能有一个Looper,所以Looper.prepare()函数首先检查该线程是否已经拥有一个Looper,如果有则抛出异常。Looper通过ThreadLocal类为每个线程储存独立的Looper实例,简单说一下ThreadLocal的实现原理:
Java并发编程:深入剖析ThreadLocal
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
publicstaticvoidloop(){ finalLooperme=myLooper(); if(me==null){ thrownewRuntimeException("NoLooper;Looper.prepare()wasn'tcalledonthisthread."); } finalMessageQueuequeue=me.mQueue; for(;;){ Messagemsg=queue.next();//mightblock if(msg==null){ //Nomessageindicatesthatthemessagequeueisquitting. return; } msg.target.dispatchMessage(msg); msg.recycleUnchecked(); } }
在4行可以看到一个我们熟悉的异常信息,说明并没有Looper与当前线程相关联,也就无法进行消息传递。Looper.loop()方法本身是一个死循环,不断在MessageQueue中取出Message对象进行处理,然后调用Message.recycleUnchecked()方法对其回收,这也是为什么官方推荐使用Message.obtain()方法来获取Message实例,而不是直接新建对象。当没有消息可处理时,MessageQueue.next()方法将阻塞,直到新的消息到来。
对于MessageQueue,我们只需要关注两个函数即可,一个是MessageQueue.enqueueMessage()另一个是MessageQueue.next(),它们分别对应着队列的插入与取出操作。MessageQueue中队列是使用单链表实现的,由Message.next属性指向其下一个元素。
booleanenqueueMessage(Messagemsg,longwhen){ synchronized(this){ msg.markInUse(); msg.when=when; Messagep=mMessages; booleanneedWake; if(p==null||when==0||when向MessageQueue中插入元素时,需要根据Message.when属性的大小决定插入的位置,它代表了Meesage需要被处理的时间,拿Handler.sendMessage()函数为例。
publicfinalbooleansendMessage(Messagemsg){ returnsendMessageDelayed(msg,0); } publicfinalbooleansendMessageDelayed(Messagemsg,longdelayMillis){ if(delayMillis<0){ delayMillis=0; } returnsendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis); } publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){ MessageQueuequeue=mQueue; returnenqueueMessage(queue,msg,uptimeMillis); } privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis){ msg.target=this; if(mAsynchronous){ msg.setAsynchronous(true); } returnqueue.enqueueMessage(msg,uptimeMillis); }从调用流程来看,Handler.sendMessage()函数其实就是向MessageQueue的消息队列中插入了一个Message.when属性为当前时间的元素。
对于MessageQueue.next()函数,简单来说它的作用就是在MessageQueue的头部取出元素,然后执行Handler.dispatchMessage()函数。
publicvoiddispatchMessage(Messagemsg){ if(msg.callback!=null){ handleCallback(msg); }else{ handleMessage(msg); } } privatestaticvoidhandleCallback(Messagemessage){ message.callback.run(); }如果我们使用Handler.post()函数发送一个Runnable对象,那么最终Runnable对象会在Handler.handleCallback()函数中执行。如果是一个普通Message,那么它会被分发到一个我们熟悉的函数中,Handler.handleMessage(),这就是为什么一般我们都需要重写这个函数对消息进行处理。