Handler中的奇奇怪怪

背景

了解Handler原理时,有一个疑问handler中的休眠/唤醒不用Java中wait和notify呢,而是调用native方法(nativePollOnce/nativeWake)呢,奇奇怪怪 的又得需要学起来,没事找事的一天嘛。

这样不行吗?wait/notify 伪代码

MessageQueue.java
//调用MessageQueue的next方法获取消息
Message next() {、、、synchronized (this) {//这时候检查队列有没有消息,没有消息调用this.wait()等待  if (message == null) {this.wait();}if(有消息但消息未到期){this.wait(time);}}、、、}
//调用MessageQueue.enqueueMessage()添加消息
enqueueMessage(Message message) {、、、synchronized (this) {//消息加入队列后会调用this.notity()唤醒next()方法if (message != null) {this.notify();}}、、、}

在学习nativePollOnce/nativeWake前,还需要对Linux相关的知识熟悉一下。

Linux相关

eventfd

eventfd 是从内核2.6.22开始支持的一种新的事件等待/通知机制。用来通知事件的文件描述符,它不仅可以用于进程间的通信,还可以用户内核发信号给用户层的进程。简而言之:eventfd 就是用来触发事件通知,它只有一个创建方法:

int eventfd(unsigned int initval, int flags); 表示创建一个 eventfd 文件并返回文件描述符

  • 参数:initval, 初始值

  • 参数:flags

    • EFD_CLOEXEC 会自动关闭这个文件描述符。
    • EFD_NONBLOCK 执行 read / write 操作时,不会阻塞。
    • EFD_SEMAPHORE count 递减 1。

相关操作

  • write(): 其实是执行 add 操作,累加 count值。
  • read(): 根据设置不同的flags标记,读取到不同的值

EFD_SEMAPHORE:读到的值为 1,同时 count 值递减 1。
其他的都是:读取 count 值后置 0

阿西吧什么乱七八糟的,别急看看这个下面这个Demo;

eventfd demo

  #include <cstdlib>#include <inttypes.h>#include <iostream>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string>#include <sys/eventfd.h>#include <unistd.h>using namespace std;int main(int argc, char* argv[]) {int event_fd;if (argc < 2) {std::cout << "please input llegal argv " << endl;exit(EXIT_FAILURE);}event_fd = eventfd(0, EFD_NONBLOCK);if (event_fd == -1) {std::cout << "create evebtFd fail" << endl;exit(EXIT_FAILURE);}switch (fork()) {case 0:for (int j = 1; j < argc; j++) {long u = atoi(argv[j]);printf("Child writing %lu to efd\n", u);write(event_fd, &u, sizeof(long));}printf("Child completed write loop\n");exit(EXIT_SUCCESS);default:sleep(2);long u;printf("Parent about to read\n");read(event_fd, &u, sizeof(long));printf("Parents first read %lu from efd\n", u);long u2;read(event_fd, &u2, sizeof(long));printf("Parents second read %lu from efd\n", u2);exit(EXIT_SUCCESS);}}

⚠️ #include <sys/eventfd.h> 是在Linux操作系统中的,在Mac电脑中是找不到包的,需要装虚拟机或者其他的C++开发软件包,这里推荐一个在线免费的编译C++的软件Lightly

Q eventfd和socket、pipe、fd_set、有什么区别和联系?

Epoll

epoll是Linux内核为处理大批量文件描述符而改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中,只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。提高应用程序效率。 来源自百度百科

epoll API

int epoll_create(int size)

创建 eventpoll 对象,返回一个 epfd,即 eventpoll 句柄。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

对eventpoll执行的操作,返回值:成功 0;失败 -1

  • epfd 对一个 eventPoll 进行操作
  • op 表示要执行的操作,包括 EPOLL_CTL_ADD (添加)、EPOLL_CTL_DEL (删除)、EPOLL_CTL_MOD (修改);
  • fd 表示被监听的文件描述符;
  • event 表示要被监听的事件,包括:
    • EPOLLIN(表示被监听的fd有可以读的数据)
    • EPOLLOUT(表示被监听的fd有可以写的数据)
    • EPOLLPRI(表示有可读的紧急数据)
    • EPOLLERR(对应的fd发生异常)
    • EPOLLHUP(对应的fd被挂断)
    • EPOLLET(设置EPOLL为边缘触发)
    • EPOLLONESHOT(只监听一次)

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

返回值:监听到的产生的事件数 等待 epfd 监听的 fd 所产生对应的事件。

  • epfd 表示 eventpoll句柄;
  • events 表示回传处理事件的数组;
  • maxevents 表示每次能处理的最大事件数;
  • timeout:等待 IO 的超时时间,-1 表示一直阻塞直到来 IO 被唤醒,大于 0 表示阻塞指定的时间后被唤醒

epoll 使用示例

创建一个管道,使用 epoll 监听管道读端,然后进入阻塞:

       #include <iostream>#include <stdio.h>#include <string>#include <sys/epoll.h>#include <sys/eventfd.h>#include <unistd.h>using namespace std;int main(int argc, char* argv[]) {if (argc < 2) {exit(EXIT_FAILURE);}int event_fd;int epoll_fd;event_fd = eventfd(0, EFD_NONBLOCK);if (event_fd == -1) {std::cout << "create evebtFd fail";exit(EXIT_FAILURE);}epoll_fd = epoll_create(8);if (epoll_fd < 0) {std::cout << "create epollFd  fail";}struct epoll_event read_event;read_event.events = EPOLLIN;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &read_event);switch (fork()) {case 0:for (int j = 1; j < argc; j++) {long u = atoi(argv[j]);sleep(1);printf("Child writing %lu to efd\n", u);write(event_fd, &u, sizeof(long));}printf("Child completed write loop\n");exit(EXIT_SUCCESS);default:printf("Parent about to read\n");struct epoll_event events[16];int ret;while (1) {ret = epoll_wait(epoll_fd, events, 1, -1);printf("Parent  epoll_wait return ret : %d\n", ret);if (ret > 0) {long u;read(event_fd, &u, sizeof(long));printf("Parents  read %lu from efd\n", u);}}exit(EXIT_SUCCESS);}}

结果

Handler 中的 epoll 源码分析

主要分析 MessageQueue.java 中的三个 native 函数:

           private native static long nativeInit(); //初始化private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞private native static void nativeWake(long ptr); //唤醒

nativeInit 返回long,这是为什么? 预知一下,或许这个可以解答我们的问题

nativeInit

首先来看 nativeInit 方法,nativeInit 在 MessageQueue 构造函数中被调用,其返回了一个底层对象的指针:

               MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();   //保存NativeMessageQueue}
               //android_os_MessageQueue.cppstatic jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();...return reinterpret_cast<jlong>(nativeMessageQueue);}

einterpret_cast<type-id> (expression)

type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值
返回值是NativeMessageQueue,而 NativeMessageQueue 初始化时会创建一个底层的 Looper 对象:

  NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {mLooper = Looper::getForThread();if (mLooper == NULL) {mLooper = new Looper(false); Looper::setForThread(mLooper);}}

Looper 的构造函数如下:

  Looper::Looper(bool allowNonCallbacks) :mAllowNonCallbacks(allowNonCallbacks), ...{mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);...rebuildEpollLocked();}

有没有熟悉的感觉,这和我们的Epoll的demo很相似,首先通过创建eventFd, ,专门用于事件通知。接着来看 rebuildEpollLocked 方法:

  void Looper::rebuildEpollLocked() {mEpollFd = epoll_create(EPOLL_SIZE_HINT);struct epoll_event eventItem;memset(& eventItem, 0, sizeof(epoll_event));eventItem.events = EPOLLIN;eventItem.data.fd = mWakeEventFd;int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem); ...}

可以看到我们已经熟悉的 epoll 操作了:通过 epoll_create 创建 epoll 对象,然后调用 epoll_ctl 添加 mWakeEventFd 为要监听的文件描述符。

nativePollOnce

之前学习 Handler 机制时多次看到过 nativePollOnce 方法,也知道它会进入休眠,下面就具体看看它的原理。对应的底层调用同样是在 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 中,接着来看 Looper 的 pollOnce 方法:

           int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {for (;;) {...result = pollInner(timeoutMillis);}}int Looper::pollInner(int timeoutMillis) {...struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);...

至此通过调用 epoll_wait 方法,当前线程进入休眠,等待被唤醒。

nativeWake

最后来看如何通过 nativeWake 唤醒线程,首先是 android_os_MessageQueue.cpp 中:

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

与 nativeInit、nativePollOnce 一样,最终实现都是在 Looper.cpp 中,Looper 的 wake 方法如下:

void Looper::wake() {uint64_t inc = 1;ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));if (nWrite != sizeof(uint64_t)) {if (errno != EAGAIN) {LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",mWakeEventFd, strerror(errno));}}
}

其中关键逻辑是对 mWakeEventFd 发起写入操作,从而唤醒 nativePollOnce 中通过 epoll_wait 进入休眠的线程。

Object.wait/notify源码分析

TODO 这里的源码分析留着以后吧

结束

回答刚开始的疑问handler中的休眠/唤醒不用Java中wait和notify呢,而是调用nativePollOnce/nativeWake呢?

MessageQueue.java
//调用MessageQueue的next方法获取消息
Message next() {、、、synchronized (this) {//这时候检查队列有没有消息,没有消息调用this.wait()等待  if (message == null) {this.wait();}if(有消息但消息未到期){this.wait(time);}}、、、}
//调用MessageQueue.enqueueMessage()添加消息
enqueueMessage(Message message) {、、、synchronized (this) {//消息加入队列后会调用this.notity()唤醒next()方法if (message != null) {this.notify();}}、、、}

private native static long nativeInit(); 返回值是nativeMessage对象,阻塞时会将mPtr当成参数nativePollOnce();

如果单纯用object.wait,那对于native层的消息是处理不到的,队列空闲时不能只判断Java层的MessageQueue,nativePollOnce去判断Native层,若大家都空闲,方法会阻塞到native的epoll_wait()方法中,等待唤醒。
单纯用wait和notify,只能处理java层的消息,对于系统的消息不能处理。

Android 中的休眠/唤醒相关推荐

  1. android 触摸 唤醒屏幕,Android中屏幕保持唤醒

    1.锁的类型 PowerManager中各种锁的类型对CPU .屏幕.键盘的影响: PARTIAL_WAKE_LOCK : 保持CPU 运转,屏幕和键盘灯有可能是关闭的. SCREEN_DIM_WAK ...

  2. 20.EC实战 笔记本电脑的休眠唤醒是如何实现的

    文章目录 1. EC什么时候(收到什么命令)之后来执行S0-->S3下电时序? 2. S3(休眠)状态应该保留哪些电源? 3. 控制笔记本进入休眠状态的三种方式 总结: 前言: 最近很多朋友在咨 ...

  3. 一文搞懂ECU休眠唤醒之利器-TJA1145

    前言 首先,小T请教大家几个小小问题,你清楚: 什么是TJA1145吗? 你知道休眠唤醒控制基本逻辑是怎么样的吗? TJA1145又是如何控制ECU进行休眠唤醒的呢? 使用TJA1145时有哪些注意事 ...

  4. android休眠唤醒驱动流程分析【转】

    转自:http://blog.csdn.net/hanmengaidudu/article/details/11777501 标准linux休眠过程: l        power managemen ...

  5. android8-java-代码中休眠唤醒-sleep/wake

    因为java代码中使用到了休眠唤醒功能,于是 搜索了一堆资料,没一个合适我的,很生气,于是自己写一个实用的 1.休眠 mKeyEvent_time=SystemClock.uptimeMillis() ...

  6. android 休眠唤醒机制分析(一)

    Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作.wake_lock一般在关闭lcd.tp但系统 ...

  7. android 休眠唤醒驱动流程分析,Android4.0.4休眠唤醒机制分析(基于MSM8260)

    当手机满足一定的条件时,会进入休眠状态.从手机进入休眠到唤醒,主要分为三个阶段: early suspend suspend late resume early suspend执行在休眠前需要完成的一 ...

  8. 计算机休眠唤醒后 网络受限,Windows 10中的睡眠/唤醒/休眠状态后,Wi-Fi网络消失...

    我注意到一个奇怪的事情,就是我全新的联想笔记本电脑从睡眠或休眠状态唤醒后,无法通过Wi-Fi适配器连接互联网.在这种情况下,无线连接状态显示为"无Internet访问"或" ...

  9. rk3399 Android休眠/唤醒分析(2)

    rk3399 Android休眠/唤醒分析(2) 文章目录 rk3399 Android休眠/唤醒分析(2) rk3399休眠唤醒kernel驱动 state_store pm_suspend sus ...

最新文章

  1. SAP Lumira 初探
  2. xampps开启mysql_xampps mysql无法启动
  3. 2015年《大数据》高被引论文Top10文章No.10——我国大数据交易的主要问题及建议...
  4. tcpdf html 支持css吗,TCPDF 5.1 发布,增加对CSS的支持
  5. Java 程序员中位数薪资达 1.45 万,但面试屡屡被拒?
  6. Ubuntu18.04 下面安装docker
  7. 雷林鹏分享:jQuery EasyUI 数据网格 - 设置排序
  8. 前端面试-webpack篇
  9. 深入浅出统计学-第三章
  10. Flutter- Android项目集成flutter模块
  11. OSEK直接网络管理(NM)
  12. 手写一个类似 element-ui 的可输入下拉选择
  13. XYNU—ACM暑假集训第三次测试 贪心算法
  14. android banner设置图片比例,Banner基本使用 2.1.0
  15. OSM数据下载及两种格式转换方法(shp等格式)
  16. 厦大C语言上机1382
  17. Linux学习06--进程
  18. 当前计算机硬盘容量的计量单位是GB,当前计算机硬盘容量的计量单位是GB,它相当于________字节...
  19. Eigen介绍及简单使用
  20. 合泰杯——合泰单片机工程之点亮LED

热门文章

  1. JavaScript面向对象学习构造函数、静态成员和实例成员(二)
  2. 支付宝:沙箱环境简单使用
  3. 重磅!骚年你的屏幕适配方式该升级了,面试真题解析
  4. Anaconda Python版本对应
  5. 如何用普通电池给单片机供电?
  6. 解决美的空调集控器ccm15接入智能家居不显示unique_id的问题
  7. 俞永福内部信谈“阿里收购大麦网”:将整合进入大文娱板块
  8. php制作圆形用户头像——自定义封装类源代码
  9. VScode 官网下载太慢解决方法
  10. 安卓学习UI组件-解决ScrollView嵌套ListView,GridView的冲突