Handler 系列二:如何通信
承接上一篇Handler系列一,上篇主要总结了Handler如何通信,这篇来介绍Handler怎么通信。
- Handler的通信机制
- Handler,Looper,MessageQueue如何关联
Handler 通信机制
- 创建Handler,并采用当前线程的Looper创建消息循环系统;
- Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;
- Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。
Handler如何关联Looper、MessageQueue
Handler及其关联的类图
以上类图可以快速帮助我们理清Handler与Looper、MessageQueue的关系,以下从源码的角度慢慢分析:
1、Handler 发送消息
上一段很熟悉的代码:
Message msg =Message.obtain(); //从全局池中返回一个message实例,避免多次创建message(如new Message)msg.obj = data;msg.what=1; //标志消息的标志handler.sendMessage(msg);
从sendMessageQueue开始追踪,函数调用关系:sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,携带者传来的message与Handler的mQueue一起通过enqueueMessage进入队列了。
对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。
2、消息入队列
在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target
很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage
回调到该handler中处理消息。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
在MessageQueue中,由Message的消息链表进行入队列
boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue. Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}
3、Looper 处理消息
再说处理消息之前,先看Looper是如何构建与获取的:
- 构造Looper时,构建消息循环队列,并获取当前线程
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}
- 但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:
public static void prepare() {prepare(true);}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));}
这里创建Looper,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?
static final ThreadLocal sThreadLocal = new ThreadLocal();
它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。
- TheadLocal 如何保存与获取Looper?
public void set(T value) {Thread currentThread = Thread.currentThread();Values values = values(currentThread);if (values == null) {values = initializeValues(currentThread);}values.put(this, value);}public T get() {// Optimized for the fast path.Thread currentThread = Thread.currentThread();Values values = values(currentThread);if (values != null) {Object[] table = values.table;int index = hash & values.mask;if (this.reference == table[index]) {return (T) table[index + 1];}} else {values = initializeValues(currentThread);}return (T) values.getAfterMiss(this);}
在 set 中都是通过 `values.put` 保存当前线程的 Looper 实例,通过 `values.getAfterMiss(this)`获取,其中`put`和`getAfterMiss`都有`key`和`value`,都是由Value对象的table数组保存的,那么在table数组里怎么存的呢?table[index] = key.reference;table[index + 1] = value;很显然在数组中,前一个保存着ThreadLocal对象引用的索引,后一个存储传入的Looper实例。
接下来看Looper在loop中如何处理消息
在loop
中,一个循环,通过next
取出MessageQueue中的消息
若取出的消息为null,则结束循环,返回。
- 设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。
- 若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。
public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerPrinter logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}msg.target.dispatchMessage(msg);if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();}}
4、handler处理消息
Looper把消息回调到handler的dispatchMessage中进行消息处理:
- 若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递
runnable
时,把runnable
对象赋值给了message的callback
。 - 若handler的mCallback不为空,则交由通过
callback
创建handler方式去处理。 - 否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
总结
以一个时序图来总结handler的消息机制,包含上述如何关联Looper和MessageQueue的过程。
Handler-Looper-MessageQueue时序图
Handler 系列二:如何通信相关推荐
- C# Socket系列三 socket通信的封包和拆包
通过系列二 我们已经实现了socket的简单通信 接下来我们测试一下,在时间应用的场景下,我们会快速且大量的传输数据的情况! 1 class Program 2 { 3 static void Mai ...
- Remoting系列(二)----建立第一个入门程序
http://www.cnblogs.com/Ring1981/archive/2006/07/23/455043.aspx Remoting系列(二)----建立第一个入门程序 下面的Remotin ...
- 微服务架构系列二:密码强度评测的实现与实验
本文是继<微服务架构系列一:关键技术与原理研究>的后续,系列一中论述了微服务研究的背景和意义,主要调研了传统架构的发展以及存在的问题和微服务架构的由来,然后针对微服务架构的设计原则.容器技 ...
- JAVA面试常考系列二
转载自 JAVA面试常考系列二 题目一 解释一下线程和进程 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调 ...
- 架构系列二:使用Nginx+tomcat实现集群部署
架构系列二:使用Nginx+tomcat实现集群部署 一.环境介绍 VM1:Ubuntu-S100 IP:192.168.130.128 部署Tomcat应用及Nginx VM2:Ubuntu-S ...
- [知识库分享系列] 二、.NET(ASP.NET)
最近时间又有了新的想法,当我用新的眼光在整理一些很老的知识库时,发现很多东西都已经过时,或者是很基础很零碎的知识点.如果分享出去大家不看倒好,更担心的是会误人子弟,但为了保证此系列的完整,还是选择分享 ...
- IM开发者的零基础通信技术入门(二):通信交换技术的百年发展史(下)
1.系列文章引言 1.1 适合谁来阅读? 本系列文章尽量使用最浅显易懂的文字.图片来组织内容,力求通信技术零基础的人群也能看懂.但个人建议,至少稍微了解过网络通信方面的知识后再看,会更有收获.如果您大 ...
- 西门子S7-200PLC系列(二)
西门子S7-200PLC系列(二) 软元件(定义):用户使用的PLC中每一个输入/输出.内部存储单元.定时器和计数器等都称作软元件. 软元件(组成):电子电路和寄存器及存储单元组成 软元件有其不同的功 ...
- 单片机简单应用系列二
单片机系列二--单片机与外围电路之间的通信协议(SPI.IIC.1-wire) 这是单片机系列的第二部分,在这一部分我们主要介绍一些单片机与外设器件之间的通信协议,在第一部分中我们主要介绍了并行传输和 ...
最新文章
- nsqjs客户端的部署
- 说说JSON和JSONP,也许你会豁然开朗
- Ubuntu下Qt配置Opencv
- const与#define相比,区别和优点超详解总结
- sql server 2008安装_性能不够?基于时序数据库的Zabbix 5.2安装指南
- JavaScript | 如何为变量分配十进制,八进制和十六进制值?
- python层次聚类_用Python做层次聚类分析
- windows server 2012 st 版本的php环境问题修复 与删除
- 中软国际2020年业绩再创新高 归母净利同比增长26.5%
- SLAM--卡尔曼滤波、粒子滤波
- 微信公众号测试号接入微信公众平台开发----node.js
- windows 批量创建文件夹
- 电脑可以联网但是有的网页打不开
- python颜色画线_matplotlib设置颜色、标记、线条,让你的图像更加丰富(推荐)
- Ubuntu安装nvm
- ubuntu与win10共享LE蓝牙鼠标
- u盘恢复数据|U盘打不开提示格式化怎么恢复数据?
- 阿里巴巴达摩院发布2019十大科技趋势
- Hadoop势微,云原生上位——传统大数据平台的云原生化改造
- linux sh脚本各种数值进制转换(比如10进制转16)若干例子
热门文章
- 点击页面空白处就关闭某个层是怎么做到的
- Spring 2.5架构图
- 最小树 次小树 模板
- C语言经典例19-完数
- 【五线谱】高低八度标记 ( 高八度标记 | 标记范围的音符整体提升一个八度 | 低八度标记 | 标记范围的音符整体降低一个八度 )
- 【Groovy】MOP 元对象协议与元编程 ( 方法合成 | 动态注入方法 )
- 【Android 逆向】代码调试器开发 ( 使用 NDK 中的 ndk-build + Android.mk 编译 Android 平台的代码调试器可执行应用 )
- 【Flutter】Flutter 混合开发 ( Dart 代码调试 | Flutter 单独调试 | 混合模式下 Flutter 调试 )
- 【Android 安全】DEX 加密 ( Application 替换 | 兼容 ContentProvider 操作 | 源码资源 )
- 【音乐理论】音与音高 ( 音域 | 音符表示 )