Android多媒体系统分析-Handler, MessageQueue, Runnable与Looper概念

时间:2021-04-06 17:32:43
相信不少人对这几个概念深恶痛绝,因为它们“像雾像雨又像风”——自我感觉都很熟识,如果下一次再相遇,却又陌生得很。这种“隔靴搔痒”的感觉促使我们必须与这些“顽固分子”来个彻底的决断。 
先不要想太多,任头脑中随意画下对这些概念的第一印象,如图5-5所示。 
Android多媒体系统分析-Handler, MessageQueue, Runnable与Looper概念 
▲图5 5 概念初探 
图5-5是我们对这几个概念的“感官”释义,读者可以尝试着思考下是否和自己所想的基本一致。 
那么,如果把这些概念糅合在一起,又会是怎样的呢?如图5-6所示。 
Android多媒体系统分析-Handler, MessageQueue, Runnable与Looper概念 
▲图5 6 Runnable, Message, MessageQueue, Looper和Handler的关系简图 
下面来解释图5-6的含义。 
 Runnable和Message可以被压入某个MessageQueue中,形成一个集合 
注意,一般情况下某种类型的MessageQueue只允许保存相同类型的Object。图中我们只是为了叙述方便才将它们混放在同一个MessageQueue中,实际源码中需要先对Runnalbe进行相应转换。 
 Looper循环地去做某件事 
比如在这个例子中,它不断地从MessageQueue中取出一个item,然后传给Handler进行处理,如此循环往复。假如队列为空,那么它会进入休眠。 
 Handler是真正“处理事情”的地方 
它利用自身的处理机制,对传入的各种Object进行相应的处理并产生最终结果。 
用一句话来概括它们,就是: 
Looper不断获取MessageQueue中的一个Message,然后由Handler来处理。 
接下来的一系列分析无论多复杂,都是基于这句话展开的,希望读者牢记。 
可以看出,上面的几个对象是缺一不可的。它们各司其职,很像一台计算机中CPU的工作方式:*处理器(Looper)不断地从内存(MessageQueue)中读取指令(Message),执行指令(Handler),最终产生结果。当然,到目前为止我们还只是从逻辑的层面理清了它们的关系,接下来将从代码的角度来验证这些假设的真实可靠性。 
1.Handler 
代码路径:frameworks/base/core/java/android/os/Handler.java 
读者有没有注意过,Handler和线程Thread是什么关系。 
public class Handler {… 
final MessageQueue mQueue; 
final Looper mLooper; 
final Callback mCallback; 
本小节中几个主要元素的类关系如图5-7所示。 
从图中可以看出,Handler和Thread确实没有在表象上产生直接的联系。但是因为: 
① 每个Thread只对应一个Looper; 
② 每个Looper只对应一个MessageQueue; 
③ 每个MessageQueue中有N个Message; 
④ 每个Message中最多指定一个Handler来处理事件。 
由此可以推断出,Thread和Handler是一对多的关系。 
Android多媒体系统分析-Handler, MessageQueue, Runnable与Looper概念 
▲图5 7 Thread和Handler的关系图 
Handler是应用开发人员经常会使用到的一个类。概言之,它有两个方面的作用。 
 处理Message,这是它作为“处理者”的本职所在。 
 将某个Message压入MessageQueue中。 
实现第一个功能的相应函数声明如下。 
public void dispatchMessage(Message msg);//对Message进行分发 
public void handleMessage(Message msg);//对Message进行处理 
Looper从MessageQueue中取出一个Message后,首先会调用Handler.dispatchMessage进行消息派发;后者则根据具体的策略来将Message分发给相应的责任人。默认情况下Handler的派发流程是: 
Message.callback(Runnable对象)是否为空 
在不为空的情况下,将优先通过callback来处理。 
Handler. mCallback是否为空 
在不为空的情况下,调用mCallback.handleMessage。 
如果前两个对象都不存在,才调用Handler.handleMessage 
由此可见,Handler的扩展子类可以通过重载dispatchMessage或者handleMessage来改变它的默认行为。具体选择何种方式取决于项目的实际需求。 
Handler的第二个功能是容易引起开发人员困惑之所在,因为这样的设计形成了Handler MessageQueueMessageHandler的“循环圈”(见图5-7)。 
相应的功能函数声明如下。 
1. Post系列: 
final boolean post(Runnable r); 
final boolean postAtTime(Runnable r, long uptimeMillis); 
… 
2. Send系列: 
final boolean sendEmptyMessage(int what); 
final boolean sendMessageAtFrontOfQueue(Message msg); 
boolean sendMessageAtTime(Message msg, long uptimeMillis); 
final boolean sendMessageDelayed(Message msg, long delayMillis);等等 
Post和Send两个系列的共同点是它们都负责将某个消息压入MessageQueue中;区别在于后者处理的函数参数直接是Message,而Post则需要先把其他类型的“零散”信息转换成Message,再调用Send系列函数来执行下一步。 
我们只挑选第一个Post函数来分析源码,其逻辑流程如图5-8所示。 
Android多媒体系统分析-Handler, MessageQueue, Runnable与Looper概念 
▲图5 8 post()的调用逻辑 
public final boolean post(Runnable r) 

return sendMessageDelayed(getPostMessage(r), 0); 

因为调用者提供的是Runnable对象,post需要先将其封装成一个Message,接着通过对应的send函数把它推送到MessageQueue中。 
private static Message getPostMessage(Runnable r) { 
Message m = Message.obtain(); /*Android系统会维护一个全局的Message池。当用户需要使 
用Message时,可以通过obtain直接获得,而不是自行创建。这样的设计可以避免不必要的资源浪费*/ 
m.callback = r;/ 将Runnable对象设置为Message的回调函数
return m; 

当准备好Message后,程序调用sendMessageDelayed来执行下一步操作。这个函数可以设定延迟多长时间后再发送消息,其内部又通过当前时间+延时时长计算出具体是在哪个时间点(sendMessageAtTime)发送消息。 
sendMessageAtTime函数的源码如下。 
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 
MessageQueue queue = mQueue;/ Handler对应的消息队列
if (queue == null) {/*正常情况下,每个Thread都会有一个MessageQueue来承载消息, 
除非发生了意外queue才会为空*/ 
RuntimeException e = new RuntimeException( 
this + ” sendMessageAtTime() called with no mQueue”); 
Log.w(“Looper”, e.getMessage(), e); 
return false; 

return enqueueMessage(queue, msg, uptimeMillis);/ 将这个消息压入MessageQueue中

这样我们就将一条由Runnable组成的Message通过Handler成功地压入了MessageQueue中。读者可能会觉得很奇怪,最终仍然是依靠Handler来处理这一消息的,为什么它不直接执行操作,而是大费周折地先把Message压入MessageQueue中,然后再处理呢?这其实体现了程序设计一个良好的习惯,即“有序性”。 
比如某天你和朋友去健身房运动,正当你在跑步机上气喘吁吁时,旁边有个朋友跟你说:“哥们,最近手头紧,借点钱花花。”那么这时候你就有两个选择: 
马上执行 
这就意味着你要从跑步机上下来,问清借多少钱,然后马上打开电脑进行网上转账。 
稍后执行 
上面的方法在某些场合下是有用且必需的——比如你的朋友等着这笔钱急用。不过大部分情况下“借钱”这种事并不是刻不容缓的,因而你可以跟朋友说:“借钱没问题,你先和我秘书约时间,改天我们具体谈细节。”那么在这种情况下,“借钱事件”就先通过秘书写入了MessageQueue进行排队。这意味着你并不需要马上从跑步机上下来,中断愉悦的健身运动。 
之后秘书会一件件通知你MessageQueue上的待办事宜(当然你也可以主动问秘书),直到某个Message上标明“借钱事件”时才需要和这位朋友做进一步商谈。在一些场合下这样的处理方式是合情合理的。比如说健身房一小时花费是10万元,而你朋友只借100元,那么显然“借钱事件”优先级较小,排队处理就是必要的。 
下面以框图的形式来加深大家的理解,如图5-9所示。 
2.MessageQueue 
源码路径:frameworks/base/core/java/android/os 
MessageQueue正如其名,是一个消息队列,因而它具有“队列”的所有常规操作。包括: 
 新建队列 
由其构造函数和本地方法nativeInit组成。 
其中,nativeInit会在本地创建一个NativeMessageQueue对象,然后直接赋给MessageQueue中的成员变量。这一系列的操作实际上都是通过内存指针进行的,详见本书JNI章节的描述。 
Android多媒体系统分析-Handler, MessageQueue, Runnable与Looper概念 
▲图5 9 两种消息处理方式类比 
 元素入队 
final boolean enqueueMessage(Message msg, long when); 
 元素出队 
final Message next(); 
 删除元素 
final void removeMessages(Handler h, int what, Object object); 
final void removeMessages(Handler h, Runnable r, Object object); 
 销毁队列 
和创建过程一样,也是通过本地函数nativeDestroy来销毁一个MessageQueue。 
整个MessageQueue类的源码数量不多,也并不难理解,这里先不做深入分析。接下来涉及MessageQueue的具体应用场景时,我们再有重点地进行了解。 
3.Looper 
源码路径:frameworks/base/core/java/android/os 
Looper有点类似于发动机——正是由于它的推动,Handler甚至整个程序才成为活源之水,不断地处理新的消息。 
还记得前面我们将Handler,MessageQueue,Looper等概念糅合在一起组合成的那个“大脑印象图”吗?实际上它们在Android系统中的源码实现和这个图是比较接近的,只不过还需要做一点修改,如图5-10所示。 
Android多媒体系统分析-Handler, MessageQueue, Runnable与Looper概念 
▲图5 10 更贴近Android实现的“印象图” 
图中传达了一个信息,即Looper中包含了一个MessageQueue。因此,整个框图就只剩下Looper和Handler(还有多数情况下隐藏得很深的Thread)之间的关系了。 
应用程序使用Looper分为两种情况。 
主线程(MainThread) 
也就是ActivityThread,下一小节有详细介绍。 
普通线程 
下面是一个使用Looper的普通线程范例,名为LooperThread。 
class LooperThread extends Thread { 
public Handler mHandler; 
public void run() { 
Looper.prepare();/ 一句简单的prepare,究竟做了些什么工作?
mHandler = new Handler() { 
public void handleMessage(Message msg) { 
…/ 处理消息的地方。继承Handler的子类通常需要修改这个函数

}; 
Looper.loop();/ 进入主循环


这段代码在Android线程中很有典型意义。概括起来只有三个步骤: 
1.Looper的准备工作(prepare) 
2.创建处理消息的handler 
3.Looper开始运作(loop) 
虽然代码给人的感觉非常简洁,不过乍一看仍有不少地方难以理解。比如整个过程我们都没有看到Looper对象的创建,程序最后也只是调用了Looper.loop,整个系统的循环消息处理机制就“跑”起来了。另外,mHandler是如何保证把外部的消息投递到Looper所管理的MessageQueue中的,或者说Looper和Handler之间有何隐藏的联系呢? 
下面结合上面那段LooperThread代码,来逐一品味下Android呈现出的这一台好戏。 
首先, 
Looper.prepare(); 
既然要使用Looper类的函数,那么LooperThread中肯定就得 
import android.os.Looper; 
仔细观察,Looper里有个非常重要的成员变量: 
static final ThreadLocal sThreadLocal = new ThreadLocal(); 
这是一个静态类型的变量,意味着一旦import了Looper后,sThreadLocal就已经存在并构建完毕。ThreadLocal对象是一种特殊的全局变量,因为它的“全局”性只限于自己所在的线程,而外界所有线程(即便是同一进程)一概无法访问到它。这从侧面告诉我们,每个线程的Looper都是独立的。 
可以猜想下,虽然Looper提供了若干static的成员函数以方便开发者进行调用,但是它们毕竟只代表了“公共的行为”,其内部一定还需要有针对每个Thread的特定数据存储空间。举个例子,Looper中的这些static操作有点类似于银行的普通业务服务窗口,它并不指定任何特定的客户,但是去办理业务的人却又是完全可以区分开来的。为什么呢?因为大家手头都会持有反映自己身份的各种证件和申请表格——sThreadLocal。 
因而sThreadLocal肯定会创建一个只针对当前线程的Looper及其他相关的数据对象,而且这个操作很可能是在prepare(Looper总共就两行,这个就很明显了)中。下面来验证下是不是这样的。 
private static void prepare(boolean quitAllowed) { 
if (sThreadLocal.get() != null) {/*sThreadLocal.get返回的是模板类,这个场景中是Looper。 
这个判断保证一个Thread只会有一个Looper实例存在*/ 
throw new RuntimeException(“Only one Looper may be created per thread”); 

sThreadLocal.set(new Looper(quitAllowed)); 

上面的最后一个语句验证了我们的猜想:sThreadLocal的确保存了一个新创建的Looper对象。其实sThreadLocal这个名字取得不太好,容易引起误解。如果改成sThreadLocalData,估计大家就能看得更明白些了;而且它还是一个模板类,这就说明它并不存储特定类型的数据,而是任何你感兴趣的东西都可以——出租的是“柜子”,而不是“鞋柜”。 
接下来创建一个Handler对象,我们单独将它提取出来以方便阅读。 
public Handler mHandler; 
… 
mHandler = new Handler() { 
public void handleMessage(Message msg) {… 

}; 
可见,mHandler是LooperThread的成员变量,并通过new操作创建了一个Handler实例。仅从这两行代码来看,并没有什么蹊跷的地方。那么,Handler到底是如何与Looper关联起来的呢?没错,就是构造函数。 
Handler有多个构造函数,比如: 
public Handler(); 
public Handler(Callback callback); 
public Handler(Looper looper); 
public Handler(Looper looper, Callback callback); 
之所以有这么多构造函数,是因为Handler有如下内部变量需要初始化。 
final MessageQueue mQueue; 
final Looper mLooper; 
final Callback mCallback; 
我们就以LooperThread例子中采用的第一个函数来讲解下它的构造函数。 
public Handler() { 
/ …省略部分代码
mLooper =Looper.myLooper();/ 还是通过sThreadLocal.get来获取当前线程中的Looper实例
… 
mQueue = mLooper.mQueue; / mQueue是Looper与Handler之间沟通的桥梁
mCallback = null; 

这样Handler和Looper,MessageQueue就联系起来了。后续Handler执行Post/Send两个系列的函数时,会将消息投递到mQueue也就是mLooper.mQueue中。一旦Looper处理到这一消息,它又会从中调用Handler来进行处理。 
到目前为止,代码就只剩下最后的一句Looper.loop()了。因为和ActivityThread中的内容有重叠,我们统一放在下一小节讲解。