MessageQueue的工作原理
MessageQueue是消息队列,但内部结构并不是用队列实现的,而是采用单链表来存储消息列表的;主要包含两个操作:插入enqueueMessage()和读取next();enqueueMessage()把一个Message对象插入消息队列中,具体插入方式与单链表的插入一致;next()从消息队列中读取Message,读取成功后就会把Message从消息队列中删除,next()是一个阻塞的操作,当MessageQueue中没有消息的时候会一直处于阻塞状态直到有新得消息加入。
Looper的工作原理
Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,所以在Handler的构造函数中通过Looper.myLooper()来获取Looper;而myLooper()的主要工作是通过ThreadLocal的get()方法获取已经存储的Looper。
Looper.myLooper()那么Looper是什么时候被set()进去ThreadLocal的呢?也就是说Looper是如何被创建的?
Looper的创建主要通过Looper.prepare()方法,通过ThreadLocal的set()方法把创建的Looper存储起来;ThreadLocal能保证不同线程对同一个ThreadLocal实例的读写操作仅限于自身线程内,不会互相干扰,因为每一个线程Thread都对应着唯一的Looper,通过ThreadLocal能对指定线程的Looper进行读取操作。
同时创建Looper的时候会同时创建MessageQueue,quitAllowed为退出的标志位。
Looper构造方法补充:Looper也提供了prepareMainLooper()方法,主要是给主线程ActivityThread创建Looper用的,最终也还是调用了prepare()方法,系统会自动调用此方法为主线程创建Looper。
Looper.prepareMainLooper()Looper创建之后通过Looper.loop()开启消息循环队列,loop()主要实现了一个for死循环,通过queue.next()不断从MessageQueue中读取Message,此处的next()是一个阻塞操作,如果消息队列没有消息将会一直阻塞,导致loop()也会产生阻塞;通过next()读取的Message会交给msg.target.dispatchMessage(msg)进行处理,msg.target本身就是发送这条消息的Handler对象,dispatchMessage()最终还是调用了handleMessage(msg)进行消息处理,所以最终还是绕了一圈,Handler把消息发送给了MessageQueue,Looper再把消息分发给Handler进行处理;
Looper.loop() Handler的handleMessage()补充:Looper.loop()里面执行了for死循环,那么死循环是什么时候被终止的呢?当MessageQueue.next()返回了null的时候退出死循环;当Looper.quit()或者Looper.quitSafely()方法被调用时,就会调用MessageQueued的quit()方法来通知消息队列退出,此时MessageQueue.next()就会返回null。在子线程中如果手动创建了Looper,必须在不需要使用的时候调用quit()方法终止消息队列的循环,否则该子线程会一直处于阻塞状态。
Looper.loop() Looper.quit()
Handler的工作原理
Handler主要有两个作用:发送消息和处理消息,发送消息可以通过post(Runnable r)或者sendMessage(Message msg)实现,post()最终还是调用了sendMessage();sendMessage()主要的作用是将Message通过enqueueMessage()插入消息队列MessageQueue中;Looper通过loop()开启消息循环不断从MessageQueue中读取Message,然后调用dispatchMessage()方法处理消息,最终交给了Handler的handleMessage()进行处理。
Handler、Looper、MessageQueue三者的关系可以通过Handler的构造方法得出,在Handler的构造方法中通过Looper.myLooper()获取一个Looper,再从Looper对象中获取MessageQueue。
为什么主线程不会因为Looper.loop()里的死循环而报ANR
因为主线程的生命周期跟Looper是一致,如果没有Looper执行死循环,那么主线程就会运行一段时间后自动退出,无法与用户继续进行交互。Looper.loop()只会阻塞主线程,而不会卡死主线程,当主线程没有消息需要处理时就会阻塞主线程,当有消息进来时就会唤醒主线程。
Message的消息缓存池
我们在获取Message的时候建议一般都有obtainMessage()方法,而不是直接通过new重新创建一个Message,obtainMessage()内部调用了obtain()方法。Message内部维护了一个消息缓存池,主要通过recycle()方法来缓存使用过的Message,recycle()又会调用recycleUnchecked()方法,该方法主要对Message做一些清除标记的操作,比如说what和arg1置为0等,并且如果缓存池的Message数量不超过最大缓存数量,则把清除标记后的Message加入到单链表的头部。获取消息缓存池的Message时通过obtain()方法获取,如果缓存池为null则创建新的Message对象,否则取出缓存池中第一个Message对象并返回。
补充
(1)在子线程中创建Handler必须先创建Looper,再手动开启消息循环:Looper.prepare() -> new Handler() -> Looper.loop();
(2)线程默认是没有Looper的,使用Handler时必须为线程创建Looper,由于Android中的主线程ActivityThread被创建时就会初始化Looper,所以Handler可以直接创建在主线程中;
(3)Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,所以使用Handler的时候必须为当前线程创建Looper或者在一个默认有Looper的线程中去使用;
(4)为什么在Android中不允许在子线程中访问UI呢?因为Android的UI控件不是线程安全的,如果在多线程并发访问的环境下可能会导致UI控件发生不可预期的状态;
参考
《Android开发进阶:从小工到专家》 第3章 3.1 Android中的消息机制
《Android开发艺术探索》第10章 Android中的消息机制
网友评论