Android消息机制:Looper、Handler、MessageQueue分析
Android消息机制:Looper、Handler、MessageQueue分析
0.概述
Android的消息处理机制是围绕消息队列(MessageQueue)来实现的,一个线程拥有了一个消息队列之后,就可以进入到一个消息循环中,同时其他线程以及线程本身可以往这个消息队列(后面会讲到是通过Handler)发送消息,消息里面可以携带很多信息,以便目标线程提供消息循环接收到这个消息后能够做一些指定的操作。这样一个线程的Life Cycle站在“消息机制”的角度可以分为创建消息队列和进入消息循环两个阶段,其中消息循环阶段又分为发送消息和接收消息两个子阶段,它们是交替进行的。Android系统主要通过MessageQueue、Looper、Handler三个类来实现Android应用程序来实现消息处理机制的,其中每个类的功能如下:
- MessageQueue:消息队列,管理线程的消息
- Looper:创建消息队列(Looper.prepare()),进入消息循环(Looper.loop())
- Handler:用来发送和接收消息
分为四个部分
Android应用程序线程中的消息队列是使用一个MessageQueue来描述的,这个MessageQueue在一个线程中是只有一个的,它可以通过调用Looper的静态方法prepareMainLooper()或者prepare()来创建,前者是为UI线程创建一个消息队列,后者是为非UI线程创建一个消息队列。
那么到此我们就涉及到了两个类:MessageQueue和Looper,由于Android消息传递机制涉及到底层的C++代码,还涉及到Linux的系统调用(e_poll机制)、管道读写,因此刚才说到的两个类还有对应的两个Native类
下面是一个关系示意图:
# Looper类有一个final的MessageQeue型的成员变量 mQueue,它持有一个对应的MessageQueue对象
其中Looper里面还有静态方法prepare(),prepareMainLooper(),loop()等主要的方法,这些方法都和创建消息队列进行消息循环相关。
# MessageQueue类中有一个private的long型的mPtr变量,这个变量存放着framework层用C++代码编写的NativeMessageQueue对象的地址值,同时它还有一个private的Message类型的mMessages变量,这个变量是该MessageQueue的一个消息链表的一个引用,链表的节点类型是Message,这个消息链表是按照Message的处理时间从小到大排列的。
其中MessageQueue类中主要的方法有nativeInit()、nativePollOnce()、equeueMessage()、nativeWake()方法。
# 在C++层,Java层的MessageQueue对应一个NativeMessageQueue对象,这个NativeMessageQueue中也存在一个Looper(Native)类型的 mLooper成员变量,还有pollOnce()、wake()等主要方法
#Looper(Native)类中有两个管道文件描述符的变量用来进行该线程的管道的读写,int型的 mWakeReadPipedFd 和 mWakeWritePipedFd。
还有pollOnce()、wake()方法。
简单的关系示意图:Looper ----> MessageQueue -----> NativeMessageQueue -----> Looper(Native)
1.1 Looper的部分方法的部分源代码解析:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
其中sThreadLocal是Looper类的一个ThreadLocal类型的一个final静态成员变量,可以将它理解为一个线程的局部变量,或者一个HashMap,它有get 和 set的方法,关于set的方法,文档给出的解释是这样的:set the value for currentThread.
2.消息循环--建立循环
2.1Looper.loop() ----> MessageQueue.next()[如果没有消息的时候,也许会阻塞] ----->MessageQueue.nativePollOnce() -----> MessageQueue.pollOnce() -----> NativeMessageQueue.pollOnce() -------> Looper(Native).pollInner()[阻塞在 epoll_wait(mEpollFd,eventItems,EPOLL_MAX_EVENTS,timeoutMillis)] ------> Looper(Native).awoken()[最终就是调用read(mWakeReadFd,buffer,sizeof(buffer))函数,这里可以看出线程根本不关心写入到与它关联的管道的数据是什么,只是简单的将这些数据读出来而已,清理管道的数据,以便于下一次的消息循环。]
其中timeoutMillis这个参数很重要,这个参数的意思就是在文件描述符没有发生IO事件的时候,当前线程在epoll_wait函数中进入睡眠等待的时间,0位不等待,-1为无限等待,直到其他线程将其唤醒。
这个参数在MessageQueue中也存在,即MessageQueue的next()方法中的局部变量nextPollTimeoutMillis
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
3.消息循环--发送消息
Handler:
Handler内部拥有两个成员变量一个是MessageQueue类型的mQueue和mLooper
还有两个重要的方法:sendMessage(),handleMessage()其中,handleMessage()是在消息对应的目标线程中被调用的。而sendMessage()确实有可能是在另外的线程。
Handler的某个构造函数如下:
public Handler(Callback callback, boolean async) {
。。。。。。。。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
。。。。。。。。。
}
其中可以看到mLooper是通过Looper.myLooper()得来的,mQueue是通过mLooper的成员变量mQueue得到的。
有了这个两个重要的成员变量mLooper和mQueue之后,我们就可以开始发送和接收消息了。
Handler.sendMessage() -----> MessageQueue.enqueueMessage() ------> MessageQueue.nativeWake() -----> NativeMessageQueue.wake() -----> Looper(Native).wake()[就是要唤醒处理消息的线程,前面提到了该线程因为调用了epoll_wait而沉睡了,因此现在需要向该线程的管道写入一点数据,解除epoll_wait的阻塞,然而写的什么数据却不重要,因为只要达到唤醒线程的目的即可,这样线程被唤醒以后,就可以继续执行Looper的loop(),不断的去调用MessageQueue的next()方法,不断的去处理消息队列中的消息了,这里写入的数据是这样的write(mWakePipeFd,"W",1),就是写入了一个W而已]
4.消息循环--接收消息
如果别的线程(可以是处理消息的线程)调用了sendMessage(),那么到C++层最终就会导致想管道写入了一个W,这样就可以唤醒目标线程,导致目标线程继续从消息队列获取消息,然后分发消息,调用handler的handleMessage()方法,这样,就完成了一个线程之间的消息传递了。
某一个线程调用了sendMessage -----> 接着导致向管道写入了W,线程被唤醒 -----> Looper.loop()(继续执行) -----> Handler.dispatchMessage() -------> Handler.handlerMessage();[这些方法均是在目标线程中的方法栈中运行]
5.其他
在MessageQueue类中还有一个重要的成员变量即:private IdleHandler[] mPendingIdleHandlers;
IdleHandler是MessageQueue类中定义的一个接口
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
在MessageQueue的next()方法中,如果队列中没有可以处理的消息的时候:会执行下面这段代码:
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
依次调用注册到MessageQueue中的实现了IdleHandler接口的对象的queueIdle()的方法,已通知他们此时消息队列是空的,线程是空闲的。如果需要在线程空闲的时候做点其他的事情而不是傻傻的睡眠的话,需要向MessageQueue中注册实现了接口的对象。
idler.queueIdle()返回一个bool值表示是不是你实现的IdleHandler只被调用一次。调用MessageQueue的addIdleHander(IdlerHandler idleHandler)便可以注册.
6.总结:
- Android应用程序的消息机制(以下简称消息机制)主要涉及Java层的三个类Looper MessageQueue 和 Handler,C++层NativeMessageQueue和Looper(Native)。
- 消息机制的主要用途是保证线程之间消息传递的正确性和准确性。因为我们都知道Android中UI线程和非UI线程之间经常是需要通信的。
- 消息机制的framework层的实现是利用管道的读写事件来完成线程的睡眠和唤醒,这里涉及到Linux的epoll机制,这个有兴趣的可以深入了解。另外消息机制中的MessageQueue还提供了空闲回调机制,当队列消息为空的时候,会回调注册到MessageQueue的订阅者(注册者),以便于充分利用线程空闲的时间来完成其他操作。
7.Demo
https://github.com/Spground/DemoMessageQueue-Handler-Looper