1、概述

在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答案这就是今天的主题Android的消息机制;

  • Android处理消息的方式——handler、Looper 和 MessageQueue
  1. Handler :负责将任务添加到队列,执行结束后在主线程执行UI操作
  2. Looper :未绑定的线程开启循环消息队列,并获取消息
  3. MessageQueue :任务队列,保存发送的消息

2、源码分析

对于Handler的使用方法此处不做介绍,在使用是只要注意避免内存泄漏需创建静态Handler,此处从执行过程和原理的角度简单分析Handler的源码,首先大家都知道在主线程中可以直接使用Handler,而在子线程中不可以,有没有想过为什么呢?现在就先看看主线程究竟有什么不同吧,从头看安卓的入口方法ActivityThread.main()

​
public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");...Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}​复制代码

  • 上述代码看出在程序启动后,执行三个步骤:
  1. 调用Looper.prepareMainLooper();创建消息循环Looper
  2. 使用 thread.getHandler(); 获取UI线程的Handler
  3. 使用 Looper.loop()开启消息循环

Handler与消息队列关联,而消息队列被封装在Looper中,每个Looper关联一个线程,所以Handler的消息大致为handler作为一个消息处理器,将消息传递到消息队列中,线程中的Looper逐个取出消息并执行,此处的线程即为UI线程,

  • Handler最基本的用法是创建Handler 发送Message到队列,现在看看创建Handler时都做了哪些事
​public Handler(Callback callback, boolean async) {mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}​复制代码

  1. 调用Looper.myLooper():获取Looper对象,如果为空则抛出异常,然后调用lopper.mQuene开启消息队列
  • 查看myLooper()方法
public static @Nullable Looper myLooper() {return sThreadLocal.get();}复制代码

  1. 在myLooper中看出,Looper从sThreadLocal中获取,那什么时候设置进去的呢?另外从这也可以看出来,只要sThreadLocal没有Looper的实例就会抛出异常,sThreadLocal就是执行的线程,换句话说只要Handler所在的线程中没有Looper的实例就会异常;
  • 接下来看看UI线程开始时创建的Looper对象的方法prepareMainLooper()
​public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}复制代码

  • prepareMainLooper() 调用了prepare()方法:
​private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));}复制代码

  1. 看到了 sThreadLocal.set(new Looper(quitAllowed)),此处创建Looper对象设置进sThreadLocal中,是不是明白了上面的sThreadLocal。get()获取的Looper是哪里的了。

到这里我们介绍了Handler 和 Looper的创建,以及各自对线程的关联,下面查看如何启动消息循环获取任务并执行,在入口方法的最后调用了Loop.loop()方法,下面查看Looper.loop()方法:

public static void loop() {final Looper me = myLooper();final MessageQueue queue = me.mQueue;
for (;;) {Message msg = queue.next(); // might blockif (msg == null) {return;}
...msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}复制代码

  1. 调用myLooper()获取Looper的对象,获取消息队列后调用next()依次取出其中的Message,注意这里是个死循环
  2. 调用Messagemsg.target.dispatchMessage(msg)执行方法
  • 查看Message的源码便知target实际是Handler的对象,说明绕了一圈最后还是Handler的dispatchMessage(msg)处理逻辑
Handler target;
Runnable callback;
Message next;复制代码

  • Handler的dispatchMessage(msg)方法
​public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
private static void handleCallback(Message message) {message.callback.run();}复制代码

总结一下:looper从MessageQuene中依次取出Message,然后调用Msg绑定的Handler的dispatchMessage(msg)处理,dispatchMessage()中依次判断执行:

  1. 先判断Message的Runable对象callback是否为null,若不为空使用handleCallback()即调用Runnable,run()执行
  2. 若callback为空判断mCallBack,若mCallBack不为空则调用mCallBack.handleMessage()处理
  3. 如果上面两者都为null,则执行handleMessage(),此处即为创建Handler()重写的handlerMessage()

到此Android中Handler的消息处理,从创建Handler、Looper和MessageQueue到消息的传递和事件的处理都介绍完了,下面我们看看使用时如何发送消息到MessageQueue以及上述的callback、mCallBack什么情况下为空?

  • 创建的Handler后发送消息方法:sendMessage(Message msg)、post(Runnable r)
  1. sendMessage()
public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;return enqueueMessage(queue, msg, uptimeMillis);}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;   // 此处设置target为Handler对象if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}复制代码

上面的执行过程代码从上倒下一目了然,在enqueueMessage()中设置了Message.target即为调用方法的Handler,所以在调用dispatchMessage()时,会调用Handler的handleMessage()处理

  • post(Runnable r)
public final boolean post(Runnable r){return  sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;   // 设置Message的callback为rreturn m;}复制代码

  1. 调用getPostMessage()设置Message中的callback,所以callback就是发送的runnable,其余方法和sendMessage()一致。

Handler消息机制的原理和过程介绍完毕了,此时知道为什么不能在子线程创建handler吗?抛出的异常在上述代码中也出现了

mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}复制代码

当Looper.myLooper()取出为空时抛出异常,而myLooper()中时从sThreadLocal.get()中获取,即此时sThreadLocal中没有设置Looper对象,所以也就没有后面的队列等操作,从上面知道创建并设置Looper对象是在prepare()方法中,所以要在子线程中使用Handler要先调用Looper.prepare()创建Looper对象,后调用Looper.loop()开启消息循环。

转载于:https://juejin.im/post/5c4e7445f265da61602d4908

Android进阶知识树——Android消息队列相关推荐

  1. Android进阶知识树——Android Handler消息机制

    1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...

  2. android进阶知识总结,Android进阶学习有哪些知识点

    Android进阶学习有哪些知识点 发布时间:2020-07-29 12:50:39 来源:亿速云 阅读:114 作者:Leah 本篇文章给大家分享的是有关Android进阶学习有哪些知识点,小编觉得 ...

  3. Android进阶知识:绘制流程(上)

    1.前言 之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android中View事件分发与响应的流程.关于View除了事件传递流程还有一个很重要的就是View的绘制流程.一个 ...

  4. Android进阶知识(二十五):Bitmap简介及其高效加载

    Android进阶知识(二十五):Bitmap简介及其高效加载 一.Bitmap   Bitmap代表一个位图,在Android中指的是一张图片,可以是png.jpg等格式的图片.BitmapDraw ...

  5. 我的Android进阶之旅------Android利用温度传感器实现带动画效果的电子温度计

    要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...

  6. android服务进阶,我的Android进阶之旅------Android服务的生命周期回调方法

    先引用一段官网上的文字 ======================================================================================== ...

  7. 我的Android进阶之旅------Android利用Sensor(传感器)实现水平仪功能的小例

    这里介绍的水平仪,指的是比较传统的气泡水平仪,在一个透明圆盘内充满液体,液体中留有一个气泡,当一端翘起时,该气泡就会浮向翘起的一端.    利用方向传感器返回的第一个参数,实现了一个指南针小应用.   ...

  8. 这份354页笔记的Android进阶知识+大厂高频面试题,绝对干货

    程序员与别的专业有所不同,其他专业都是越老越香,而程序员却是一个例外,因为计算机技术更新太快,而且工作强度很大,因此大部分程序员只会写 3 年代码.3 年后要不晋升做项目经理,要么转行,个别研究所除外 ...

  9. Android进阶知识:Retrofit相关

    1.前言 Retrofit是什么?Retrofit是一个遵循RESTful设计的进行HTTP网络请求框架的封装,是现在Android端最火的进行网络请求的库.就像Volley是谷歌官方对HttpURL ...

最新文章

  1. springboot 自动装配_Java互联网架构-SpringBoot自动装配核心源码剖析
  2. python画图中grid等于true_Python3.0科学计算学习之绘图(二)
  3. 前端学习(1435):vue能做什么
  4. mysql中不要 秒的函数_Mysql中日期和时间函数应用不用求人 | 很文博客
  5. IT部门每天忙到飞起,年终总结价值却不能被量化?附年终报告模板
  6. MySQL定时备份数据库方案(全库备份)
  7. RestTemplate使用笔记
  8. RSS 之父炮轰 Google 强推 HTTPS
  9. 23种设计模式(9):访问者模式(转)
  10. 服务器安全-使用ipset 和iptables禁止国外IP访问
  11. LeetCode题目Java代码解答 (详细解释!!!)
  12. macOS Catalina 10.15.7 ISO/CDR 虚拟机镜像下载
  13. 动手学深度学习讲义批量下载
  14. PGMIntro概率图模型
  15. 数据分析师都有哪些发展方向?
  16. 给图片加边框源代码c语言,给图片加框的代码及效果
  17. Unity(十):3D角色移动跳跃
  18. PyTorch基础:Tensor的自动广播机制与向量化
  19. Codeup(云效)手把手教部署SpringCloud项目到私有主机
  20. 目标检测 | 让YOLOv2告诉你Trick的力量

热门文章

  1. 第十四天 09-文件与内容查找与归档压缩爆破
  2. 《新财富》:泡沫难破 房价至少被高估二成
  3. CSDN博客运营团队2022年H2总结
  4. 30个令人捧腹的关于码农和编程的笑话
  5. 【密码学】古代、古典密码
  6. 16课:关于Springboot和@Cacheable注解拉去缓存,@CacheEvict清空缓存的原理
  7. 如何通俗理解 beta分布、汤普森采样和狄利克雷分布
  8. Android 项目是如何编译成.apk的
  9. CentOS7更换国内源以及安装补充更新源(安装nethogs)
  10. matlab数学建模方法与实践 笔记1:快速入门