【18】Android 线程间通信(三) - Handler

概述

接下来我们会从native层来分析一下,Handler做了什么,以及之前提到过的应用层的两个native的调用链。

nativeWake

最早接触这个方法还记得是什么时候吗?MessageQueue#enqueueMessage中,在这个方法的末尾,我们看到了它的身影,通过判断条件needWake是否为真,从而执行到这个方法中。

	//MessageQueue.javamsg.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);}

我们首先看看这个needWake的条件是什么,从上面源码可以看到,这个needWake是一个局部变量,在方法内定义的。当进入第一个if条件判断,会给needWake赋值mBlocked。mMessage是保存的头节点的Message消息,当头结点消息为空(没消息队列暂时没有消息),或者新消息延迟为0,又或新消息的延迟小于头节点消息的延迟,就进入条件体内部。而这个mBlocked在next方法中,会被赋值,当nativePollOnce未被阻塞,即当前有消息需要立即执行,mBlocked就为false。而当消息队列消息为空,IdleHandlers集合也为空,就会重新赋值给true。

进入else当中,也会给needWake赋值,满足mBlocked为true,且为头部为屏障消息,且新消息为异步消息,才会是true。

接着我们看一下这个mPtr,这个在MessageQueue中定义并且注解说明是专门为nativa code使用的。赋值是在MessageQueue创建的时候,通过nativeInit返回上来。这个先保留着,之后我们再慢慢分析。

	MessageQueue.java@UnsupportedAppUsage@SuppressWarnings("unused")private long mPtr; // used by native code

而nativeWake对应的native的类 /frameworks/base/core/jni/android_os_MessageQueue.cpp

//android_os_MessageQueue.cpp
void NativeMessageQueue::wake() {mLooper->wake();
}static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->wake();
}

进而调用到了native层的Looper中,继续看看Looper做了什么。/system/core/libutils/Looper.cpp

//Looper.cpp
void Looper::wake() {
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ wake", this);
#endifuint64_t inc = 1;ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));if (nWrite != sizeof(uint64_t)) {if (errno != EAGAIN) {LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",mWakeEventFd.get(), nWrite, strerror(errno));}}
}

这段代码执行到这里就执行完毕了。这段代码主要做了一个事情,就是通过write方法,向mWakeEventFd写入inc值。

fd 文件描述符。系统管理了一个fd列表,记录了各种不同的fd,mWakeEventFd是其中一个用于唤醒事件的fd。

这里我们一直跟踪下来,发现nativeWake走到native层,最后也只是在fd上写了一个值。而主要执行进来的手段,也是needWake判断条件为真,即:
1、消息队列没有消息的前提下,新消息的延迟when,小于头部消息。
2、消息队列没有消息的前提下,头部有一个同步屏障,且进来的新消息是一个异步消息。

nativePollOnce

这个方法接触的时候是在MessageQueue#next方法中,我们在文章一有补充说明到,nativePollOnce是一个阻塞方法,特定条件下会阻塞当前线程。

@UnsupportedAppUsageMessage 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);...}

next方法刚进入的时候,会初始化nextPollTimeoutMillis值为0,然后进入for循环,进入了nativePollOne,同nativeWake一样,会传入ptr这个值进去,同时把nextPollTimeoutMillis传入。

//android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jlong ptr, jint timeoutMillis) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {...mLooper->pollOnce(timeoutMillis);...
}//Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0;for (;;) {...result = pollInner(timeoutMillis);}
}int Looper::pollInner(int timeoutMillis) {...struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);...for (int i = 0; i < eventCount; i++) {const SequenceNumber seq = eventItems[i].data.u64;uint32_t epollEvents = eventItems[i].events;if (seq == WAKE_EVENT_FD_SEQ) {if (epollEvents & EPOLLIN) {awoken();} else {ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);}} else {const auto& request_it = mRequests.find(seq);if (request_it != mRequests.end()) {const auto& request = request_it->second;int events = 0;if (epollEvents & EPOLLIN) events |= EVENT_INPUT;if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;if (epollEvents & EPOLLERR) events |= EVENT_ERROR;if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;mResponses.push({.seq = seq, .events = events, .request = request});} else {ALOGW("Ignoring unexpected epoll events 0x%x for sequence number %" PRIu64" that is no longer registered.",epollEvents, seq);}}...}...
}

这里直接贴出了整个调用链,前面的调用我们都不关心,因为最终还是把timeoutMillis传到了Looper#pollOnceInner中,省略了很多细节,只保留了我们关注的东西。epoll_wait函数,等待文件描述符(fd)上的事件,eventItems存储了发生的事件,timeoutMills应用传下来的超时时间。
1、timeoutMillis 为 -1,epoll_wait 将无限期地等待事件
2、 timeoutMillis 为 0,epoll_wait 将立即返回,不会阻塞
3、一个整数,超时时间,时间到了,epoll_wait也会返回

然后通过for循环取出eventItems中,上一个事件发生后到此之间所有发生的事件,并且判断是否属于WAKE_EVENT_FD_SEQ,这个fd有点眼熟吧,就是nativeWake中写入的那个fd。然后执行awoken进行唤醒。

epoll Linux底层提供的一个消息监听的接口
1、通过写入fd事件,使得epoll_wait将被唤醒。
2、timeoutMillis时间达到

至此,nativePollOnce我们就分析清楚了,当有事件发生,或者延时事件到达,就会取出期间所有发生的事件,判断事件类型是否属于wake事件,并唤醒应用层的nativePollOnce,让逻辑继续执行。

Ptr

这个东西不知道看到这里是否还记得它是什么?就是上面我们提到的在调用这两个native方法都会传入的数据类型,我们到现在还没有搞懂这个东西是做什么用的。只记得在MessageQueue初始化的过程会通过nativeInit返回这个值上来,并且后续将这个值传递下去。

//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();if (!nativeMessageQueue) {jniThrowRuntimeException(env, "Unable to allocate native queue");return 0;}nativeMessageQueue->incStrong(env);return reinterpret_cast<jlong>(nativeMessageQueue);
}

这里的nativeMessageQueue是一个指针,将指针类型转换为一个长整数类型返回回去。我们知道一个MessageQueue对应一个Looper,即对应一个线程。只有在创建MessageQueue的时候,会执行初始化,native层也会维护一个nativeMassageQueue的对象和应用层的MessageQueue一一对应。这样上层触发唤醒,或者阻塞的时候,下层native可以通过这个ptr拿到之前创建的NativeMessageQueue进行wake或者pollonce的操作了。

总结

1、nativePollOnce会阻塞线程
2、MessageQueue和NativeMessageQueue一一对应
3、nativeWake会写fd触发epoll_wait的监听
4、只有fd属于wake的类型才会唤醒线程
5、nativeWake唤醒的条件比较苛刻(从这里看)

这样一来,Handler的所有内容就基本讲完了,可能还涉及一些其他的内容大家可以自行补充一下,这里就不在多提了。这次Handler的相关内容整理下来,我自己的收获也是蛮多的。

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

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

相关文章

LNMP架构部署及应用

部署LNMP架构流程 1.安装Nginx&#xff08;上传软件包&#xff0c;执行脚本&#xff09; yum -y install pcre-devel zlib-devel gcc gcc useradd -M -s /sbin/nologin nginx tar zxf nginx-1.12.0.tar.gz cd nginx-1.12.0 ./configure --prefix/usr/local/nginx --usernginx…

python原型链污染

python原型链污染 ​ 后面会有跟着Article_kelp慢慢操作的&#xff0c;前面先面向题目学习。 背景&#xff1a; ​ 国赛遇到了这个考点&#xff0c;然后之后的DASCTF夏季挑战赛也碰到了&#xff0c;抓紧粗略学一手&#xff0c;学了JavaScript之后再深究原型链污染。 简介&a…

传输层和网络层的关系,ip协议+ip地址+ip报头字段介绍(4位TOP字段,8位生存时间(ttl)),ip地址和端口号的作用

目录 传输层和网络层的关系 引入 介绍 ip协议 介绍 ip地址 引入 数据传递过程 举例(ip地址的作用) ip报头 格式 4位版本号 ip地址不足的问题 8位服务类型 4位TOP(type of service)字段 最小延时 最大吞吐量 4位首部长度 16位总长度 8位协议号 首部校验和…

《样式设计001:表单的2种提交方式》

描述&#xff1a;在开发小程序过程中&#xff0c;发现一些不错的案例&#xff0c;平时使用也比较多&#xff0c;稍微总结了下经验&#xff0c;以下内容可以直接复制使用&#xff0c;希望对大家有所帮助&#xff0c;废话不多说直接上干货&#xff01; 一&#xff1a;表单的2种…

【强化学习的数学原理】课程笔记--4(随机近似与随机梯度下降,时序差分方法)

目录 随机近似与随机梯度下降Mean estimationRobbins-Monro 算法用 Robbins-Monro 算法解释 Mean estimation用 Robbins-Monro 算法解释 Batch Gradient descent用 SGD 解释 Mean estimation SGD 的一个有趣的性质 时序差分方法Sarsa 算法一个例子 Expected Sarsa 算法n-step S…

电容认识和特点总结

图片 常见的电容名字及特点 名字特点容量和耐压独石电容MLCCMulti layer Ceramic Capacitors (多层陶瓷电容) 常见的贴片电容&#xff0c;容量大于瓷片电容0.5pF~100uF,耐压<100V瓷片/陶瓷电容耐压远高于独石电容,容量小<0.1uf&#xff0c;用于晶振旁路电容滤波铝电解电…

PY32F002B单片机 ISP 串口下载注意事项

一、PY32F002B ISP 串口下载的连接方式 仿真上的 VCC 和 GND 连接到 MCU 的 VCC 和 VSS&#xff0c; 仿真的 TX 接 MCU 的 RX&#xff0c;RX 接 MCU 的 TX。 二、因为 PY32F002B 没有 BOOT&#xff0c;需要用 ISP 串口下载的话需要下载串口引导程序。 下载这个目录下的 IAP…

Python酷库之旅-第三方库Pandas(036)

目录 一、用法精讲 111、pandas.Series.item方法 111-1、语法 111-2、参数 111-3、功能 111-4、返回值 111-5、说明 111-6、用法 111-6-1、数据准备 111-6-2、代码示例 111-6-3、结果输出 112、pandas.Series.xs方法 112-1、语法 112-2、参数 112-3、功能 112-…

几种常用排序算法

1 基本概念 排序是处理数据的一种最常见的操作&#xff0c;所谓排序就是将数据按某字段规律排列&#xff0c;所谓的字段就是数据节点的其中一个属性。比如一个班级的学生&#xff0c;其字段就有学号、姓名、班级、分数等等&#xff0c;我们既可以针对学号排序&#xff0c;也可…

OpenGL-ES 学习(7) ---- VBO EBO 和 VAO

目录 VBO(Vertex Buffer Object)EBO(Element Buffer Object)VAO(Vertex Array Object) VBO(Vertex Buffer Object) EBO(Element Buffer Object) VBO(Vertex Buffer Object) 实际是指顶点缓冲器对象 在 opengl-es 2.0 的编程中&#xff0c;用于绘制图元的顶点数据是从 CPU 传…

暑假第一周学习内容-ZARA仿写

仿写ZARA总结 文章目录 仿写ZARA总结前言无限轮播图分栏控制器与UIScrollViewUIScorllView的协议部分UISegmentedControl的协议部分 自定义cell 前言 本文主要是用来总结仿写ZARA中遇到的一些问题&#xff0c;以及ZARA中学习到的一些新知识。 无限轮播图 这里我们先给出无限…

使用Windows Linux 子系统安装 Tensorflow,并使用GPU环境

在Microsoft Store商店安装Ubuntu 20.04 使用 nvidia-smi 命令查看GPU信息&#xff0c;查看支持的CUDA版本&#xff0c;这里最高支持11.7 安装cuda工具集 进入官网&#xff1a;CUDA Toolkit Archive | NVIDIA Developer&#xff0c;现在对应版本&#xff0c;点击 配置平台&…

LeNet实验 四分类 与 四分类变为多个二分类

目录 1. 划分二分类 2. 训练独立的二分类模型 3. 二分类预测结果代码 4. 二分类预测结果 5 改进训练模型 6 优化后 预测结果代码 7 优化后预测结果 8 训练四分类模型 9 预测结果代码 10 四分类结果识别 1. 划分二分类 可以根据不同的类别进行多个划分&#xff0c;以…

Unity运行时节点编辑器——互动电影案例

Unity运行时节点编辑器——互动电影案例 引子 最近需要做一个互动电影的小项目&#xff0c;需求很简单&#xff0c;就是有一堆的视频&#xff0c;然后在某视频播放完的时候&#xff0c;让观众做一个选择题&#xff0c;然后根据观众做出的选择&#xff0c;继续播放不同的视频&…

科研绘图系列:R语言分割小提琴图(Split-violin)

介绍 分割小提琴图(Split-violin plot)是一种数据可视化工具,它结合了小提琴图(violin plot)和箱线图(box plot)的特点。小提琴图是一种展示数据分布的图形,它通过在箱线图的两侧添加曲线来表示数据的密度分布,曲线的宽度表示数据点的密度。而分割小提琴图则是将小提…

【题解】2014年408计网真题

15.【2014统考真题】使用浏览器访问某大学的Web网站主页时&#xff0c;不可能使用到的协议是&#xff08;&#xff09;。A. PPP B. ARP C. UDP D. SMTP 我们逐一分析给定的选项&#xff1a; A. PPP&#xff08;Point-to-Point Protocol&#xff0c;点对点协议&#xff09;&…

Python 模块导入方式

在Python 中&#xff0c;导入外部模块有2种方式 以 Pyhton 自带的 time 模块 为例&#xff1a; 使用 import time 导入方式 import time print(time.ctime()) 注意事项&#xff1a; time 模块导入后&#xff0c;使用以下格式来调用模块中的函数: 模块名.函数名 如果导入的模…

绿色算力|暴雨服务器用芯片筑起“十四五”转型新篇章

面对全球气候变化、技术革新以及能源转型的新形势&#xff0c;发展低碳、高效的绿色算力不仅是顺应时代的要求&#xff0c;更是我国建设数字基础设施和展现节能减碳大国担当的重要命题&#xff0c;在此背景下也要求在提升算力规模和性能的同时&#xff0c;积极探索推动算力基础…

day2加餐 Go 接口型函数的使用场景

文章目录 问题价值使用场景其他语言类似特性 问题 在 动手写分布式缓存 - GeeCache day2 单机并发缓存 这篇文章中&#xff0c;有一个接口型函数的实现&#xff1a; // A Getter loads data for a key. type Getter interface {Get(key string) ([]byte, error) }// A Getter…

【iOS】APP仿写——网易云音乐

网易云音乐 启动页发现定时器控制轮播图UIButtonConfiguration 发现换头像 我的总结 启动页 这里我的启动页是使用Xcode自带的启动功能&#xff0c;将图片放置在LaunchScreen中即可。这里也可以通过定时器控制&#xff0c;来实现启动的效果 效果图&#xff1a; 这里放一篇大…