源码浅析: Message/Handler/MessageQueue/Looper
来源:互联网 发布:ae cc mac下载 编辑:程序博客网 时间:2024/05/21 13:54
源码浅析: Message/Handler/MessageQueue/Looper
(注:以下部分摘自 侯捷----《深入浅出MFC》)
熟悉Win32/MFC编程的人都知道,Windows应用程序的都是以事件来驱动。换句话说,程序不断等待(利用一个while回路),等待任何可能的输入,然后做判断,然后再做适当的处理。这个「输入」就是以消息(Message)的形式表现出来的。这个过程可以用下图简单的表示。如果把应用程序获得的各种「输入」分类,可以分为由硬件装置所产生的消息(如鼠标移动或键盘被按下),放在系统队列(system queue)中,以及由Windows系统或其它Windows程序传送过来的消息,放在程序队列(application queue)中(在这个过程中,使用SendMessage(…)和PostMessage(…)这两个API来发送消息)。以应用程序的眼光来看,消息就是消息,来自哪里或放在哪里其实并没有太大区别,反正程序调用GetMessage API就取得一个消息,程序的生命靠它来推动。
可想而知,每一个Windows程序都应该有一个回路如下:
对于接受并处理消息的主角就是窗口。每一个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的「窗口函数」(window procedure)。如果窗口获得一个消息,这个窗口函数必须判断消息的类别,决定处理的方式。
所以程序一旦运行起来,就会不停的:获取消息-->分发消息-->处理消息-->获取消息-->….
消息从哪里来?嗯。上面提到过了,从消息队列(消息泵)里来。如果没有消息怎么办,那就在获取消息(GetMessage)的这个函数这里阻塞住,直到有新的消息发送到消息队列里。如果你了解上面所描述的概念,再看Android的消息循环应该会发现真的很简单,只不过多了一些封装,多了一些类而已,整体思路都是大同小异的。
相关概念
在看源码前,我们先需要熟悉一下它们的概念及作用。Message:用于封装消息的简单数据结构。里面包含消息的ID、数据对象、处理消息的Handler引用和Runnable等。
Handler:消息的发送者和最终消息处理者。
MessageQueue:消息队列,提供消息的添加、删除、获取等操作来管理消息队列。
Looper:用于建立消息循环并管理消息队列(MessageQueue),不停的从消息队列中抽取消息,分发下去并执行。
注:以下分析均以 android 2.3.3 源码为基础。
Message源码分析
成员变量
我们先看一下它的成员变量。
long when:该消息何时被处理的绝对时间戳。
Handler target:谁来处理该消息。如果它为空,那说明该消息可能被recycle掉了,存放在Message Pool中,或者,它代表一个QUIT消息。
Runnable callback:Runnable对象,如果为该Message设置了该对象,那么有优先执行它。这里需要看Handler的消息处理机制。在分析Handler时再提。
Message next:这个看起来有点奇怪,有种似曾相识的感觉,想想,到底什么情况。哦,想起来了,就是C语言里面链表的数据结构。
由于JAVA是没有指针这个概念的,所以内部维护了一个next的引用。所以,实际上,Message本身不单纯是一个简单的只包含数据的类,它实际上是一个链式结构的类,也就是说,一个Message本身就是一个消息队列,它通过next将所有消息串联起来。既然Message本身就是消息队列,那MessageQueue又是如何建立消息队列的又是怎么回事?实际上,MessageQueue内部只有一个Message成员,它所要做的工作就是把Message实体串连起来,形成消息链。
接着再看静态成员变量:
从这上面能看出,有个叫mPool的Message对象,如果理解了Message本身就是链表结构,那么,应该就明白了为什么一个消息叫Pool(池),因为一个Message本身就代表着一群Message,通过next把一系列Message给串联起来。对于无数个message实体来说,他们共享同一个全局的消息池(链),里面存放废弃掉的message。很明显,这是在做缓存机制。
在该类中,核心函数有:
obtain()及其系列函数
obtain()系列函数最核心的函数就只有obtain()方法,其它函数只不过提供了更多的可选参数,内部都是调用obtain()方法,因此,我们只需要关注核心函数的实现即可。
该函数内部首先是从全局的废弃消息池(链)中去取,看看有没有废弃掉的Message,如果有,那我们就获取消息链中第一个废弃掉的Message,这样,就无需再创建一个新的Message;如果消息池中没有,那就只能new一个新的消息出来。这样做的好处就是废物再利用,减少创建时间。实际上,这种思想很值得我们借鉴。对于其它重载版的obtain方法,内部都是先调用它,然后再使用其它额外的参数进行填充的。如:
recycle()函数
其中:clearForRecycle代码如下这个函数首先是看当前消息池中废弃个数已达上限(池子是不是满了),如果没有达到上限,则调用clearForRecycle()函数把当前消息的各种信息清空,然后添加到消息链的头部。注意:该函数的if (mPoolSize < MAX_POOL_SIZE)实际上是没有起到任何作用的,搜遍Message所有代码也没有发现mPoolSize的值有任何变化,始终为0,也就是说,这句话是恒成立的。只要该Message被recycle掉,那他就会加入到废弃链中。
可以用以下图示表示该过程:值得说明一点的是,该recycle()函数何时被调用?有以下两个时机被调用:
- MessageQueue类中的removeMessages(...)及其系列函数,即当我们要从消息队列中干掉一个Message时,该Message被回收到废弃消息链。
- Looper类中的loop函数。即当我们使用完了某个Message后,该Message被回收到废弃消息链。
sendToTarget()函数
该函数比较简单,就是通过Message内部引用的Handler将消息发送出去。Handler源码分析
成员变量
我们先看一下它的成员变量:
Handler作为一个管理者,其重要做用就是创建并发送消息,最后再处理消息。
发送消息即为把指定的Message放入到消息队列中,等到合适的时机,消息泵从消息队列中抽取消息,再分发下去,进行处理。
因此,在Handler中,有必要维护当前线程的MessageQueue和Looper的引用。对于一个线程来说,MessageQueue和Looper都是唯一的,而多个handler是可以共享同一个线程的MessageQueue和Looper的引用。
Handler里面有以下几类核心函数共同完成上面的功能。
- 构造函数
- 创建消息函数
- 发送消息函数
- 移除消息函数
- 消息分发及处理函数
构造函数
构造函数主要是对成员变量进行初始化,获取线程中的Looper、MessageQueue等对象。
默认构造函数通过Looper.myLooper()函数从当前线程中获取Looper对象,如果Looper对象不存在,那么Handler构造就失败了,会抛出RuntimeException,而MessageQueue是由Looper对象创建出来的,因此,mQueue直接便能从Looper中获取。对于UI线程,在程序初始化时,实际上looper对象就已被创建出来(通过调用Looper.prepare()进行创建,并把looper对象存放到一个静态的sThreadLocal中),因此,正常情况下,当我们new出来的Handler不指明任何参数时,实际上就是会默认关联到UI线程。但是,但如果该对象是在某个线程的Run方法中被创建出来,那么它会被关联到该后台线程。“关联到该线程”的意思实际上就是,当Handler关联到UI线程,那最终发送的消息是加到了UI线程的消息队列,如果它关联到后台线程,则发送的消息加到了后台线程的消息队列。下面的这种方式,mHandler会被直接关联到指定的线程。
创建消息函数:obtainMessage()及其系列函数
这些函数都很简单,无非是通过Message.obtain(…)方法创建消息。obtain方法已在Message类中进行相关说明。还记得obtain方法的调用过程吗?忘记了的请回过头再看看。
发送消息函数
发送的过程是由post其系列函数和send系列函数进行的。如下:
这些函数最终都是调用的sendMessageAtTime函数。
该方法就是调用MessageQueue的enqueueMessage(…)方法将指定的Message插入到消息队列中去,即加入Message链,并指明何时应该从消息队列中取出来执行。其中uptimeMillis就是绝对时间戳,uptimeMillis = current time + delayMillis。
postXXX系列函数来说,需要指定一个Runnable对象,在合适的时间执行。
实际上,他们最终调用的还是sendMessageAtTime函数,只不过中间多了一步,即根据Runnable创建Message对象。如:
其中,getPostMessage代码如下:就是通过obtain方法获取一个Message,并设置其callback。
移除消息系列函数
这些函数的作用就是从当前消息队列中移除掉所有指定ID或指定Runnable对象的Message。如:
上面的所有函数完成了第一个功能,即消息的发送,并加入到消息队列中去。
但发送完后,并不是立即就能执行,当消息从消息泵中被取出来后,才行执行。因此,消息的发送和处理实际上是一个异步的过程。
消息分发及处理函数
当消息从消息泵中抽取出来后,就会进行消息的分发。
消息抽取的过程需要参见Looper的核心处理函数:
从上面可以看出,loop函数本身就是一个回路(死循环),不停的调用queue.next()函数从消息链中取出消息(如果取不到消息就会被阻塞住)。然后通过MSG的target成员变量(Handler)来调用其dispatchMessage方法将消息分发下去,然后执行消息处理函数。
在消息的分发的过程中,其执行是有优先级的:
如果Message中包含callback(即通过post系列函数设置的Runnable对象),那么它会被优先执行。
否则,如果给当前的Handler设置了mCallback,那么它会优先执行。如果该方法返回true,分发结束,处理完毕。如果返回false,那么他还有机会执行默认的handleMessage函数。
以下是三种情况的示例代码:
MessageQueue源码分析
成员变量
mMessages:在最初不了解Message类时,以为MessageQueue里面,存放的是一个类似于LinkedList<Message>的数据结构。当了解Message数据结构后,才知道MessageQueue里面维护的只有一个Message,因为Message本身就能构建一个Message链。MessageQueue主要功能就是维护这个Message链,如插入和删除Message链中的元素,并提供获取Message链中next Message的方法。这里,mMessages始终指向的是消息链的第一个节点,即头节点。
mIdleHandlers:外部注册的回调列表(listeners)。如果当前消息队列已没有新的Message能被取出来时,线程即将被阻塞前被调用。即线程处于空闲时间时,被调用。
mPendingIdleHandlers:该成员变量配合上面的mIdleHandlers使用,我觉得它没有必要保存起来,完全用一个临时变量即可。
mQuiting:当前消息队列是否已准备退出。实际上,如果它为True,也就表明当前线程将会立马结束掉。
mQuitAllowed:是否允许退出消息队列。对于主线程(UI线程),该标志量为true。
mBlocked:标志当前消息队列是否处于阻塞状态。下面的接口定义了线程处于空闲状态时的回调函数。由此可以看出,当你想在线程不忙的时候干点其它事情的话,这个接口就能派得上用场了。
以下方法用来注册和移除线程处于空闲状态时的回调函数。enqueueMessage(Message msg, long when)函数
该函数的目的就是把指定的Message按照绝对时间插入到当前的消息队列中去。还记得该函数是在哪里被调用的吗?请回过头看看Handler的源代码:sendMessageAtTime(Message msg, long uptimeMillis)
next()函数
我们先提前一步看看Looper.loop()函数(里面省掉了若干无用代码)。
该函数就是从消息队列中取出消息,然后把这个取出来的消息扔给Looper,Looper根据消息进行处理。
从上面的代码来看,loop函数本身就是一个回路(死循环),不停的调用queue.next()函数从消息链中取出消息(如果取不到消息就会被阻塞住)。然后通过MSG的target成员变量(Handler)来调用其dispatchMessage方法将消息分发下去,然后执行消息处理函数。如果取出来的消息的target为null,那么说明该消息是退出消息,则Looper退出,线程即将结束。
从上面的代码来看,next()函数主要有三个作用:
- 如果消息链中有合适的消息,直接将MSG扔出去。
- 如果没有,在消息循环进入阻塞状态。通过nativePollOnce进行阻塞。
- 在阻塞前,会通过外界(用户)注册的IdleHandler接口通知外界(用户),线程即将处于block状态,外界可以处理一些其它的事情。如说垃圾回收。
在看这个函数时,最开始觉得这里的逻辑有点困惑,特别是for(;;)和IdleHandler调用的过程。看上去,这里的for(;;)看上去是是个死循环,但实际上,每调用一次next()函数,这个循环最多只会被执行三次。
第一次循环,正如上面的功能1,如果消息链中有合适的消息,直接将MSG扔出去。
如果没有,则会通知各listeners,线程空闲了。执行完后,为了避免在listners执行的过程中,有消息投递,那么此时重置nextPollTimeoutMillis,然后进行第二次循环,由于此时nextPollTimeoutMillis为0,则nativePollOnce不会阻塞,立即返回,取MSG,如果此时消息链中还是没有MSG,则会在将会在continue处结束第二次循环,此时nextPollTimeoutMillis已被设置为-1,最终,第三次循环时,nativePollOnce发现nextPollTimeoutMillis为-1,则进入无限等待状态,直到有新的MSG被投递到队列中来。当有新的MSG后,由于enqueueMessage中调用了nativeWake函数,nativePollOnce会从等待中恢复回来并返回,继续执行,然后将新的MSG扔出去,for循环结束。三次循环结束。
至于nativePollOnce函数是如何进行阻塞的,可以参考:http://book.51cto.com/art/201208/353352.htm删除函数
其内部运作基本一样,我们只需要搞明白一个即可:
该函数有三个作用:
- 查找功能:如果doRemove为false,则该函数中是从消息链中查找是否有对应的消息。有则返回true,否则返回false
- 删除功能:从消息链中找到所有的同ID、同target、同object的消息,并把它从当前的消息链中断开。
- 构建Message Pool:构建废弃消息链(池)。还记得Message的recycle()方法吗?该方法就会把当前的消息废弃掉,加入到废弃消息链中,以供废品再利用。
Looper源码分析
Looper的主要功能是管理MessageQueue,不停的从MessageQueue里面抽取消息,然后分发下去,周而复始,直到抽取到的消息是退出消息,Looper结束,线程即将退出。
Looper有以下几点需要注意:
- 一个线程只能有一个Looper对象。
- 一个Looper对象只能有一个MessageQueue
我们先看一下它的重要成员变量及初始化函数:
显然,在创建一个Looper时,它就会顺便创建一个消息队列,初始化mRun,并关联到当然线程。由于构造函数是私有的,那如何创建Looper对象?通过prepare()函数。
对于每个线程来说,sThreadLocal存放着Looper的唯一实例,多次调用会直接导致异常。所以,一个线程只能调用一次prepare()函数。另外,在该类中,维护了一个主线程的Looper对象,并提供了一系列方法可以访问它:
顺便贴出主线程Looper对象生成的源代码:
对于Looper类来说,最重要的莫过于loop()函数,不过该函数已被重复提过几次,这里不再重复描述了。
另外,再一个重要函数是quit(),它通过向消息队列中插入一条QUIT Message来退出Looper循环,从而达到退出线程的目的。其中,Quit Message的标志就是该Message的target为null。
- 源码浅析: Message/Handler/MessageQueue/Looper
- 源码浅析: Message/Handler/MessageQueue/Looper
- Android源码浅析: Message/Handler/MessageQueue/Looper
- Handler、Message、MessageQueue、Looper调用过程源码浅析
- Handler、Message、MessageQueue、Looper调用过程源码浅析
- Handler源码分析,Handler,Looper,Message,MessageQueue
- Handler MessageQueue Message Looper关系浅析
- Handler,Looper,Message,MessageQueue之间关系浅析
- (Handler+Message+Looper+MessageQueue)源码分析
- Handler,Looper,MessageQueue,Message源码端理解
- Handler,MessageQueue,Message,Looper源码分析
- Looper,Handler,Message,MessageQueue
- Message,MessageQueue,Looper,Handler
- Message,Handler,Looper,MessageQueue
- Handler、Looper、MessageQueue、Message
- Handler,Message,Looper & MessageQueue
- Handler、looper、message、messageQueue
- Handler,Looper,MessageQueue(Message)
- java的数据类型
- 基于用户投票的排名算法(三):Stack Overflow
- hibernate 工程配置说明
- 前台json的获取方法
- 基于用户投票的排名算法(四):牛顿冷却定律
- 源码浅析: Message/Handler/MessageQueue/Looper
- 数据存储Introduce
- java自动类型提升
- android——代码实现在指定位置显示View
- Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例
- Android 插值属性动画Property Animation
- 在Linux上配置同时支持ASP.NET和PHP的服务器
- 基于用户投票的排名算法(五):威尔逊区间
- OCR:几大开源库