Handler详解

跟Handler有关系的,包括Thread,Looper,Handler,MessageQueue

Looper:

由于Looper是android包加入的类,而Thread是java包的类,所以,想要为Thread创建一个Looper,需要在线程内部调用Looper.prepare

Looper内部会存储一个ThreadLocal,因此每个线程都会有自己的一个Looper。

Looper内部有自己存储了一个MessageQueue,以及主线程的MainLooper。

调用Looper一般会有两个方法:Looper.prepare以及Looper.loop方法

Looper.prepare

prepare会新创建一个Looper,塞进ThreadLocal,因此prepare必须在线程内部调用,才能将线程本身作为key。

同时,会创建MessageQueue,以及存储当前线程。

顺便看下两个比较常用的方法:

myLooper是从ThreadLocal中获取的当前线程所属的Looper。

myQueue对应的是当前线程的Looper中存储的MessageQueue。

Looper.loop

从Looper.loop方法,可以看出几个细节:

  1. 通过调用MessageQueue.next获取下一个要处理的Message
  2. 通过Message.target.dispatchMessage,将Message提交给Message中存储的Handler去处理,Handler调用dispatchMessage
  3. 可以创建一个进程唯一的Observer去监听Message的分配以及处理结束的进度。
public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;final MessageQueue queue = me.mQueue;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 loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}// Make sure the observer won't change while processing a transaction.final Observer observer = sObserver;final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;Object token = null;if (observer != null) {token = observer.messageDispatchStarting();}long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (slowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");slowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.slowDeliveryDetected = true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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();}
}

Message

从Looper.loop方法可以看出,会对Looper的MessageQueue遍历,不断取出Message,然后调用Message.target.dispatchMessage方法。

从Message的变量可以看出,target实际是Message所属的Handler。

同时Message存储了一个next,说明MessageQueue是一个链式结构。

MessageQueue

https://www.cnblogs.com/jiy-for-you/p/11707356.html

从MessageQueue.next可以获取几个有效信息:

  1. nativePollOnce:MessageQueue中没有Message的时候会卡在这个方法,类似于object.wait,当有人调用MessageQueue.enqueueMessage方法的时候,会将线程唤醒。
  2. msg.target == null,代表该msg是一个同步屏障(即阻拦同步消息的执行)。遇到同步屏障时,会往后遍历,优先执行异步消息(触发view的绘制的那个消息就是异步消息)。
  3. 如果获取到一个msg,msg.when代表的执行时间还没到,会先去执行IdleHandler里面的消息(或MessageQueue队列为空)。

 

// MessageQueue.next
Message next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// 如果没有消息,会卡在这个方法nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// 同步屏障,当遇到同步屏障,会往后寻找异步消息(isAsynchronous)执行// Stalled by a barrier.  Find the next asynchronous message in the queue.do {// 遍历,直到找到一个异步的msgprevMsg = msg;// 这一步,prevMsg!=null,这会导致msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// msg.when是这个message的执行时间,如果message的执行时间在now之后// Next message is not ready.  Set a timeout to wake up when it is ready.nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();return msg;}} else {// No more messages.nextPollTimeoutMillis = -1;}// Process the quit message now that all pending messages have been handled.if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.// 走到这,说明消息队列是空的,或队首是一个延迟执行的Messageif (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;}
}

Handler的总结:

  1. Handler内部一定会有一个Looper。Looper跟线程一一绑定。绑定的关系存在Looper的sThreadLocal中。所以如果Handler想要监听哪个线程上的消息,可以直接给Handler传那个线程的Looper即可。
  2. Looper实际上是一个轮询消息的机制,所以内部一定会存在一个MessageQueue。当Looper开始轮询的时候(调用Looper.loop),会每次调用MessageQueue.next取一个Message出来执行。
  3. Looper获取到Message之后,会调用Message.target.dispatchMessage方法。即实际调用的是Handler.dispatchMessage的方法。
  4. Message中有几个比较重要的参数:
    1. target:这个Message从属于哪个handler(从哪个handler post过去的)。
    2. callback:当调用handler.postRunnable,即创建了一个Message,msg.callBack = runnable。
    3. what:这个Message的唯一标识id。当Handler.handleMessage方法中,会接收多个message,通过what区分这个Message的类别。
    4. when:通过handler.postDelayed,设置这个Message实际应该执行的时间:curTime+delay。MessageQueue的入队实际是通过when去进行Message的排序的。
  5. handler.dispatchMessage方法,
    1. 如果Message.callback != null,直接执行Message.callback.run(即post(Runnable))中Runnable的执行。
    2. 否则如果给handler设置了Callback,就调用Callback.handleMessage
    3. 否则,调用Handler本身的handleMessage方法(空实现),需要重写。

关于barrier

// MessageQueue
private int postSyncBarrier(long when) {// Enqueue a new sync barrier token.// We don't need to wake the queue because the purpose of a barrier is to stall it.synchronized (this) {final int token = mNextBarrierToken++;final Message msg = Message.obtain();msg.markInUse();msg.when = when;msg.arg1 = token;// 代表Message的barrier,特征是target == nullMessage prev = null;Message p = mMessages;if (when != 0) {// 根据when,将代表barrier的msg插入MessageQueuewhile (p != null && p.when <= when) {prev = p;p = p.next;}}if (prev != null) { // invariant: p == prev.nextmsg.next = p;prev.next = msg;} else {msg.next = p;mMessages = msg;}return token;}
}

postSyncBarrier,生成一个target == null的Message,根据when,插入MessageQueue中。返回的token是barrier的唯一标识。只要postSyncBarrier,就要根据这个token,后面移除barrier。否则会导致同步消息一直无法执行。

看下有了barrier的MessageQueue取Message的时候是怎么表现的。

Message next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}nativePollOnce(ptr, nextPollTimeoutMillis);// nextPollTimeoutMillis:等待的时间synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// 如果取msg的时候,队首的Msg是Barrier// Stalled by a barrier.  Find the next asynchronous message in the queue.do {prevMsg = msg;msg = msg.next;// 就一直往后遍历,寻找一个异步的msg} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// 如果还没到msg的执行时间,就设置nextPollTimeoutMillis// Next message is not ready.  Set a timeout to wake up when it is ready.nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();return msg;}} else {// 如果mMessages队列为空,或有Barrier的时候,异步msg为空,就设置等待时间为-1// 为-1代表等待被唤醒// No more messages.nextPollTimeoutMillis = -1;}// Process the quit message now that all pending messages have been handled.if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}// 如果队列为空,或者还没到message的执行时间,开始执行IdleHandlerif (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.// 防止下次再走一遍IdleHandlerpendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.// 经过IdleHandler之后,可能已经有Message入队了,再遍历一遍,第二次就直接等待了。nextPollTimeoutMillis = 0;}
}

总结下next的逻辑:

  1. 如果mMessages的队首是barrier(msg.target == null),就遍历messages,优先执行异步消息(异步消息一般是优先级最高的信息:比如响应input事件或是view刷新。)。
  2. 如果不是barrier,就直接取队首的message执行。
  3. 如果1,2步骤取到的message != null,先看message的when < now,大于则直接返回message给Looper.loop方法。小于,则设置nextPollTimeoutMillis,用来设置线程的等待时间:nativePollOnce。
  4. 如果messageQueue为空,或message.when > now(即要等待),那么这个时候,就去执行IdleHandler。

总结下上面,nativePollOnce其实代表,线程在等待下一个消息的执行,或者messages队列为空。或者是设置了barrier情况下,没有异步消息的时候。

下一步,看下MessageQueue的具体打出日志代表什么。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/90455.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS字体像素与磅的对应关系

注意&#xff1a;低于iOS10的系统&#xff0c;显示的字宽和字高比高于iOS10的系统小。 这就是iOS10系统发布时&#xff0c;很多app显示的内容后面出现…&#xff0c;因而出现很多app为了适配iOS10系统而重新发布新版本。 用PS设计的iOS效果图中&#xff0c;字体是以像素&#x…

添加vue devtools扩展工具+添加后F12不显示Vue图标

前言&#xff1a;在开启Vue学习之旅时&#xff0c;遇到问题两个问题&#xff0c;第一添加不上vue devtools扩展工具&#xff0c;第二添加完成后&#xff0c;F12不显示Vue图标。查阅了很多博客&#xff0c;自己解决了问题&#xff0c;故写此博客记录。如果你遇到和我一样的问题&…

在 Linux 虚拟机上使用 Azure 自定义脚本扩展版本

参考 azure创建虚拟机,创建虚拟机注意入站端口规则开放80端口、 2.转到资源&#xff0c;点击扩展应用程序&#xff0c;创建存储账户&#xff0c;创建容器&#xff0c;上传文件&#xff0c;选择文件&#xff0c;会自动执行部署。 apt-get update -y && apt-get insta…

计算机网络-物理层(二)- 传输方式

计算机网络-物理层&#xff08;二&#xff09;- 传输方式 串型传输与并行传输 串行传输:是指数据是一个比特一个比特依次发送的&#xff0c;因此在发送端和接收端之间&#xff0c;只需要一条数据传输线路即可 并行传输:是指一次发送n个比特而不是一个比特&#xff0c;因此发送…

计算机网络-物理层(一)物理层的概念与传输媒体

计算机网络-物理层&#xff08;一&#xff09;物理层的概念与传输媒体 物理层相关概念 物理层的作用用来解决在各种传输媒体上传输比特0和1的问题&#xff0c;进而为数据链路层提供透明(看不见)传输比特流的服务物理层为数据链路层屏蔽了各种传输媒体的差异&#xff0c;使数据…

django boostrap html实现可拖拽的左右布局,鼠标拖动调整左右布局的大小或占比

一、实现的效果 最近需要在Django项目中,实现一个左右布局的html页面,页面框架使用的是boostrap。但这个布局不是简单的左右分栏布局,而是需要实现可以通过鼠标拖拽的方式动态调整左右两侧布局的大小和占比。效果大致如下: 一开始,页面分为左右两块布局: 鼠标放到中间的…

Python第三方库 - Pandas库

文章目录 1. Pandas介绍2. Pandas基础2.1 引入2.2 数据结构2.2.1 Series2.3 DataFrame2.3.1 概念 3 Pandas - CSV 文件3.1 语法3.2 遇到的问题 4 Pandas - JSON4.1 语法 参考文档 1. Pandas介绍 概念: Pandas 是 Python 的核心数据分析支持库&#xff0c;提供了快速、灵活、明…

Tomcat日志中文乱码

修改安装目录下的日志配置 D:\ProgramFiles\apache-tomcat-9.0.78\conf\logging.properties java.util.logging.ConsoleHandler.encoding GBK

Spring Security6 最新版配置该怎么写,该如何实现动态权限管理

Spring Security 在最近几个版本中配置的写法都有一些变化&#xff0c;很多常见的方法都废弃了&#xff0c;并且将在未来的 Spring Security7 中移除&#xff0c;因此又补充了一些新的内容&#xff0c;重新发一下&#xff0c;供各位使用 Spring Security 的小伙伴们参考。 接下…

若依框架浅浅介绍

由若依官网所给介绍可知 1、文件结构介绍 在ruoyi-admin的pom.xml文件中引入了ruoyi-framework、ruoyi-quartz和ruoyi-generatior模块&#xff0c;在ruoyi-framework的pom.xml文件中引入了ruoyi-system模块。 2、技术栈介绍 前端&#xff1a;Vue、Element UI后端&#xff1a…

面试热题(缺失的第一个正数)

给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 输入&#xff1a;nums [1,2,0] 输出&#xff1a;3 尝试的路途是痛苦的&#xff0c;不断的尝试新方法&#xff0c;错何尝…

flask-----初始项目架构

1.初始的项目目录 -apps 包 ------存放app -user文件夹 -------就是一个app -models.py --------存放表模型 -views.py -------存放主代码 -ext包 -init.py -------实例化db对象 -manage.py -----运行项目的入口 -setting.py -----配置文件 2.各文件内容 manage…

vue里搜索框实现防抖功能

进来调用一个闭包函数debounce()&#xff0c;赋值给一个变量debounceFunc&#xff0c;&#xff08;包闭的功能就是说里面的变量timer和参数一直驻留在函数里面&#xff09; input事件调用一个函数debounceFunc&#xff08;&#xff09;&#xff0c;并且传一个回调searchs函数&a…

创建maven的Springboot项目出现错误:Cannot access alimaven

创建maven的Springboot项目出现错误&#xff1a;Cannot access alimaven 1&#xff09;问题2) 分析问题3&#xff09;解决问题 1&#xff09;问题 创建maven的Springboot项目出现错误&#xff1a; Cannot access alimaven (http://maven.aliyun.com/nexus/content/groups/p…

【Spring】深入探索 Spring AOP:概念、使用与实现原理解析

文章目录 前言一、初识 Spring AOP1.1 什么是 AOP1.2 什么是 Spring AOP 二、AOP 的核心概念2.1 切面&#xff08;Aspect&#xff09;2.2 切点&#xff08;Pointcut&#xff09;2.3 通知&#xff08;Advice&#xff09;2.4 连接点&#xff08;Join Point&#xff09; 三、Sprin…

【Linux进程篇】环境变量

【Linux进程篇】环境变量 目录 【Linux进程篇】环境变量基本概念常见环境变量查看环境变量方法测试PATH测试HOME测试SHELL和环境变量相关的命令环境变量的组织方式通过代码如何获取环境变量命令行参数命令行第三个参数通过第三方变量environ获取 本地变量通过系统调用获取或设置…

[HDLBits] Exams/m2014 q4b

Implement the following circuit: module top_module (input clk,input d, input ar, // asynchronous resetoutput q);always(posedge clk or posedge ar) beginif(ar)q<1b0;elseq<d;end endmodule

组合模式(C++)

定义 将对象组合成树形结构以表示部分-整体’的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。 应用场景 在软件在某些情况下&#xff0c;客户代码过多地依赖于对象容器复杂的内部实现结构&#xff0c;对象容器内部实现结构(而非抽象接口)的变化…

虹科干货 | 化身向量数据库的Redis Enterprise——快速、准确、高效的非结构化数据解决方案!

用户期望在他们遇到的每一个应用程序和网站都有搜索功能。然而&#xff0c;超过80%的商业数据是非结构化的&#xff0c;以文本、图像、音频、视频或其他格式存储。Redis Enterprise如何实现矢量相似性搜索呢&#xff1f;答案是&#xff0c;将AI驱动的搜索功能集成到Redis Enter…

项目难点:解决IOS调用起软键盘之后页面样式布局错乱问题

需求背景 &#xff1a; 开发了一个问卷系统重构项目&#xff0c;刚开始开发的为 PC 端&#xff0c;其中最头疼的一点无非就是 IE 浏览器的兼容适配性问题&#xff1b; 再之后项目经理要求开发移动端&#xff0c;简单的说就是写 H5 页面&#xff0c;到时候会内嵌在 App 应用或办…