android下的线程,Looper线程,MessageQueue,Handler,Message等之间的关系,以及Message的send/post及Message dispatch的过程。
Looper线程
我们知道,线程是进程中某个单一顺序的控制流,它是内核做CPU调度的单位。那何为Looper线程呢?所谓Looper线程,即是借助于Looper和MessageQueue来管理控制流的一类线程。在android系统中,app的主线程即是借助于Looper和MessageQueue来管理控制流的,因而主线就是一个特殊的Looper线程。其实,不仅仅只有主线程可以用Looper和MessageQueue来管理控制流,其它的线程也一样可以。我们可以先看一下android源代码(Looper类,位置为frameworks/base/core/java/android/os/Looper.java)的注释中给出的一种Looper线程的实现方式:
可以看到,就是在线程的run()方法中,调用Looper.prepare()做一些初始化,然后创建一个Handler对象,最后执行Looper.loop()启动整个的事件循环。就是这么简单的几行代码,一个可以使用消息队列来管理线程执行流程的Looper 线程就创建好了。
那么上面的每一行代码,具体又都做了些什么呢?接着我们就先来看下,神秘的Looper.prepare()到底都干了些什么:
由这段代码可知,Looper.prepare()是一个静态方法,它做的事情就是简单地为当前的线程创建一个Looper对象,并存储在一个静态的线程局部存储变量中。在Looper的构造函数中又创建了一个MessageQueue对象。同时Looper会引用到当前的线程,并将一个表示执行状态的变量mRun设置为true。对于此处的线程局部存储变量sThreadLocal,可以简单地理解为一个HashMap,该HashMap中存放的数据其类型为Looper,key则为Thread,而每个线程又都只能获得特定于这个线程的key,从而访问到专属于这个线程的数据。
启动Looper线程就和启动普通的线程一样,比如:
同样是new一个对象,然后调用该对象的start()方法。
Android SDK本身也提供了一个class来实现LooperThread那样的机制,以方便在app中创建一个执行事件处理循环的后台线程,但提供的功能要完善得多,这个class就是HandlerThread。我们可以看一下这个class的定义:
它不仅有事件处理循环,还给app提供了灵活地设置HandlerThread的线程优先级的方法;事件循环实际启动前的回调(onLooperPrepared());获取HandlerThread的Looper的方法(这个方法对于Handler比较重要);以及多种停掉HandlerThread的方法——quit()和quitSafely()——以避免资源的leak。
Looper线程有两种,一种是我们上面看到的那种由app自己创建,并在后台运行的类型;另外一种则是android Java应用的主线程。创建前者的Looper对象需要使用Looper.prepare()方法,而创建后者的,则需使用Looper.prepareMainLooper()方法。我们可以看一下Looper.prepareMainLooper()的实现:
比较特别的地方即在于,此处调用prepare()方法传进去的quitAllowed参数为false,即表示这个Looper不能够被quit掉。其他倒是基本一样。整个android系统中,调用到prepareMainLooper()方法的地方有两个:
一处在SystemServer——system_server的主线程——的run()方法中,用于为system_server主线程初始化消息处理队列;另外一处在ActivityThread的run()方法中,自然即是创建android app主线程的消息处理队列了。
通过消息与Looper线程交互
那Looper线程的特别之处究竟在哪里呢?如前所述,这种线程有一个Looper与之关联,会使用消息队列,或者称为事件循环来管理执行的流程。那这种特别之处又如何体现呢?答案即是其它线程可以向此类线程中丢消息进来(当然此类线程本身也可以往自己的消息队列里面丢消息),然后在事件处理循环中,这些事件会得到处理。那究竟要如何往Looper线程的消息队列中发送消息呢?
回忆前面我们创建Looper线程的那段代码,不是有创建一个Handler出来嘛。没错,就是通过Handler来向Looper线程的MessageQueue中发送消息的。可以看一下使用Handler向Looper线程发送消息的方法。LooperThread的代码与上面的一样,向Looper线程发送消息的部分的写法:
使用Handler向一个Looper线程发送消息的过程,基本上即是,调用Message.obtain()或Handler.obtainMessage()获取一个Message对象->设置Message对象->调用 在Looper线程中创建的Handler对象的方法发送消息。
Handler究竟是如何知道要向哪个MessageQueue发送消息的呢?从前面的代码中,我们似乎看不到任何Handler与MessageQueue能关联起来的迹象。这究竟是怎么回事呢?这也是我们特别强调 要使用Looper线程中创建的Handler对象 来向该Looper线程中发送消息的原因。我们可以看一下Handler对象构造的过程:
Handler的构造函数共有7个,其中4个不需要传递Looper参数,3个需要。前面我们用的是不需要传递Looper对象的构造函数,因而现在我们主要关注那4个不需要传递Looper参数的。它们都是Handler(Callback callback, boolean async)不同形式的封装,而在这个构造函数中是通过Looper.myLooper()获取到当前线程的Looper对象,并与相关的MessageQueue关联起来的。这也是前面我们在实现Looper线程时,要在其run方法中创建一个public Handler的依据。
当然我们也可以使用那些能够手动传递Looper对象的构造函数,在构造Handler对象时,显式地使其与特定的Looper/消息队列关联起来。比如配合HandlerThread.getLooper()方法来用。
(这个地方我们看到, 创建Handler对象时,可以传递另外两个参数,一个是Callback,另一个是async,那这两个参数在这套消息队列机制中,又起到一个什么样的作用呢? 后面我们在来解答这个问题。)
Handler提供了两组函数用于向一个Looper线程的MessageQueue中发送消息,分别是postXXX()族和sendXXX()族,这两组函数的调用关系大体如下所示:
我们可以先看一下sendXXX()族消息发送方法:
这些方法之间大多只有一些细微的差别,它们最终都调用Handler.enqueueMessage()/MessageQueue.enqueueMessage()方法。(哈哈,这个地方,被我们逮到一个Handler类某贡献者所犯的copy-paste错误:仔细看一下sendMessageAtTime()和sendMessageAtFrontOfQueue()这两个方法,创建RuntimeException的部分,消息中都是”sendMessageAtTime()”。 )Handler实际的职责,并不完全如它的名称所示,在处理message外,它还要负责发送Message到MessageQueue。
注意,在Handler.enqueueMessage()中,会将 Message的target设为this,而且是不加检查,强制覆盖原来的值 。后面我们将会看到,Message的target就是Message被dispatched到的Handler,也是将会处理这条Message的Handler。这个地方强制覆盖的逻辑表明,我们用哪个Handler来发送一条消息,那么这条消息就将会被dispatched给哪个Handler来处理,消息的发送者即接收者。这使得 Handler那些obtainMessage()方法,及Message需要Handler参数的那些obtain()方法显得非常多余。通过这些方法会在获取Message时设置它的target,但这些设置又总是会在后面被无条件地覆盖掉 。
再来看一下MessageQueue.enqueueMessage()方法:
此处我们看到,MessageQueue用一个单向链表来保存所有的Messages,在链表中各个Message按照其请求执行的时间先后来排序。将Message插入MessageQueue的算法也还算清晰简洁,不必赘述。但此处, MessageQueue/Message的author为什么要自己实现一个单向链表,而没有用Java标准库提供的容器组件呢?是为了插入一条新的Message方便,还是仅仅为了练习怎么用Java实现单向链表?wake的逻辑后面我们会再来研究 。
向MessageQueue中发送消息的postXXX()方法:
这组方法相对于前面的sendXXX()族方法而言,其特殊之处在于,它们都需要传入一个Runnable参数,post的消息,其特殊之处也正在于,Message的callback将是传入的Runnable对象。这些特别的地方将影响这些消息在dispatch时的行为。
消息队列中消息的处理
消息队列中的消息是在Looper.loop()中被取出处理的:
在取出的msg为NULL之前,消息处理循环都一直运行。为NULL的msg表明消息队列被停掉了。在这个循环中,被取出的消息会被dispatch给一个Handler处理,即是msg.target,执行Handler.dispatchMessage()方法。
注意:Looper.loop()循环体的末尾,调用了从消息队列中取出且已经被dispatched处理的Message的recycle()方法,这表明Looper会帮我们自动回收发送到它的消息队列的消息。在我们将一条消息发送给Looper线程的消息队列之后,我们就不需要再担心消息的回收问题了,Looper自会帮我们很好的处理
接着来看Handler.dispatchMessage():
可以认为有三种对象可能会实际处理一条消息,分别是消息的Runnable callback,Handler的Callback mCallback和Handler对象本身。但这三种对象在获取消息的处理权方面有一定的优先级,消息的Runnable callback优先级最高,在它不为空时,只执行这个callback;优先级次高的是Handler的Callback mCallback,在它不为空时,Handler会先把消息丢给它处理,如果它不处理返回了false,Handler才会调用自己的handleMessage()来处理。
通常,Handler的handleMessage()方法通常需要override,来实现消息处理的主要逻辑。Handler的mCallback,使得开发者可以比较方便的将消息处理的逻辑和发送消息的Handler完全分开。
此处也可见post消息的特殊之处,此类消息将完全绕过Handler中用于处理消息的handleMessage() 方法,而只会执行消息的sender所实现的Runnable。
Sleep-Wakeup机制
还有一个问题,当MessageQueue中没有Messages时,Looper线程会做什么呢?它会不停地轮询,并检查消息队列中是否有消息吗?计算机科学发展到现在,闭上眼睛我们都能猜到,Looper线程一定不会去轮询的。Looper线程也确实没有去轮询消息队列。在消息队列为空时,Looper线程会去休眠,然后在消息队列中有了消息之后,再被唤醒。但这样的机制又是如何实现的呢?
Sleep-Wakeup机制所需设施的建立
我们从Sleep-Wakeup机制所需设施的建立开始。回忆前面的Looper构造函数,它会创建一个MessageQueue对象,而Sleep-Wakeup机制所需设施正是在MessageQueue对象的创建过程中创建出来的。(在android消息队列机制中,消息取出和压入的主要逻辑都在MessageQueue中完成,MessageQueue实现一个定制的阻塞队列,将等待-唤醒的逻辑都放在这个类里想必也没什么让人吃惊的地方吧。)我们来看MessageQueue的构造函数:
这个方法调用nativeInit()方法来创建Sleep-Wakeup机制所需设施。来看nativeInit()的实现(在frameworks/base/core/jni/android_os_MessageQueue.cpp):
可以看到,nativeInit()所做的事情,就是创建一个NativeMessageQueue对象,在NativeMessageQueue的构造函数中,会来创建一个Looper对象。与Java层的Looper对象类似,native层的这种Looper对象也是保存在线程局部存储变量中的,每个线程一个。接着我们来看Looper类的构造函数和Looper::getForThread()函数,来了解一下,native层的线程局部存储API的用法(Looper类的实现在frameworks/native/libs/utils/Looper.cpp):
关于pthread库提供的线程局部存储API的用法,可以看到,每个线程局部存储对象,都需要一个key,通过pthread_key_create()函数创建,随后各个线程就可以通过这个key并借助于pthread_setspecific()和pthread_getspecific()函数来保存或者获取相应的线程局部存储的变量了。再来看Looper的构造函数。它创建了一个pipe,两个文件描述符。然后设置管道的两个文件描述属性为非阻塞I/O。接着是创建并设置epoll实例。由此我们了解到,android的消息队列是通过epoll机制来实现其Sleep-Wakeup机制的。
唤醒
然后来看当其他线程向Looper线程的MessageQueue中插入了消息时,Looper线程是如何被叫醒的。回忆我们前面看到的MessageQueue类的enqueueMessage()方法,它在最后插入消息之后,有调用一个nativeWake()方法。没错,正是这个nativeWake()方法执行了叫醒Looper线程的动作。那它又是如何叫醒Looper线程的呢?来看它的实现:
它只是调用了native层的Looper对象的wake()函数。接着再来看native Looper的wake()函数:
它所做的事情,就是向管道的用于写的那个文件中写入一个“W”字符。
休眠
Looper线程休眠的过程。我们知道,Looper线程在Looper.loop()方法中,不断地从MessageQueue中取出消息,然后处理,如此循环往复,永不止息。不难想象,休眠的时机应该是在取出消息的时候。Looper.loop()通过MessageQueue.next()从消息队列中取出消息。来看MessageQueue.next()方法:
值得注意的是上面那个对于nativePollOnce()的调用。wait机制的实现正在于此。来看这个方法的实现,在native的JNI code里面:
继续追Looper::pollOnce()的实现(在frameworks/native/libs/utils/Looper.cpp):
它通过调用epoll_wait()函数来等待消息的到来。
HandlerThread的退出
HandlerThread或使用Looper/MessageQueue自定义的类似东西,必须在不需要时被停掉。如前所见,每次创建MessageQueue,都会占用好几个描述符,但在Linux/Android上,一个进程所能打开的文件描述符的最大个数是有限制的,大多为1024个。如果一个app打开的文件描述符达到了这个上限,则将会出现许多各式各样的古怪问题,像进程间通信,socket,输入系统等很多机制,都会依赖于类文件的东西。同时,HandlerThread也总会占用一个线程。这些资源都是只有在显式地停掉之后才会被释放的。
我们来看一下HandlerThread的退出机制。先来看一个使用了HandlerThread,并适时地退出的例子,代码在frameworks/base/core/java/android/app/IntentService.java:
在这个例子中,Service的onCreate()方法创建了HandlerThread,保存了HandlerThread的Looper,然后在Service的onDestroy()方法中停掉了Looper,实际上也即是退出了HandlerThread。
来看一下Looper停止方法具体的实现:
Looper有提供两个退出方法,quit()和quitSafely(),这两个方法都会阻止再向消息队列中发送消息。但它们的主要区别在于,前者不会再处理消息队列中还没有被处理的所有消息,而后者则会在处理完那些已经到期的消息之后才真的退出。
由前面我们对Looper.loop()方法的分析,也不难理解,MessageQueue退出即Looper退出。此处也是直接调用了MessageQueue.quit()方法。那我们就来看一下MessageQueue的quit()方法:
主要做了几个事情:
- 第一,设置标记mQuitting为true,以表明HandlerThread要退出了;
- 第二,根据传入的参数safe,删除队列里面适当类型的消息;
- 第三,调用nativeWake(mPtr),将HandlerThread线程唤醒。
我们都知道,Java里面是没有办法直接终止另外一个线程的,同时强制中止一个线程也是很不安全的,所以,这里也是只设置一个标记,然后在MessageQueue.next()中获取消息时检查此标记,以在适当的时候退出。MessageQueue.next()里中止消息处理循环的,主要是下面这几行:
然后在 MessageQueue.dispose()方法中完成最终的退出,及资源清理的工作:
android中消息队列机制,大体如此。
Done。