当前位置: 首页 > news >正文

【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.lryc.cn/news/405155.html

相关文章:

  • 静态路由技术
  • SpringBoot缓存注解使用
  • @RequestBody接收到的参数中如何限制List的长度?
  • Linux C语言 54-目录操作
  • Java实战中如何使用多线程(线程池)及其为什么使用?
  • kafka集群搭建-使用zookeeper
  • 【python】Numpy运行报错分析:IndexError与形状不匹配问题
  • 你有多自律就有多自由
  • Codeforces Round 959 (Div. 1 + Div. 2 ABCDEFG 题) 文字讲解+视频讲解
  • WSL2 Centos7 Docker服务启动失败怎么办?
  • 分布式锁-redisson锁重试和WatchDog机制
  • ESP8266模块(2)
  • Docker安装笔记
  • 《昇思25天学习打卡营第21天|Pix2Pix实现图像转换》
  • Python和MATLAB网络尺度结构和幂律度大型图生成式模型算法
  • 在jsPsych中使用Vue
  • 机器学习·概率论基础
  • c生万物系列(面向对象:封装)
  • 当当网数据采集:Scrapy框架的异步处理能力
  • React——useEffect和自定义useUpdateEffect
  • Hadoop大数据处理架构中ODB、DIM、DWD、DWS
  • 【刷题汇总 -- 爱丽丝的人偶、集合、最长回文子序列】
  • 基于vue3 + vite产生的 TypeError: Failed to fetch dynamically imported module
  • 批量自动添加好友,高效拓展人脉圈.
  • Web开发:一个可拖拽的模态框(HTML、CSS、JavaScript)
  • 【深度学习】fooocusapi,docker,inpainting图像
  • 算法017:二分查找
  • 谷粒商城实战笔记-37-前端基础-Vue-基本语法插件安装
  • mybatis中的缓存(一级缓存、二级缓存)
  • 实现自动化采购:食堂采购系统源码开发详解