二十三、Handler 从源码分析到全面掌握

概述

Handler是 安卓面试的必问点。在安卓开发中,handler经常用于子线程执行异步任务,然后通知到主线程更新UI。 本文将从源码分析开始,一步步了解到 handler 核心知识。

从 new Handler() 开始

Handler有一个无参构造函数。如下:

上图中 无参构造函数,实际上调用了 Hanlder(Callback callback,boolean async),而在这个有参构造器中,两个关键点

  • 创建了一个 Looper对象,赋值给了 mLooper
  • 将 mLooper中的.mQueue 赋值 给了 本类中的mQueue

说明 mLooper 和 mQueue 是 handler这个消息处理体系中的重要组成部分。

Looper是什么? 它何时被初始化?

启动一个Java程序人入口是一个 main函数,如果main函数执行完毕,那么,程序就会停止运行,app进程就会终止。但是我们打开一个App之后,除非我们按下返回键或者home键回到桌面,不然它就是一直处于运行状态。s

也就是说:Looper维护了一个无限循环,保证app进程一直在运行,这是一个重要概念,必须理解,并且记清楚。

在安卓的app中,启动app的入口函数实际上是 ActivityThread.java的main函数。 如下图: s

图中1处,创建出当前线程的Looper对象;

图中2处,启动这个looper对象的无限循环。

可是说起来是 创建了 当前线程的Looper对象,但是貌似并没有在创建时指定线程对象啊? 请看下图:

1处的 prepare(false)的实际上执行到了 prepare(boolean quitAllowed) , 创建出了一个不允许退出的Looper对象,并且将该looper设置到了 sThreaLocal 中。 这使得创建出来的Looper与当前线程发生了绑定。 并且注意,图中2处,是为了保证每一个线程的 Looper只会被创建一次,当有第二次来时,就抛出RuntimeException异常。

Lopper的构造方法如下:

其中创建了一个MessageQueue消息队列,也指定了 Looper的所属线程为 currentThread。

然后,myLooper方法,其实也就是从 sThreadLocal中取出了当前线程的Looper对象。

Looper多次初始化会导致程序崩溃

上图中的Looper.prepare();会导致程序崩溃

,这是因为:该代码运行在主线程中,而主线程的Looper在ActivityThread.java的 main函数中已经 执行了一次。

这也就意味着:

  • Lopper的prepare方法只能在一个线程中执行一次
  • Looper的构造函数只能在一个线程中执行一次
  • 一个线程中的 MessageQueue 只能被初始化一次

Lopper的职责

它的职责很简单,就是 不停地从 MessageQueue中取出 Message 并且执行 Message 指定的任务。

上图的main函数中,有一个 Looper.loop() 开启了无限循环,以保证app进程持续运行。(必须是死循环)

图中1处,从 queue中取出下一个message,

图中2处,取得message.target,并且执行 dispatchMessage.

那么这个target是什么呢?从Message的源码可以看到:

它其实就是Handler

,而 Handler的 中有一个空方法 handlerMessage(),这也就是 我们在创建handler需要重写的方法。

那么Handler是何时成为 一个Message的target的呢?

看下图:

handler的一个重要方法就是发送消息的 sendMessage。 它有一些名称类似的方法,但是作用都是大同小异。基本都走到了 enqueueMessage, 看下图:

图中1处,当发送一个 message时,顺便将 自身this 赋值给了msg.target

图中2处,如果一个message没有设置 target,那么直接抛出异常。

图中3处,按照 messagewhen (执行延时) 来决定插入的位置。

可以看出,MessageQueue其实是一个按照时间顺序来排列的有序队列(数据结构为单链表)。

常见面试题

Handler的 post(Runnable)和sendMessage() 的区别

从 post的源代码分析开始:

将 runnable对象指定为了 message的callback

而在 处理message时,会优先执行 message的callback回调,也就是执行了 Runnable的run方法。

SendMessage时,则没有指定 message的callback。处理 message时,优先执行 Handler的mCallback对象,重写的handlerMessage次之。

所以。一句话,根本上的区别是post(Runnable)和sendMessage()message的处理方式设置的位置不一样。前者就是 runnable本身,后者是 handlerMessage函数,或者是 mCallback的handlerMessage。

Looper.loop()为什么不会阻塞主线程

首先这种提问方式其实是一个陷阱,app主线程能够持续运行的前提,就是 Looper的loop函数中的无限循环能够持续运行。

其次,如果 该应用没有 Message需要处理(也就是暂时没有任何message进入时),app会如何暂时释放CPU资源,而不是持续占用CPU。

此图中1处,queue.next() 后面有一句官方的 注释might block,虚拟语气,说明可能会阻塞。

其实看看内部源码:

这里存在一个 nativePollOnce方法。它是一个native方法。当执行时,主线程会暂时释放CPU,并进入到休眠状态,直到下一条消息到达,或者有事务发生,通过往 pipe管道写端写入数据来唤醒主线程工作。

这里采用的是 c++层的epoll机制。

Handler的sendMessageDelayed 或者 postDelayed 是如何实现的

Handler的两个核心逻辑,一个是 线程间数据共享,一个是 发送以及执行延时任务。

上述两种发送消息的方式,都可以通过设置延时时间来 达成业务目的。

具体做法:

MessagQueue 中插入消息时,会根据Message的执行时间进行排序。 而在 处理Message时,核心逻辑在 MessageQueuenext方法中。

蓝色框框中,进行了当前时间消息执行时刻的对比。

  • 如果当前时间已经超出了 消息的执行时刻,那么会马上返回这个message对象给到 Looper去处理。
  • 如果当前时间小于 消息的执行时刻,那么就会计算出一个 时间差,这个时间差就是CPU需要休眠的时间,而由于这是一个for死循环内的过程,所以 nativePoolOnce会马上执行休眠,休眠完成之后再次对比当前时间与 消息的执行时刻。

其实仔细想想,这种方式只能保证 message在when之前不被执行,而不能保证一定在when当前时刻执行。

总结

  • 应用的启动是从 ActivityThread 的 main函数开始,先是执行了 Looper.prepare(),该方法先是 创建了一个Lopper对象,然后在私有方法中又创建了一个 MessageQueue作为Looper的成员变量,Looper对象通过ThreadLocal绑定在了主线程上。

  • 当创建Handler对象时,构造方法中通过ThreadLocal获取绑定的Looper对象,并获取它的MessageQueue作为Handler的成员变量

  • 在子线程中 使用 上一步创建的 Handler子类对象的 sendMessage方法时,将 message的target设置为 自身。同时调用了成员变量的MessageQueue 的 enqueueMessage 方法,将 message放到 MessageQueue中

  • 主线程创建好之后,会执行Looper.loop方法,该方法中获取与线程绑定的Looper对象,继而获取 MessageQueue,并开启一个会阻塞(释放CPU)的死循环,只要MessageQueue中还有message,就会获取该message,并执行 message.target.dispatchMessage处理消息。

相关推荐
安冬的码畜日常1 分钟前
【CSS in Depth 2 精译_036】5.6 Grid 网格布局中与对齐相关的属性 + 5.7本章小结
前端·css·css3·html5·网格布局·grid·css网格
啧不应该啊1 小时前
vue配置axios
前端·javascript·vue.js
__fuys__1 小时前
【HTML样式】加载动画专题 每周更新
前端·javascript·html
Want5952 小时前
HTML粉色烟花秀
前端·css·html
让开,我要吃人了2 小时前
HarmonyOS鸿蒙开发实战(5.0)自定义全局弹窗实践
前端·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·鸿蒙开发
yanlele2 小时前
前端面试第 66 期 - Vue 专题第二篇 - 2024.09.22 更新前端面试问题总结(20道题)
前端·javascript·面试
一条晒干的咸魚2 小时前
响应式CSS 媒体查询——WEB开发系列39
前端·css·html·css3·响应式设计·媒体查询
凌晨五点的星2 小时前
网络安全-webshell绕过,hash碰撞,webshell绕过原理
开发语言·前端·javascript
天心天地生3 小时前
【bugfix】-洽谈回填的图片消息无法显示
开发语言·前端·javascript
啧不应该啊3 小时前
element plus 按需导入vue
前端·javascript·vue.js