通常说到Android的消息机制,一般也是说的Android的Handler的运行机制,而Hander的运行过程,可以说是我们App项目运行的一个基础,app运行过程中的很多事件,如Activity的生命周期回调,线程之间的通信,都是需要通过Handler来处理的。
这里有一张Handler的运行模型图。
1.以Handler的sendMessage方法为例,当发送一个消息后,会将此消息加入消息队列MessageQueue中。
2.Looper负责去遍历消息队列并且将队列中的消息分发给对应的Handler进行处理。
3.在Handler的handleMessage方法中处理该消息,这就完成了一个消息的发送和处理过程。
这里从图中可以看到参与消息处理有四个对象,它们分别是Handler, Message, MessageQueue,Looper。
现在来看一下Handler的前世今生
1 当我们当App启动的时候,首先就会进入ActivityThread的main方法里面,这里也是我们Android程序的入口。
在这个main方法里面,我们会创建app中全局唯一的Looper对象和MessageQueue对象。
这些对象都创建好之后,我们的looper对象就开始进入loop状态,就是无限循环状态,不停的从Message Queue中获取Message对象,如果有需要处理的Message对象,就交给Handler对象处理,如果没有,那就把自己阻塞起,一直等到有新的Message对象被添加到MessageQueue中才被唤醒。
2 当我们需要在自己的应用中使用Handler的时候,我们可以之间使用UI线程中的Handler对象,也可以继承Handler对象,重写handlerMessage方法。因为我们的Looper对象是全局对象,生命周期和app一样,所以在使用的时候,这里需要注意避免因为Handler的使用不当造成内存泄露。这里推荐使用内部静态类加若引用的方式。
3 使用Handler发送消息,不管Handler是在哪里发送消息,最后消息都会被放入到我们的全局消息队列里面,等待处理。
4最后是处理消息。Looper会不停的从消息队列里面去取Message对象。然后调用handler的dispatchMessage方法,将消息丢给Handler的handlerMessage方法处理。
以上就是Handler的一个工作流程。从Handler创建,到发送消息到消息队列,然后Looper不断的循环从消息队列中再把消息取出来,最后再交给Handler的handlerMessage方法处理。通过这样的一个循环反复,来处理我们App里面的相关事件。
消息阻塞和延时
Looper 的阻塞主要是靠MessageQueue 来实现的,在next()进行阻塞,在enqueueMessage进行唤醒。主要依赖native 层的Looper 依靠epoll 机制进行的。
阻塞和延时,主要是next()中nativePollOnce(ptr, nextPollTimeoutMillis)调用naive方法操作管道,由nextPollTimeoutMillis决定是否需要阻塞nextPollTimeoutMillis为0的时候表示不阻塞,为-1的时候表示一直阻塞直到被唤醒,其他时间表示延时。
唤醒
主要是指enqueueMessage()@MessageQueue 进行唤醒。
简单理解阻塞和唤醒
就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
常见问题分析
1 为什么不能在子线程中更新UI,根本原因是什么?
mThread是UI线程,这里会检查当前线程是不是UI线程。那么为什么onCreate里面没有进行这个检查呢。这个问题原因出现在Activity的生命周期中,在onCreate方法中,UI处于创建过程,对用户来说界面还不可视,直到onStart方法后界面可视了,再到onResume方法后界面可以交互。在Android中,UI不是线程安全的,因为是线程不安全的,所以当UI在主线程中被创建出来后,就只能在Ui线程中进行操作来。否则会出现数据不同步的问题。所以是不可以在自线程中操作UI的。那为什么要设计成线程不安全的呢,这里是为了提高UI的执行效率。
2为什么主线程用Looper死循环不会引发ANR异常?
简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,
此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,
通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制。
3为什么Handler构造方法里面的Looper不是直接new?
如果在Handler构造方法里面new Looper,怕是无法保证保证Looper唯一,只有用Looper.prepare()才能保证唯一性,具体去看prepare方法。
4MessageQueue为什么要放在Looper私有构造方法初始化?
因为一个线程只绑定一个Looper,所以在Looper构造方法里面初始化就可以保证mQueue也是唯一的Thread对应一个Looper 对应一个mQueue。
5 Handler.post的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的?
由Looper所在线程决定的。逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。
网友评论