简介
这一系列文章是专门研习底层源码原理的,本篇开始分析 Handler 的源码。
在开发中,我们经常会遇到在子线程中操作一些流程,当操作完毕后通过 Handler 发送数据给主线程,并通知主线程做相应操作的变更的场景。其背后原理: Handler 、Looper、MessageQueue 这三类想必大多数Android从业者都能说出其各自代表的功能实现。但是仅仅知道这个类的作用,却始终串联不起来其与底层源码的实现沟通,其实并不能算是完全理解,所以接下来要做的事就是从 应用启动 开始分析,摸清整个 Handler 的核心机制。
想象一下,在我们最亲近的身体里面,我们的眼睛所看,耳朵所听,肢体所摸,都是我们对外界作出反应的一个线程,它无时无刻不在感受着外界给他的信息作出反馈,我们的外表动作,比如:眨眼,抬手,打哈欠,等等…是我们大脑在不间断轮询各个感官器官所要表达的信息所做出的的一系列指令。细品一下,我们有这样的关系比对:
眼看耳听鼻闻 ≈ Handler
所看所听所闻 ≈ Message
眼耳鼻神经系统 ≈ sendMessage
大脑神经系统 ≈ Looper(如果大脑几乎啥都轮询不到了,那多半睡着了,同时符合先到先觉机制)
Looper.prepare(人一出生就初始化,这一辈子也就初始化一次)
Looper.loop(不断轮询)
神经元组织 ≈ MessageQueue
反馈神经 ≈ dispatchMessage
外表肢体 ≈ handlerMessage
综上对应我们接下来需要分析的源码类,如下:
源码分析
ActivityThread:
应用启动是从 ActivityThread 的 main 方法开始,首先执行 Looper.prepare(),prepare 方法里 new 了一个 Looper 对象,在私有的构造方法里,创建了 MessageQueue 作为此 Looper 的对象成员变量,Looper 对象通过 ThreadLocal 和当前线程进行绑定。
public static void main(String[] args) { |
Handler:发送&接收消息
构造函数
// 这两个全局变量均是通过 Looper 来获取 |
dispatchMessage
public void dispatchMessage(@NonNull Message msg) { |
sendMessage
代码时序为:sendMessage -> sendMessageDelayed -> sendMessageAtTime
最终调用 enqueueMessage 将 消息Message 插入 消息队列MessageQueue(ActivityThread main方法中通过 Looper 创建的)
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, |
Looper: 轮询消息队列
成员变量
// 线程内部存储类 维护 Looper:在一个本地线程里,永远都只会维护自身的副本变量,互不干扰 |
prepare
// 确保每个线程只会有一个Looper |
prepareMainLooper
public static void prepareMainLooper() { |
这里有一个很巧妙的处理机制,我们知道运行一个Java程序,其入口函数 main 方法执行完毕之后,程序就会停止运行,也就是进程自动终止。但是当我们启动一个应用程序,却发现只要我们不退出这个app,程序就可以一直操作运行。熟悉源码我们就会发现,这是因为 启动一个应用程序后,在其 main 主方法里,一直存在一个Looper对象,它在内部维护一个无限循环,保证 APP 进程持续进行。
小结:
prepare 方法在一个线程中只会被调用一次,也就是说一个线程只会有一个 Looper。
Looper 的构造方法在一个线程中只能被调用一次。
正是因为第二个原因,导致 MessageQueue 在一个线程中也就只会初始化一次。
loop
public static void loop() { |
这里有一个问题:Handler是何时被设置为一个 Message 的 target 的?
MessageQueue:用于存储消息管理消息的消息队列
enqueueMessage
boolean enqueueMessage(Message msg, long when) { |
发送消息
post/send Message
postSyncBarrier 同步屏障消息
message.target=null,这类消息不会真的被执行,它起到了flag的作用,MessageQueue在遍历消息队列时,如果对头同步屏障消息,那么会被if判空拦截达到忽略同步消息的目的,优先让异步消息执行。这就是它的目的,一般异步消息和同步屏障消息会一同使用。让重要的紧急的消息优先执行,在系统层分发处理用的较多。
异步消息&同步屏障 使用场景
- ViewRootImpl接收屏幕垂直同步信息事件用于驱动UI测绘
- ActivityThread接收AMS的事件驱动生命周期
- InputMethodManager分发软键盘输入事件
- PhoneWindowManager分发电话页面各种事件
ThreadLocal
- ThreadLocal提供了线程独有的局部变量存储能力,可以在整个线程存活的过程中随时取用,方便了一些逻辑的实现。
- ThreadLocal不具备线程安全的能力
ThreadLocal是如何发生内存泄露的?
发生 内存泄露的场景是在 使用线程池。因为池子中的任务在执行完成之后,并不会被立刻回收,但是如果任务执行完成之后,我们手动将线程池中的线程置为空,那么被弱引用持有的TheadLocal就会被GC回收,但是这样就会导致Entry中的value被赋予强引用值,如果我们没有手动移除这个value,那么就很有可能造成内存泄露。
弱引用什么时候才会被回收?
当这个弱引用所持有的对象,它的强引用,也就是我们new出来的对象被置为空的时候——没有引用的时候,GC扫描到这个弱引用对象的时候才会被回收。如果没有被置为空,只能等到内存严重不足的时候,才会被回收。
源码涉及开发模式
享元模式
复用消息Message对象。(默认50个对象,回收复用recycleUnchecked,拷贝对象并初始化)
总结
handler.sendMessage 发送消息到 消息队列MessageQueue,然后 ActivityThread main()方法中的 looper.loop() 不断遍历消息队列消息队列 MessageQueue 里面的 Message,当 Message 达到了可执行时间(when) 开始执行,执行后 调用 Message 对象绑定的 target对象(Handler)来进行消息分发处理。
至此,几大类里重要的方法均分析完毕,有关 Handler 的发送消息 和 消息处理流程也就相应结束。接下来,就是一些抠细节的地方,但也往往是细节,所以都不重视,导致面试的时候没有想到,得不偿失。
剑指细节
Handler 的 post(Runnable) 与 sendMessage 的区别?
代码序列:
post(Runnable) -> sendMessageDelayed(getPostMessage(Runnable)) -> sendMessageAtTime() -> enqueueMessage
-> Looper.loop # msg.target.dispatchMessage(msg);
上面分析了 dispatchMessage 方法,这里直达,可以看出,dispatchMessage 分两种情况:
1、如果 Message 的 Callback 不为 null,则优先选择 post(Runnable) 执行,之后则会调用 Runnable 的 run方法,这里 Runnabel只是一个回调接口,跟线程是没有关系的;
2、如果 Message 的 Callback 为 null,这种一般就会选择后者 sendMessage 来发送处理,之后会调用 Handler 的 handlerMessage 的方式处理接收消息
Looper.loop() 为什么不会阻塞主线程?
刚才我们分析过 Looper.loop() 是一个死循环方法的执行过程。但是UI线程却并没有阻塞,反而还能进行各种手势操作。这是因为在 MessageQueue 的 next 方法中,可以看到:
|
【答】
主线程确实是通过Looper.loop()进入了循环状态,也正因为这样,主线程才不会像我们一般创建的线程一样,当可执行代码结束之后,线程生命周期就终止了。
在主线程的 MessageQueue 没有消息时,便阻塞在了 MessageQueue.**next()** 中的 **nativePollOnce()**方法里,此时主线程会释放CPU资源进入休眠状态,直到新消息达到。所以主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
这里采用Linux的epoll机制,是一种IO的多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪(读或写就绪),则立刻通知相应程序进行读或者写操作拿到最新的消息,进而唤醒等待的线程。
延迟消息是如何得到保证的(Handler实现发送延迟消息的原理是什么)?
我们常用 postDelayed() 与 sendMessageDelayed() 来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过 sendMessageAtTime() -> enqueueMessage -> queue.enqueueMessage 这一系列方法将消息插入到MessageQueue中。所以并不是先延迟再发送消息,而是直接发送消息,再借助 MessageQueue的设计来实现消息的延迟处理的。原理如下:
首先通过 SystemClock.uptimeMillis + 需要延迟的时间 计算这个消息它应该执行的时间戳,然后借入native的epoll命令阻塞一段时间,超时之后自动恢复,然后继续往下检查执行,找出需要执行的符合条件的message。如果有,就拿出来处理,延时消息就是这么来的。
有关 nativePollOnce 的函数分析,可以参考 《深入理解Android:卷2》一文。
Handler 的 sendMessageDelayed/postDelayed 是如何实现的?
post 一类的方法发送的是 Runnable 对象,但是其最后还是会被封装成 Message 对象,将Runnable对象赋值给Message对象中的Callback变量,然后交由 sendMessageAtTime()方法发送出去。在处理消息时,会在dispatchMessage() 方法里首先被 handleCallback(msg) 方法执行,实际上就是执行Message对象里面的Runnable对象的run方法。
而sendMessage一类的方法发送的直接是Messgae对象,处理消息时,在dispatchMessage里优先级会低于handleCallback(msg)方法,是通过重写的 handleMessage(msg) 方法执行。
为什么要通过Message.obtain() 方法获取Message对象?
obtain方法可以从全局消息池中得到一个空的Message对象,提供了对象复用能力,避免重复创建对象造成的资源的浪费以及频繁GC的问题,这样可以有效节省系统资源。同时,通过各种obtain重载方法还可以得到一些Message的拷贝,或对Message对象进行一些初始化达到复用,可参考上面的涉及开发模式
同步屏障 SyncBarrier 是什么?有什么用?
在一般情况下,同步和异步消息处理起来没什么不同。只有在设置了同步屏障之后才会有差异。同步屏障从代码层面上看是一个Message对象,但是其target是为空,用以区分普通消息还是屏障消息。在 MessageQueue.next() 中如果当前消息是一个同步屏障,则跳过之后的所有同步消息,并遍历找到一个异步消息使其优先得到处理。但是这个api开发者是调用不了的。一般是供系统使用。比如:在ViewRootImpl的UI绘制流程中就有体现。
IdleHandler 是什么?有什么用?
当消息队列没有消息时调用 或者 如果队列中仍有待处理的消息,但都未到执行时间时(也会调用此方法)。用以监听主线程即将进入空闲状态,在这里可以进行一些数据懒加载、日志上传等业务操作。
为什么非静态类的Handler会导致内存泄露,该如何解决?
首先,非静态内部类、匿名内部类、局部内部类都会隐式的持有其外部类的引用(这个在字节码里能看到隐式的持有this),也就是说在Activity中创建的Handler会因此持有Activity的引用。
当我们在主线程使用Handler的时候,Handler会默认绑定这个线程的Looper对象,并关联其MessageQueue,Handler发出的所有消息都会加入到这个MessageQueue中。Looper对象产生的生命周期贯穿整个主线程的生命周期,所以当Looper对象中的MessageQueue里还有未处理完的Message时,因为每个Message都持有Handler的引用,所以Handler无法被回收,自然其持有的引用的外部类Activity也无法被回收,从而造成内存泄露。
如何让子线程中弹出Toast?
调用Looper.prepare 以及 Looper.loop(),但是切记线程任务执行完成后,需要手动调用 Looper,quitSafely() ,否则线程是不会结束的。