handler 消息机制
包含framework/base/core/java/andorid/os/
- Handler.java
- Looper.java
- Message.java
- MessageQueue.java
消息机制主要包含
Message
:消息->分为硬件产生的消息和软件产生的消息
MessageQueue
: 消息队列 主要功能是向消息池投递消息(MessageQueue.enqueueMessage)和从消息池中取走消息MessageQueue.next
handler
:消息辅助类主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage)
Looper
: 不断循环执行按分发机制将消息分发给目标处理者
当Message的回调方法不为空时,则回调方法msg.callback.run(),其中callBack数据类型为Runnable,否则进入步骤2;
当Handler的mCallback成员变量不为空时,则回调方法mCallback.handleMessage(msg),否则进入步骤3;
调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。
对于很多情况下,消息分发后的处理方法是第3种情况,即Handler.handleMessage(),一般地往往通过覆写该方法从而实现自己的业务逻辑
MessageQueue.enqueueMessage()
Handler.sendEmptyMessage()等系列方法最终调用MessageQueue.enqueueMessage(msg, uptimeMillis),
将消息添加到消息队列中,其中uptimeMillis为系统当前的运行时间,不包括休眠时间。
获取消息 是调用 Handler.obtainMessage()方法,最终是调用 Messsage.obtain(this)方法
removeMessages() 调用的也是 MessageQueue.removeMessages(this,…) 的方法
MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,
其中MessageQueue类中涉及的native方法如下:
nativeInit
nativeDestroy
nativePollOnce
nativeWake
nativeIsPolling
nativeSetFileDescriptorEvents
创建过程
1.Looper的prepare或者prepareMainLooper静态方法被调用,将一个Looper对象保存在ThreadLocal里面。
2.Looper对象的初始化方法里,首先会新建一个MessageQueue对象。
3.MessageQueue对象的初始化方法通过JNI初始化C++层的NativeMessageQueue对象。
4.NativeMessageQueue对象在创建过程中,会初始化一个C++层的Looper对象。
5.C++层的Looper对象在创建的过程中,会在内部创建一个管道(pipe),并将这个管道的读写fd都保存在mWakeReadPipeFd和mWakeWritePipeFd中。
然后新建一个epoll实例,并将两个fd注册进去。
6.利用epoll的机制,可以做到当管道没有消息时,线程睡眠在读端的fd上,当其他线程往管道写数据时,本线程便会被唤醒以进行消息处理
消息循环
1.首先通过调用Looper的loop方法开始消息监听。loop方法里会调用MessageQueue的next方法。next方法会堵塞线程直到有消息到来为止。
2.next方法通过调用nativePollOnce方法来监听事件。next方法内部逻辑如下所示(简化):
进入死循环,以参数timout=0调用nativePollOnce方法。
如果消息队列中有消息,nativePollOnce方法会将消息保存在mMessage成员中。nativePollOnce方法返回后立刻检查mMessage成员是否为空。
如果mMessage不为空,那么检查它指定的运行时间。如果比当前时间要前,那么马上返回这个mMessage,否则设置timeout为两者之差,进入下一次循环。
如果mMessage为空,那么设置timeout为-1,即下次循环nativePollOnce永久堵塞。
3.nativePollOnce方法内部利用epoll机制在之前建立的管道上等待数据写入。接收到数据后马上读取并返回结果。
消息发送
1.Handler对象在创建时会保存当前线程的looper和MessageQueue,如果传入Callback的话也会保存起来。
2.用户调用handler对象的sendMessage方法,传入msg对象。handler通过调用MessageQueue的enqueueMessage方法将消息压入MessageQueue。
3.enqueueMessage方法会将传入的消息对象根据触发时间(when)插入到message queue中。然后判断是否要唤醒等待中的队列。
如果插在队列中间。说明该消息不需要马上处理,不需要由这个消息来唤醒队列。
如果插在队列头部(或者when=0),则表明要马上处理这个消息。如果当前队列正在堵塞,则需要唤醒它进行处理。
4.如果需要唤醒队列,则通过nativeWake方法,往前面提到的管道中写入一个"W"字符,令nativePollOnce方法返回。
消息处理
Looper对象的loop方法里面的queue.next方法如果返回了message,那么handler的dispatchMessage会被调用。
如果新建Handler的时候传入了callback实例,那么callback的handleMessage方法会被调用。
如果是通过post方法向handler传入runnable对象的,那么runnable对象的run方法会被调用。
其他情况下,handler方法的handleMessage会被调用。
关于Handler 消息机制的问题
handler 的作用 是 切换线程
1.handler 是怎么获取到当前looper的?
looper 和Thread 的是对应的 一个looper 一个Thread ,当looper调用 prepare 方法的时候会将 looper储存在ThreadLocal中
Threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据
通过调用 myLooper的方法从ThreadLocal中 调用 get 方法获取
2.当MessageQueue 没有消息的时候,在干什么
当MessageQueue.next() 从队列中取消息实际上是在 nativePollOnce() 这个native 方法中实现的,当没有消息的时候 ,该方法会阻塞线程
-
一个线程中最多有多少个Handler,Looper,MessageQueue?
可以有多个Handler 因为handler 是自己new出来的
looper 和messageQueue 只能有一个 因为创建的looper 是保存在ThreadLocal 中的
ThreadLocal 提供了针对于单独的线程的局部变量,并能够使用 set()、get() 方法对这些变量进行设置和获取,并且能够保证这些变量与其他线程相隔离。
MessageQueue 是在looper 的构造中创建的所以也只有一个
-
Looper死循环为什么不会导致应用卡死,会耗费大量资源吗?
因为是处于睡眠状态
looper .loop() 方法是一个死循环 不断的从消息队列中取数据queue.next(),实际上是通过 nativePollOnce() native
方法 调用C++的方法来实现的, 当没有数据的时候,会调用 epoll_wait()阻塞方法.此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
Android是基于消息处理机制的,用户的行为都在这个Looper循环中,我们在休眠时点击屏幕,便唤醒主线程继续进行工作
-
子线程的如何更新UI,比如Dialog,Toast等?系统为什么不建议子线程中更新UI?
线程中更新UI的重点是创建它的ViewRootImpl和checkThread所在的线程是否一致。
-
主线程如何访问网络?
将严苛模式网络检测关掉 网络请求之前
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build(); StrictMode.setThreadPolicy(policy);
-
如何处理Handler使用不当造成的内存泄漏?
有延时消息,在界面关闭后及时移除Message/Runnable,调用
handler.removeCallbacksAndMessages(null)
内部类导致的内存泄漏改为静态内部类,并对上下文或者Activity/Fragment使用弱引用。
-
Handler的消息优先级,有什么应用场景?
通过dispatchMessage 来分发消息,里面有两个if判断
-
判断message.callback() 是否为空,不为空则执行这个回调,
-
如果handler 的CallBack不为空,则执行这个回调
-
是handler的handlerMessage的方法
-
-
主线程的Looper何时退出?能否手动退出?
不能手动退出,主线程的looper 是在app退出的时候退出的
App退出时 ActivityThread中的mH(Handler)收到消息后退出,不可手动退出
因为主线程不允许退出,一旦退出就意味着程序挂了,退出也不应该用这种方式退出。
-
如何判断当前线程是安卓主线程?
Looper.getMainLooper() Looper.getMainLooper().getThread()
使用Looper 判断
-
正确创建Message实例的方式?
- 通过 Message 的静态方法
Message.obtain()
获取; - 通过 Handler 的公有方法
handler.obtainMessage()
所有的消息会被回收,放入sPool中,使用享元设计模式。** 享元模式主要用于减少对象创建的数量,以减少内存占用和提高性能**
- 通过 Message 的静态方法
-
handler 的同步屏障机制
Handler中加入了同步屏障这种机制,来实现[异步消息优先]执行的功能。
当messgeQueue.next() 去消息的时候 会判断对头是不是同步屏障消息,如果是的话遍历队列里面所有的异步消息,取出来执行操作,否则就会阻塞,主线程就处于空闲状态,在之道同步屏障 消息配移除,才开始执行同步任务
-
如果在子线程中使用handler需要做什么?
looper.prepare() looper.loop() looper.quit()
-
Handler 中使用了什么设计模式
Message 使用了享元模式 ----减少对象的创建,对象可以反复使用
MessageQueue 生产者消费者