从源码解析Android异步消息处理机制(一)
前言:本篇纯属个人学习笔记,故摘录《Android开发艺术探索》大量语句,特此声明
Android的消息机制主要指Handler的运行机制,Handler的运行又和MessageQueue、Looper这两者紧密相关。简单来说,异步消息处理线程启动后,Looper会以无限循环的形式去MessageQueue查找是否有新的消息,如果有的话就回调相应的消息处理函数,否则就一直等待着(阻塞)。
2.源码解析
MessageQueue
MessageQueue(Android.os.MessageQueue类),消息队列。它的内部存储了一组消息,以队列的形式对外提供插入和读取的工作(读取操作本身会伴随着删除操作)。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。插入和读取对应的方法分别是enqueueMessage()和next()。
我们主要看next()方法(而enqueueMessage()方法有兴趣的道友可自行查看源码)
1 | Message next() { |
可以发现next()方法是一个无限循环的方法,如果MessageQueue没有消息,那么next()会一直阻塞在那里,当有新消息时,next方法会返回这条消息并将其从单链表中移除。
Looper
Looper(Android.os.Looper类),消息循环。由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去MessageQueue查找是否有新的消息,如果有的话就回调相应的消息处理函数,否则就一直等待着(阻塞)。Looper中还有个特殊的概念——ThreadLocal,它并不是线程,它的作用是在每个线程中存储数据。
我们直接上源码开撸:
首先看一下它的构造函数:
1 | private Looper(boolean quitAllowed) { |
我们可以看到在构造方法中它会创建一个MessageQueue即消息队列,然后通过变量mThread将当前线程保存起来。
我们知道,Handler的运行需要Looper,没有Looper的线程会报错,那如何为一个线程创建Looper?Looper.prepare()这个方法就是提供了这个功能:
1 | private static void prepare(boolean quitAllowed) { |
在Looper.prepare()方法中sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量,通过sThreadLocal.set()将一个Looper实例放入了ThreadLocal,中间的if判断则说明了Looper.prepare()方法不能被调用两次,一个线程只能有一个Looper实例。
Looper最重要的一个方法是loop(),只有调用了loop后,消息循环机制才会真正地起作用:
1 | /** |
我们一行行看代码,在myLooper()方法中返回了sThreadLocal存储的Looper实例,如果me为null,则抛出异常,也就是说loop()必须在prepare()方法之后运行。接着拿到该looper实例中的mQueue(具体可见Looper的构造函数)。再接下来就进入一个for(;;)死循环,而唯一跳出循环的方式是MessageQueue的next()方法返回了null。紧接着把消息交给msg的target的disptchMessage(msg)方法去处理。而msg的target就是handler对象,下面会进行分析。最后一句msg.recycle()则释放消息占据的资源。
接着我们分析一下MessageQueue.next()什么时候会返回null?我们先看看Looper的quit()方法:
1 | public void quit() { |
也就是说当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit()或quitSafely()方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。
小结
Looper主要作用:
- 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。(具体可见Looper的构造函数)
- looper()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage()去处理。