相信不少人对这几个概念深恶痛绝,因为它们“像雾像雨又像风”——自我感觉都很熟识,如果下一次再相遇,却又陌生得很。这种“隔靴搔痒”的感觉促使我们必须与这些“顽固分子”来个彻底的决断。
先不要想太多,任头脑中随意画下对这些概念的第一印象,如图5-5所示。
▲图5 5 概念初探
图5-5是我们对这几个概念的“感官”释义,读者可以尝试着思考下是否和自己所想的基本一致。
那么,如果把这些概念糅合在一起,又会是怎样的呢?如图5-6所示。
▲图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是一对多的关系。
▲图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 MessageQueueMessageHandler的“循环圈”(见图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所示。
▲图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章节的描述。
▲图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所示。
▲图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中的内容有重叠,我们统一放在下一小节讲解。