handler 原理

Posted by boredream on December 9, 2019

几年前简单看过handler原理,温故知新再捋一遍

作用

用于解决在未来某个时间处理消息、runnable任务 或由子线程向主线程发送消息、runnable任务

组成

  • Message 消息
  • Looper 轮询器
  • MessageQueue 消息队列

源码流程

ActivityThread 中 main 是程序的入口 其中会 Looper.prepareMainLooper() 将一个looper和一个thread绑定, prepare也是这个作用,但main的是和主线程绑定, 之后会以thread为key,looper为value保存在一个map里, prepare里new looper的构造方法里,还会新建一个MessageQueue对象,

最后,要再调用Looper.loop()开始轮询 静态方法,先myLooper获取当前线程对应的looper,再拿到对应的MessageQueue for循环依次queue.next()取Message,取到后dispatchMessage分发。

queue中的消息从哪里来呢? 我们需要新建一个Handler,然后 handler.sendMessage / postRunnable

Handler新建的时候会Looper.myLooper获取当前线程绑定过的looper对象,同时拿到queue对象

发送消息的方法,其实最终都是调用queue的enqueueMessage方法,将消息压入队列, 说是队列,其实MessageQueue是自己维护的一个链式结构,每个Message都有next连接起来, 当消息压入时,会根据消息的when执行时间顺序,判断插入链式中的位置, 最后,还会判断是否需要唤醒Looper中等待的线程

回到 loop轮询里的 queue.next,就是遍历这个queue里的链式结构消息,挨个取message分发, for循环取消息(之前是时间顺序存入的,所以取时候也是),然后挨个处理, 先回判断如果target==null则代表是一个消息屏障,则其下一个消息是异步消息, 通常我们send的都是同步消息,postSyncBarrier才会发送异步消息, 当有异步消息时,会提出来优先执行。参考:https://blog.csdn.net/qq_21586317/article/details/88780077 然后prevMsg.next = msg.next 重新拼接异步消息前后俩的链式结构。

无论同步异步,都会先判断当前时间和消息执行msg.when时间, 如果到了,则获取消息返回给loop (looper.loop轮询获取的next消息) 如果还没到,则记录时间差值nextPollTimeoutMillis,如果什么消息都找不到则该变量设为-1 则下一轮for循环先nativePollOnce方法根据该millis的值进行阻塞休眠,-1是一直休眠,>0是休眠xx时间 -1或者未到时间的休眠情况下,如果此时有新的message压入了queue,也会根据when判断是否需要唤起

回到loop,queue.next取到消息后,再通过message.target.dispatchMessage分发,target就是handler, dispatchMsg中,会先判断如果有runnable的callback就执行之,否则就handleMessage处理, handleMessage就是我们用户需要复写处理的地方了。

解答问题:

为什么loop一直for死循环但是不ANR呢?

首先进程就是要有一个for循环,才能一直保证运行不停止, 所谓卡死是主线程的loop被卡住了才出现的,loop的循环不会卡死程序(ANR) 且looper的这个loop在MessageQueue取消息的时候,如果空或者暂时没消息会进入阻塞休眠状态,不会一直使用CPU

AS 内存工具分析

点击打开一个页面 CPU Profile 记录如下 handler1

从上到下依次:

  1. ZygoteInit.main blablabla 孵化程序进程
  2. ActivityThread.main 程序入口
  3. Looper.loop 主线程轮询器开始轮询
  4. MessageQueue.next 在 loop循环中 next尝试拉取待处理消息
  5. nativePollOnce 轮询不到消息时,阻塞休眠等待

以上是启动到待命,当有点击事件来时 (点击事件来源参考我其它相关文章) 点击事件完整的是Touch的Down Move Up,在View的Up中会判断当前的touch是click还是longclick等, 如果判定是click则触发performClick方法,自定义回调里的打开页面请求 blablablabla 可以看到loop是一直在run的,在接受消息处理之前一直都是nativePollOnce状态阻塞等待中