详解Android中Handler的内部实现原理
本文主要是对handler和消息循环的实现原理进行源码分析,如果不熟悉handler可以参见博文《详解android中handler的使用方法》,里面对android为何以引入handler机制以及如何使用handler做了讲解。
概括来说,handler是android中引入的一种让开发者参与处理线程中消息循环的机制。我们在使用handler的时候与message打交道最多,message是hanlder机制向开发人员暴露出来的相关类,可以通过message类完成大部分操作handler的功能。但作为程序员,我不能只知道怎么用handler,还要知道其内部如何实现的。handler的内部实现主要涉及到如下几个类: thread、messagequeue和looper。这几类之间的关系可以用如下的图来简单说明:
thread是最基础的,looper和messagequeue都构建在thread之上,handler又构建在looper和messagequeue之上,我们通过handler间接地与下面这几个相对底层一点的类打交道。
一图胜千言
我们在本文讨论了thread、messagequeue、looper以及hanlder的之间的关系,我们可以通过如下一张传送带的图来更形象的理解他们之间的关系。
在现实生活的生产生活中,存在着各种各样的传送带,传送带上面洒满了各种货物,传送带在发动机滚轮的带动下一直在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到另一端进行收集处理。
我们可以把传送带上的货物看做是一个个的message,而承载这些货物的传送带就是装载message的消息队列messagequeue。传送带是靠发送机滚轮带动起来转动的,我们可以把发送机滚轮看做是looper,而发动机的转动是需要电源的,我们可以把电源看做是线程thread,所有的消息循环的一切操作都是基于某个线程的。一切准备就绪,我们只需要按下电源开关发动机就会转动起来,这个开关就是looper的loop方法,当我们按下开关的时候,我们就相当于执行了looper的loop方法,此时looper就会驱动着消息队列循环起来。
那hanlder在传送带模型中相当于什么呢?我们可以将handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。我们在传送带的一端放入货物的操作就相当于我们调用了handler的sendmessagexxx、sendemptymessagexxx或postxxx方法,这就把message对象放入到了消息队列messagequeue中了。当货物从传送带的另一端顺着管道划出时,我们就相当于调用了hanlder的dispatchmessage方法,在该方法中我们完成对message的处理。
下面重点介绍handler:
handler是暴露给开发者最顶层的一个类,其构建在thread、looper与messagequeue之上。
handler具有多个构造函数,签名分别如下所示:
- 1. publichandler()
- 2. publichandler(callbackcallback)
- 3. publichandler(looperlooper)
- 4. publichandler(looperlooper, callbackcallback)
第1个和第2个构造函数都没有传递looper,这两个构造函数都将通过调用looper.mylooper()获取当前线程绑定的looper对象,然后将该looper对象保存到名为mlooper的成员字段中。
第3个和第4个构造函数传递了looper对象,这两个构造函数会将该looper保存到名为mlooper的成员字段中。
第2个和第4个构造函数还传递了callback对象,callback是handler中的内部接口,需要实现其内部的handlemessage方法,callback代码如下:
public interface callback { public boolean handlemessage(message msg); }
handler.callback是用来处理message的一种手段,如果没有传递该参数,那么就应该重写handler的handlemessage方法,也就是说为了使得handler能够处理message,我们有两种办法:
1. 向hanlder的构造函数传入一个handler.callback对象,并实现handler.callback的handlemessage方法
2. 无需向hanlder的构造函数传入handler.callback对象,但是需要重写handler本身的handlemessage方法
也就是说无论哪种方式,我们都得通过某种方式实现handlemessage方法,这点与java中对thread的设计有异曲同工之处。
在java中,如果我们想使用多线程,有两种办法:
1. 向thread的构造函数传入一个runnable对象,并实现runnable的run方法
2. 无需向thread的构造函数传入runnable对象,但是要重写thread本身的run方法
所以只要用过多线程thread,应该就对hanlder这种需要实现handlemessage的两种方式了然于心了。
我们知道通过sendmessagexxx系列方法可以向消息队列中添加消息,我们通过源码可以看出这些方法的调用顺序,
sendmessage调用了sendmessagedelayed,sendmessagedelayed又调用了sendmessageattime。
handler中还有一系列的sendemptymessagexxx方法,而这些sendemptymessagexxx方法在其内部又分别调用了其对应的sendmessagexxx方法。
通过以下调用关系图我们可以看的更清楚些:
由此可见所有的sendmessagexxx方法和sendemptymessagexxx最终都调用了sendmessageattime方法。
我们再来看看postxxx方法,会发现postxxx方法在其内部又调用了对应的sendmessagexxx方法,我们可以查看下sendmessage的源码:
public final boolean post(runnable r) { return sendmessagedelayed(getpostmessage(r), 0); }
可以看到内部调用了getpostmessage方法,该方法传入一个runnable对象,得到一个message对象,getpostmessage的源码如下:
private static message getpostmessage(runnable r) { message m = message.obtain(); m.callback = r; return m; }
通过上面的代码我们可以看到在getpostmessage方法中,我们创建了一个message对象,并将传入的runnable对象赋值给message的callback成员字段,然后返回该message,然后在post方法中该携带有runnable信息的message传入到sendmessagedelayed方法中。由此我们可以看到所有的postxxx方法内部都需要借助sendmessagexxx方法来实现,所以postxxx与sendmessagexxx并不是对立关系,而是postxxx依赖sendmessagexxx,所以postxxx方法可以通过sendmessagexxx方法向消息队列中传入消息,只不过通过postxxx方法向消息队列中传入的消息都携带有runnable对象(message.callback)。
我们可以通过如下关系图看清楚postxxx系列方法与sendmessagexxx方法之间的调用关系:
通过分别分析sendemptymessagexxx、postxxx方法与sendmessagexxx方法之间的关系,我们可以看到在handler中所有可以直接或间接向消息队列发送message的方法最终都调用了sendmessageattime方法,该方法的源码如下:
public boolean sendmessageattime(message msg, long uptimemillis) { messagequeue queue = mqueue; if (queue == null) { runtimeexception e = new runtimeexception( this + " sendmessageattime() called with no mqueue"); log.w("looper", e.getmessage(), e); return false; } //注意下面这行代码 return enqueuemessage(queue, msg, uptimemillis); }
该方法内部调用了enqueuemessage方法,该方法的源码如下:
private boolean enqueuemessage(messagequeue queue, message msg, long uptimemillis) { //注意下面这行代码 msg.target = this; if (masynchronous) { msg.setasynchronous(true); } //注意下面这行代码 return queue.enqueuemessage(msg, uptimemillis); }
在该方法中有两件事需要注意:
- 1. msg.target = this
该代码将message的target绑定为当前的handler
- 2. queue.enqueuemessage
变量queue表示的是handler所绑定的消息队列messagequeue,通过调用queue.enqueuemessage(msg, uptimemillis)我们将message放入到消息队列中。
所以我们通过下图可以看到完整的方法调用顺序:
我们在分析looper.loop()的源码时发现,looper一直在不断的从消息队列中通过messagequeue的next方法获取message,然后通过代码msg.target.dispatchmessage(msg)让该msg所绑定的handler(message.target)执行dispatchmessage方法以实现对message的处理。
handler的dispatchmessage的源码如下:
public void dispatchmessage(message msg) { //注意下面这行代码 if (msg.callback != null) { handlecallback(msg); } else { //注意下面这行代码 if (mcallback != null) { if (mcallback.handlemessage(msg)) { return; } } //注意下面这行代码 handlemessage(msg); } }
我们来分析下这段代码:
1.首先会判断msg.callback存不存在,msg.callback是runnable类型,如果msg.callback存在,那么说明该message是通过执行handler的postxxx系列方法将message放入到消息队列中的,这种情况下会执行handlecallback(msg), handlecallback源码如下:
private static void handlecallback(message message) { message.callback.run(); }
这样我们我们就清楚地看到我们执行了msg.callback的run方法,也就是执行了postxxx所传递的runnable对象的run方法。
2.如果我们不是通过postxxx系列方法将message放入到消息队列中的,那么msg.callback就是null,代码继续往下执行,接着我们会判断handler的成员字段mcallback存不存在。mcallback是hanlder.callback类型的,我们在上面提到过,在handler的构造函数中我们可以传递hanlder.callback类型的对象,该对象需要实现handlemessage方法,如果我们在构造函数中传递了该callback对象,那么我们就会让callback的handlemessage方法来处理message。
3.如果我们在构造函数中没有传入callback类型的对象,那么mcallback就为null,那么我们会调用handler自身的hanldemessage方法,该方法默认是个空方法,我们需要自己是重写实现该方法。
综上,我们可以看到handler提供了三种途径处理message,而且处理有前后优先级之分:首先尝试让postxxx中传递的runnable执行,其次尝试让handler构造函数中传入的callback的handlemessage方法处理,最后才是让handler自身的handlemessage方法处理message。
希望本文对于大家理解android中的handler和消息循环机制有所帮助。