Android消息处理机制Thread、Handler、Looper 分类: Android 2015-07-15 09:28 5人阅读 评论(0) 收藏
——Handler/Thread/Looper & MessageQueue
Keywords: Android Message HandlerThread Looper UML
本文解析Android如何利用Handler/Thread/Looper以及MessageQueue来实现消息机制的内部实现。知道了它的内部实现机理之后,以后再遇到使用它们时候的任何问题就驾轻就熟、迎刃而解了。
Android利用执行在HandlerThread线程中的Looper的相应消息分发/处理,与其他线程中的消息发送结合,实现完整的消息处理机制。本文首先介绍这些消息处理过程中的参与者之间的关系,然后结合WifiService消息处理的实际实例,讲解消息处理的全过程,最后还从线程视图的角度,重新审视一下线程同步模型。
一、HandlerThread/Looper/MessageQueue的前世今生
下图是从Froyo从抽取出的HandlerThread、Looper和MessageQueue的关系图。他们被定义在package android.os。
图一:HandlerThread/Looper/MessageQueue实例关系
HandlerThread是一个Thread,继承自Thread。它保留着对Looper实例的引用,不过这里还看不到HandlerThread、Looper和MessageQueue如何实例化,这要到二、HandlerThread/Looper/MessageQueue实例化之后才能看清如何实现的。
一看便知,Looper的构造函数是私有的,外界无法直接实例化之;要实例化,只有通过Looper::prepare()。这样HandlerThread就与Looper建立了对应关系。对如何建立的是不是已经迫不及待了,那就接着看下一小节。
二、HandlerThread/Looper/MessageQueue实例化
上一节只是看了HandlerThread、Looper和MessageQueue之间的静态视图关系,他们具体如何实例化的,还要看动态的实例初始化过程。
图二:HandlerThread/Looper实例化
- HandlerThread是一个Thread,在它被实例化并执行start()[序列1&2]之后,它的run()方法会在它自己的线程空间执行[序列3]。
- 上节已经提到了Looper的实例化是通过Looper::prepare实现的,图中可以看到Looper::prepare()正是在HandlerThread::run()中被调用而实例化[序列4&5]的。
- 而MessageQueue是在Looper的私有构造函数Looper()中实例化的。
总结一下,HandlerThread是被显式地通过new创建的实例,而与它绑定在一起的Looper是在HandlerThread的执行过程中被实例化的,相应的MessageQueue也是在这个过程中实例化的。
三、Looper::loop分发处理消息
上节重点讲了HandlerThread/Looper的实例化过程,它发生在Thread::run()这也是线程运行的关键,从图二也看到了Looper::loop()这个消息处理的核心正是在这里执行的,下面就看看它到底做了什么。
图三:Looper::loop()处理
图中的所有序列是发生在一个无限循环体while (true) {} 中的。
- mQueue:MessageQueue是Looper保留的一份引用,通过它的next()[序列1]获取MessageQueue中的下一个要处理的消息,这个过程中如果没有相应的消息,执行它的线程会用this.wait()释放它所拥有的MessageQueue的对象锁而等待。
- 一旦有消息到来[序列2],Looper会用获得的Message的Handler(msg.target)来分发处理消息[序列3&4]。消息处理完之后,还要回收[序列5]。
四、Handler实现具体消息处理
在Looper::loop()消息处理的顺序图里看到了Handler,这个消息分发处理的参与者。下面结合WifiHandler这个具体的Handler看它如何参与到消息的发送、分发和处理的。
4.1 Handler实例化
WifiService中定义了如下的WifiHandler。
图四、消息的发送、分发、处理者Handler
看Handler的构造函数是需要Looper的,从上面的分析知道,Looper是在HandlerThread执行中实例化的,Looper实例如何获得的呢?看图一&图二,知道HandlerThread保留着Looper的应用mLooper,并可通过getLooper()被外面获取。而Handler的mQueue: MessageQueue可以通过mLooper.mQueue获得。
所以,Wifi的HandlerThread,WifiHandler可以这样实例化:
HandlerThread wifiThread = new HandlerThread("WifiService"); wifiThread.start(); mWifiHandler = new WifiHandler(wifiThread.getLooper());
4.2 消息发送
图五、消息发送
通过Message::obtain()可以建立起Message和Target/Handler,what之间的关系,并得到一个Message msg[序列1&2];然后通过msg.sendToTarget()就可以用Handler来具体发送消息了[序列3&4];通过一系列的调用,最后会通过MessageQueue::enqueueMessage()把消息放到mMessages上[序列7],还会通过this.notify()通知正在等待新消息的线程,重新拥有MessageQueue的对象锁,而处理该消息。
图六、MessageQueue与Message的关系
至此,消息的发送,分发,处理基本上介绍完毕。
4.3 消息处理
而要具体处理消息,从图三的序列4就可知道,整个框架的最后消息的处理是通过Handler::handleMessager(msg: Message)来完成。所以如果有自己具体要处理的消息,只要override Handler的handleMessage(msg: Message)方法,并加入自己特定的对消息的处理即可。
要处理消息,就看看Message的属性里都有什么。
图七、Message属性
重点关注public属性what是区分具体什么消息;可以带参数arg1, arg2。
五、重新审视
5.1 应用指南
上面介绍了消息处理的全过程,这些对于只是用来发送和处理消息的应用者来说,可能有些繁杂,这里梳理一下从应用者角度看,怎么使用之。
其实四、Handler实现具体消息处理中的WifiService就是一个典型的应用场景。
实现一个Handler的子类,并Override handleMessage()方法,亦即,实现消息处理函数handleMessage()。然后分别创建HandlerThread和Hanlder继承类的实例。这样就可以如4.2里那样发消息了,而消息处理在handleMessage()中进行。
从应用者角度看,Looper和MessageQueue是消息处理的内部机制,可以不关注它的实现细节(唯一要知道的,Handler实例化的时候,需要通过HandlerThread获得Looper的实例,从而可以传递给Handler)。
5.2 线程角度
下面再从线程的角度看一下,消息处理过程中参与的线程,以及这些线程之间的同步。
显然的,这里有线程HandlerThread的参与,而且Looper::loop()就是执行在HandlerThread的run()方法里,也就是在HandlerThread里执行,这也就是说消息的分发处理和执行是在HandlerThread的线程上下文中。另外,还有至少一个线程存在,也就是创建了HandlerThread的线程B,以及执行消息发送的线程C,B和C有可能是同一线程。
消息的发送是在另外一个线程里,就是因为有了多个线程的存在,才有了线程的同步操作。可再次关注一下图三和图五中实现线程同步的Java原语wait()/notify()。
结束语
写本文的初衷并不是为了写它而写它,是在研究Android中的Wifi实现时,遇到了实现Wifi的各种具体操作都是通过消息来实现的,就想探讨一下ANDROID消息处理机制,这样就边看边用ROSE画了图,等到消息的发送/接收/处理的过程都看完了,再回头看一下整个UML图,这不就整个一完整的消息处理机制嘛!
工作这么多年过来了,回过头来看看,自己积累了什么呢,再看一下5、6年前鄙人的BLOG发现那时还是留下了些东西的,而且写本文时,重温Java的线程同步机制时,看到当时写的东西,发现如果是当时是自己理解的东西,并且用自己擅长的领域表达出来,自己回头再看时,一目了然,几个UML图就能解决问题了。
参考资料
Google Android source Froyo(2.2) git://android.git.kernel.org/platform/manifest.git
田海立 解析JVM线程同步机制 http://blog.csdn.net/thl789/article/details/566494
修正记录
2011/07/18 修改标题、摘要及关键词
消息发送分发处理的主要参与者是Looper, Handler,还有Thread的执行。很多应用场景有他们参与就可完成,HandlerThread倒是WifiService处理时消息处理的一个特例,所以,在这些关键点上去掉HandlerThread。
(本文可自由转载,但请给出原文链接:http://blog.csdn.net/thl789/article/details/6601558)。
***************************** 全文结束 *******************************
很多初入Android或Java开发的新手对Thread、Looper、Handler和Message仍然比较迷惑,衍生的有HandlerThread、java.util.concurrent、Task、AsyncTask由于目前市面上的书籍等资料都没有谈到这些问题,今天就这一问题做更系统性的总结。我们创建的Service、Activity以及Broadcast均是一个主线程处理,这里我们可以理解为UI线程。但是在操作一些耗时操作时,比如I/O读写的大文件读写,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR的响应提示窗口,这个时候我们可以考虑使用Thread线程来解决。
对于从事过J2ME开发的程序员来说Thread比较简单,直接匿名创建重写run方法,调用start方法执行即可。或者从Runnable接口继承,但对于Android平台来说UI控件都没有设计成为线程安全类型,所以需要引入一些同步的机制来使其刷新,这点Google在设计Android时倒是参考了下Win32的消息处理机制。
1. 对于线程中的刷新一个View为基类的界面,可以使用postInvalidate()方法在线程中来处理,其中还提供了一些重写方法比如postInvalidate(int left,int top,int right,int bottom) 来刷新一个矩形区域,以及延时执行,比如postInvalidateDelayed(long delayMilliseconds)或postInvalidateDelayed(long delayMilliseconds,int left,int top,int right,int bottom) 方法,其中第一个参数为毫秒
2. 当然推荐的方法是通过一个Handler来处理这些,可以在一个线程的run方法中调用handler对象的 postMessage或sendMessage方法来实现,Android程序内部维护着一个消息队列,会轮训处理这些,如果你是Win32程序员可以很好理解这些消息处理,不过相对于Android来说没有提供 PreTranslateMessage这些干涉内部的方法。
3. Looper又是什么呢? ,其实Android中每一个Thread都跟着一个Looper,Looper可以帮助Thread维护一个消息队列,但是Looper和Handler没有什么关系,我们从开源的代码可以看到Android还提供了一个Thread继承类HanderThread可以帮助我们处理,在HandlerThread对象中可以通过getLooper方法获取一个Looper对象控制句柄,我们可以将其这个Looper对象映射到一个Handler中去来实现一个线程同步机制,Looper对象的执行需要初始化Looper.prepare方法就是昨天我们看到的问题,同时推出时还要释放资源,使用Looper.release方法。
4.Message 在Android是什么呢? 对于Android中Handler可以传递一些内容,通过Bundle对象可以封装String、Integer以及Blob二进制对象,我们通过在线程中使用Handler对象的sendEmptyMessage或sendMessage方法来传递一个Bundle对象到Handler处理器。对于Handler类提供了重写方法handleMessage(Message msg) 来判断,通过msg.what来区分每条信息。将Bundle解包来实现Handler类更新UI线程中的内容实现控件的刷新操作。相关的Handler对象有关消息发送sendXXXX相关方法如下,同时还有postXXXX相关方法,这些和Win32中的道理基本一致,一个为发送后直接返回,一个为处理后才返回 .
5. java.util.concurrent对象分析,对于过去从事Java开发的程序员不会对Concurrent对象感到陌生吧,他是JDK 1.5以后新增的重要特性作为掌上设备,我们不提倡使用该类,考虑到Android为我们已经设计好的Task机制,这里不做过多的赘述,相关原因参考下面的介绍:
6. 在Android中还提供了一种有别于线程的处理方式,就是Task以及AsyncTask,从开源代码中可以看到是针对Concurrent的封装,开发人员可以方便的处理这些异步任务。
摘录自:http://www.android123.com.cn/androidkaifa/422.html
方法一:(java习惯,在android不推荐使用)
刚刚开始接触android线程编程的时候,习惯好像java一样,试图用下面的代码解决问题
new Thread( new Runnable() { public void run() { myView.invalidate(); } }).start();
可以实现功能,刷新UI界面。但是这样是不行的,因为它违背了单线程模型:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。
方法二:(Thread+Handler)
查阅了文档和apidemo后,发觉常用的方法是利用Handler来实现UI线程的更新的。
Handler来根据接收的消息,处理UI更新。Thread线程发出Handler消息,通知更新UI。
Handler myHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case TestHandler.GUIUPDATEIDENTIFIER: myBounceView.invalidate(); break; } super.handleMessage(msg); } };
class myThread implements Runnable { public void run() { while (!Thread.currentThread().isInterrupted()) { Message message = new Message(); message.what = TestHandler.GUIUPDATEIDENTIFIER; TestHandler.this.myHandler.sendMessage(message); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
以上方法demo看:http://rayleung.javaeye.com/blog/411860
方法三:(java习惯,不推荐)
在Android平台中需要反复按周期执行方法可以使用Java上自带的TimerTask类,TimerTask相对于Thread来说对于资源消耗的更低,除了使用Android自带的AlarmManager使用Timer定时器是一种更好的解决方法。 我们需要引入import java.util.Timer; 和 import java.util.TimerTask;
public class JavaTimer extends Activity { Timer timer = new Timer(); TimerTask task = new TimerTask(){ public void run() { setTitle("hear me?"); } }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); timer.schedule(task, 10000); } }
方法四:(TimerTask + Handler)
实际上这样做是不行的,这跟Android的线程安全有关!应该通过配合Handler来实现timer功能的!
public class TestTimer extends Activity { Timer timer = new Timer(); Handler handler = new Handler(){ public void handleMessage(Message msg) { switch (msg.what) { case 1: setTitle("hear me?"); break; } super.handleMessage(msg); } };TimerTask task = new TimerTask(){ public void run() { Message message = new Message(); message.what = 1; handler.sendMessage(message); } };public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);timer.schedule(task, 10000); } }
方法五:( Runnable + Handler.postDelayed(runnable,time) )
在Android里定时更新 UI,通常使用的是 java.util.Timer, java.util.TimerTask, android.os.Handler组合。实际上Handler 自身已经提供了定时的功能。
private Handler handler = new Handler(); private Runnable myRunnable= new Runnable() { public void run() { if (run) { handler.postDelayed(this, 1000); count++; } tvCounter.setText("Count: " + count); } };
然后在其他地方调用
handler.post(myRunnable);
handler.post(myRunnable,time);
案例看:http://shaobin0604.javaeye.com/blog/515820
版权声明:本文为博主原创文章,未经博主允许不得转载。