Android系统源码分析--消息循环机制
上一章我们讲解SystemServer时涉及到了消息机制,因此这一章我们先介绍一下消息循环机制,帮助大家弄清楚消息循环的原理,有助于代码的编写和优化。
Looper-Message-MessageQueue-Handler消息处理机制
在Android系统有两个通信机制,一个是Binder,一个是消息机制,前者是跨进程通信,后者是进程内部通信。消息通信主要包括几个部分:
- 消息发送者和处理者:Handler
- 消息循环器:Looper
- 消息队列:MessageQueue
- 消息:Message
我们先看一个时序图:
图中,1-11步是Looper的准备过程,12-17步是获取消息,处理消息,回收消息的循环过程。
下面是一张消息循环过程图,图片来自网络博客(blog.mindorks.com),Looper会通过loop方法不断从消息队列去取消息,然后交给handler处理,处理完成就回收消息,要注意的是只有一个looper,但是可能有多个handler:
1、Looper
Looper是一个循环器,通过里面的loop方法不断去取消息,发送给Handler进行处理。根据上面时序图以及SystemServer启动代码我们开始分析Looper的调用过程:
1 | private void run() { |
我们先看Looper.prepareMainLooper方法:
1 | /** |
上面有段注释,我翻译一下,就是:初始化当前线程作为一个looper,并把它标记为应用的主looper。这个looper是被Android环境(系统)创建的,因此你不需要自己调用这个方法。也就是系统创建了这个looper,你不需要再创建了。我们接着看里面的内容,首先调用了prepare方法,需要注意的是Looper.prepare()在每个线程只允许执行一次,该方法会创建Looper对象:
1 | static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); |
上面的ThreadLocal是声明在类里的,并且是静态的,因此,随着类创建了该对象,get方法是获取Looper的,如果能获取到,则抛出异常,也就是确保当前线程只有一个Looper。如果是空,那么我们创建一个Looper放到里面去。
我们先看一下ThreadLocal:线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。(来自Android消息机制1-Handler(Java层))我们看一下它的set和get方法:
ThreadLocal的set方法:
1 | public void set(T value) { |
如果map不为空,则以键值对放入进行存储,此处map不是HashMap,而是其他,这里不详细解释。如果map为空,则通过下面代码创建map:
1 | void createMap(Thread t, T firstValue) { |
ThreadLocal的get方法:
1 | public T get() { |
我们看到获取的时候也是根据当前线程去获取的。因此每个线程会保存一个Looper。
我们接着看Looper的构造函数有哪些操作,也就是创建Looper做了哪些处理:
1 | private Looper(boolean quitAllowed) { |
首先是创建了MessageQueue对象,接着创建一个线程,也就是当前线程,currentThread是一个native方法,我们不再分析,我们看一下MessageQueue创建做了哪些事情:
1 | MessageQueue(boolean quitAllowed) { |
上面的nativeInit是调用的jni,我贴一下代码,不再解释:
我们回到prepareMainLooper方法接着看,如果sMainLooper不为null,则抛出异常,提示sMainLooper已经创建了,如果是null,那么调用myLooper方法回去sMainLooper:
1 | public static Looper myLooper() { |
其实这个get方法就是我们上面new完Looper放进去的,到此prepareMainLooper就完成了,相关信息也准备好了。接下来就是调用Looper.loop方法,方法下面是一个异常,怎么样才能保证异常不会抛出,就是loop方法永远执行不完。是不是只有我们接着看代码:
1 | public static void loop() { |
首先是通过myLooper方法获取Looper,如果为空,则抛出异常,提示还没有调用Looper.prepare方法,如果不为空,则通过looper获取MessageQueue对象,然后进入for循环,因为for语句中没有条件,因此该for循环为无限循环,在这个循环中有三件事,一个是获取消息队列中的下一个消息,然后处理该消息,最后处理完消息,回收消息。这三个过程就是Looper的主要作用:取消息,处理消息,回收消息。
2.Message
Message是整个循环中信息的载体,它是一个链表结构,关于链表结构可以参考下面文章:
Android自助餐–Handler消息机制完全解析–系列
链表数据结构图解 和 代码实现
基本数据结构:链表(list)
链表结构之单链表
我们看个图:
上面就是一个示例图,每个Message中都有一个后面Message的引用next,链表最后一个next为空,sPool是第一个Message。但是每个Message的内存地址不是挨着的,这样可以占用零碎的内存。
我们先来看Message包含的参数:
1 | public int what; |
前四个参数很熟悉,不再解释,flags是一个标签,表示是否正在使用;when是处理消息的时间;target就是我们上面提到的Handler;next是下一个Message的引用;sPool是一个静态变量,说明只有一个,其实这个是消息队列的头消息;sPoolSize是消息队列中消息个数;MAX_POOL_SIZE是消息队列最大消息数量。
Message中有多个用来获取Message对象的obtain复写方法。因为后面的obtain方法都是通过第一个obtain方法获取Message对象的,因此我们只看第一个参数为空的方法:
1 | /** |
系统提示尽量用这种方法获取Message对象,避免创建大量新的对象,其实也可以通过Handler来获取Message,这个我们在将Handler时候再讲。
在上面Looper中我们讲到最后消息处理完后需要回收,这个回收方法recycleUnchecked也在Message类中:
1 | /** |
消息回收时,将对应消息的标签设置为使用中,其他标签设置为空或者默认值,如果消息队列没有超过最大值,那么将sPool赋值给next,将这个Message赋值给sPool,消息队列长度加一。也就是将处理完的消息清空,重新放回消息队列等待使用。
3.Handler
Handler是发送消息和处理消息的工具。我们先看构造方法:
1 | public Handler(Callback callback, boolean async) { |
Handler中的looper是获取当前线程中的looper,looper不能为空,MessageQueue也是looper中的。
首先是发送消息,发送消息的方法很多,我们看一张图:
我们看到Handler中有多个发送消息的方法,但是最终调用了enqueueMessage方法:
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
从代码我们可以看到msg.target就是Handler,也就是在这里进行赋值的,然后是调用MQ(MessageQueue)的enqueueMessage方法,这个方法是添加消息队列的,具体内容我们后面再讲。因此,发送消息就是将消息添加到消息队列。我们前面还讲过获取Message对象可以通过Message中的obtain方法,也可以通过Handler中的方法,我们先看一张图:
Handler是通过多个复写方法obtainMessage来获取Message的,只是传入参数不同,我们看一个没有参数的方法代码:
1 | public final Message obtainMessage(){ |
我们看到其实还是调用了Message.obtain方法,并且传入了this,也就是Handler,通过Message.obtain方法将Handler赋值给Message中的target。从这,我们基本对Handler与Message的关系基本明确了,获取Message的方法我们也完全知道了,因此我们在以后用的时候不需要再去new一个Message对象,而是通过obtain方法去获取,如果有就不需要new了,如果没有系统会自己去创建。
4.MessageQueue
MessageQueue是消息队列,其实是管理消息链表的。它主要功能是取出消息–next方法,将消息加入队列–enqueueMessage方法。
我们先看加入消息队列方法enqueueMessage,也就是Handler中发送消息后加入队列的方法:
1 | boolean enqueueMessage(Message msg, long when) { |
插入消息队列有两种情况,一种是消息队列处于空闲状态,直接将消息放在消息队列前面,可能需要唤醒主线程,另一种是消息队列处于忙碌状态,就不需要唤醒,而是根据消息处理时间将消息插入到消息队列的对应位置中。
第一种状态:插入队列头
1 | if (p == null || when == 0 || when < p.when) { |
if语句的三个条件是:一、队列为空,二、插入消息需要立即处理,三、插入消息处理时间比消息队列头消息早,这三个条件说明消息队列处于闲置状态,此时要把消息放置到消息队列头部,即将插入消息的next指向消息队列的头p,然后将消息队列要处理的消息指向插入消息对象,最后判断是否需要唤醒,如果队列阻塞则需要唤醒,否则不需要。
第二种状态:插入队列中间或者后面,这种情况比较复杂
1 | needWake = mBlocked && p.target == null && msg.isAsynchronous(); |
因为不是在队列头,所以需要for循环去查找应该的位置,首先将第一个消息用prev进行缓存,然后当前消息引用指向下一个消息对象,依次类推,直到p == null(到队列最后),或者当前消息触发时间小于后面这个消息的触发时间,停止循环,说明找到了位置,此时执行最后两行代码,也就是将当前出入消息的next指向p,也就是,如果p==null,则说明插入到最后一个,如果不为空,则插入到p前面,然后将前一个prev的next指向插入的消息,此时插入成功。最后的if语句中如果需要唤醒消息队列,则调用底层方法nativeWake唤醒消息队列开始循环。到此,消息插入就讲完了。我们上面说到loop方法是通过MessageQueue的next方法取出消息,那么下面我们看一下next方法是怎么取出消息的。
1 | Message next() { |
mPtr是MessageQueue初始化的时候通过调用底层方法获取的底层NativeMessageQueue的对象,如果底层不能初始化则返回0,如果可以初始化返回对象地址,此处判断,如果没有初始化也就没有底层的NativeMessageQueue对象,因此返回null。紧接着开始for循环,开始遍历消息队列,查找需要处理的消息,在这里,如果消息队列为空,或者没有需要立即处理的消息都要使线程开始等待。接着调用nativePollOnce方法来查看当前队列中有没有消息,传入参数nextPollTimeoutMillis表示要等待的时间,如果nextPollTimeoutMillis为0则说明不需要等待。接着获取当前时间now,初始化prevMsg来缓存消息,初始化msg来缓存当前消息mMessages,下面if语句判断消息不为空但target为空,则说明该消息为“同步分隔栏”(关于“同步分隔栏”请参看聊一聊Android的消息机制一文),如果该消息为同步分隔栏,则后面的同步消息都不会被查找,只能查找异步消息来处理,也就是do-while语句中的代码,如果没有同步分割栏或者找到了后面的异步消息(可能没有),则接着判断。
如果消息不为空,则还有消息,开始判断时间,如果当前时间小于下一个消息的执行时间,说明还需要等待,那么计算需要等待的时间nextPollTimeoutMillis,如果当前时间不小于当前消息执行时间时,并且前一个消息prevMsg不为空,说明出现了“同步分隔栏”,也就是执行了do-while代码,do-while执行完,说明找到了异步消息或者遍历完整个队列没有异步消息,如果有异步消息,此时prevMsg.next = msg.next,也就是跳过同步消息,将异步消息msg.next赋值给prevMsg.next,然后将取出的msg的next赋值为null,因为要处理了,所以不再指向后面队列的消息对象,然后将msg设置为正在使用,并且返回,如果prevMsg为空,则说明没有出现“同步分隔栏”,此时将当前消息mMessages的下一个消息赋值给mMessages,然后将msg.next设置为空,就是不再引用,然后设置为正在使用,返回该消息。
如果消息为空,则nextPollTimeoutMillis = -1,说明没有消息了,则接着向下执行,如果退出消息队列,则说明所有消息都执行完了,最终调用nativeDestroy方法,如果不退出消息队列,则要进入等待状态。如果第一次进入,并且当前消息为空或者消息不为空,但是处于等待状态,那么要获取IdleHandler个数,如果小于等于0,则说明没有IdleHandler运行,调用continue执行下一次循环,如果IdleHandler个数大于0,但是等待的Handler(mPendingIdleHandlers)为空,则要创建IdleHandler数组,将mIdleHandlers放入数据,然后for循环调用每个IdleHandler的queueIdle方法,如果这个方法返回false,则从数组移除这个对象,否则保留改对象,下次空闲继续执行,最后将pendingIdleHandlerCount置为0,nextPollTimeoutMillis置为0,继续下一次循环。
那么到此,整个循环就讲完了,因为不懂C++代码,所以底层没法分析,只能分析framework层代码,说了很多还是需要自己对比代码多理解。
5.Handler的使用方法
我们在使用Handler的时候软件会提示我们有问题,那么到底该怎么写Handler呢,我从Stack Overflow找到了答案,在这就分享一下:
首先,定义一个静态MxHandler继承Handler,里面使用弱引用:
1 | public abstract class MxHandler<T> extends Handler { |
然后我们再写具体的MyHandler继承这个MxHandler:
1 | private static final class MyHandler extends MxHandler<HandlerDemo> { |
这样我们在Activity中使用是不会出现内存泄漏之类的错误。
代码地址:
直接拉取导入开发工具(Intellij idea或者Android studio)
参考:
android的消息处理机制(图+源码分析)——Looper,Handler,Message
Android中Thread、Handler、Looper、MessageQueue的原理分析
Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
Android应用程序消息处理机制(Looper、Handler)分析
注
Android开发群:192508518
微信公众账号:Code-MX
注:本文原创,转载请注明出处,多谢。