返回顶部
首页 > 资讯 > 移动开发 >AndroidHandler中的休眠唤醒实现详解
  • 143
分享到

AndroidHandler中的休眠唤醒实现详解

AndroidHandler休眠唤醒AndroidHandler 2023-01-13 15:01:53 143人浏览 独家记忆
摘要

目录Handler中的奇奇怪怪linux相关eventfd相关操作eventfd demoEpollepoll apiint epoll_create(int size)int ep

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.cpp
      static 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 进入休眠的线程。

结束

回答刚开始的疑问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 Handler中的休眠唤醒实现详解的详细内容,更多关于Android Handler休眠唤醒的资料请关注编程网其它相关文章!

--结束END--

本文标题: AndroidHandler中的休眠唤醒实现详解

本文链接: https://lsjlt.com/news/177659.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • AndroidHandler中的休眠唤醒实现详解
    目录Handler中的奇奇怪怪Linux相关eventfd相关操作eventfd demoEpollepoll APIint epoll_create(int size)int ep...
    99+
    2023-01-13
    Android Handler休眠唤醒 Android Handler
  • android休眠唤醒机制怎么实现
    Android的休眠和唤醒机制是通过系统级的PowerManager来实现的。下面是一个简单的示例代码,演示如何使用PowerMan...
    99+
    2023-10-20
    android
  • windows休眠后无法唤醒如何解决
    本文小编为大家详细介绍“windows休眠后无法唤醒如何解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“windows休眠后无法唤醒如何解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。windows休眠后无...
    99+
    2023-06-30
  • win11休眠后黑屏无法唤醒如何解决
    如果Win11休眠后黑屏无法唤醒,可以尝试以下解决方法:1. 检查硬件连接:首先确保所有的硬件设备(如显示器、键盘、鼠标等)都正确连...
    99+
    2023-08-30
    win11
  • 电脑休眠太久唤醒不了如何解决
    如果您的电脑休眠太久无法唤醒,可以尝试以下方法解决:1. 按下电源按钮:按住电源按钮5秒钟,强制关闭电脑,然后再按下电源按钮重新启动...
    99+
    2023-08-24
    电脑
  • win7休眠后黑屏没法唤醒该怎么办win7系统软件休眠后黑屏没法唤醒解决办法
    许多win7客户都遇上了计算机休眠后黑屏没法唤醒的状况,那麼这样的事情要怎么解决呢?你先开启操作面板,进到硬件配置和响声,随后在电源选项页面中寻找变更高端开关电源设定,进到以后进行睡眠质量项,以后将“容许应用唤醒计时器”和“容许混合睡眠”都...
    99+
    2023-07-12
  • win11自动休眠后屏幕唤不醒如何解决
    本篇内容介绍了“win11自动休眠后屏幕唤不醒如何解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!方法一: 首先右键开始菜单,打开“运行”...
    99+
    2023-07-02
  • Ubuntu系统启动休眠及无法唤醒问题的解决方法
    这篇文章主要讲解了“Ubuntu系统启动休眠及无法唤醒问题的解决方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Ubuntu系统启动休眠及无法唤醒问题的解决方法”吧!家都知道 Window...
    99+
    2023-06-13
  • win7睡眠后无法唤醒的具体解决方法
      一、检查硬件是否支持   1、开始 -> 在搜索程序和文件框里打上 cmd -> 回车。   2、弹出一个打命令的窗口。在里面输入 powercfg -a 按回车。   3、运行的结果我们看一下:  ...
    99+
    2023-05-29
    win7 睡眠 唤醒 解决 方法
  • Java使用Condition实现精准唤醒线程详解
    目录Condition简要介绍Condition里的主要方法使用Condition的DemoCondition简要介绍 Condition是一个接口,创建Condition的实例不能...
    99+
    2023-02-28
    Java Condition精准唤醒线程 Java Condition唤醒线程 Java Condition
  • win10电脑睡眠后出现黑屏,无法唤醒的解决方法
    这篇文章主要介绍win10电脑睡眠后出现黑屏,无法唤醒的解决方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!解决方法/步骤:打开win10系统的控制面板,随后选择电源选项进入。在打开的界面中选中当前正在使用的电源计...
    99+
    2023-06-27
  • win8.1系统无法连接无线必须休眠唤醒电脑后才能连接解决方法
    操作win8.1系统过程中会遇到各种奇葩问题,比如最近有用户反馈笔记本Win8.1系统开机后无线连接不上,尝试多次之后还是无效,必须要休眠一下电脑唤醒后才能连接,每次这样会比较麻烦,那么该如何解决此问题呢?...
    99+
    2022-06-04
    解决方法 系统 电脑
  • 详解C# 线程的挂起与唤醒
    目录一,AutoResetEvent类二,ManualResetEvent     如果说C#和C++有什么不同,博主不得不说,对于异步的支持程度是C#...
    99+
    2024-04-02
  • 详解Java线程的创建及休眠
    目录一、进程vs线程 二、线程的创建方式三、实现Runnable接口的方式(3种)四、实现Callable接口的方式(1种)五、线程的休眠六、线程优先级七、守护线程一、进程vs线程 ...
    99+
    2024-04-02
  • linux 定时休眠的实现思路
    最近公司规定晚上走人后必须关闭电脑,但是像我们这样的人,经常会忘记了关闭电脑,而且关闭电脑之后再恢复工作环境也是件挺麻烦的事情,无奈之下只能折腾一下,让linux定时休眠了。 休眠的类型 目前大概由三种类型的休眠: su...
    99+
    2022-06-04
    linux 定时休眠
  • Ubuntu 16.04睡眠后唤醒网络连接不上的解决方法
    这篇文章给大家分享的是有关Ubuntu 16.04睡眠后唤醒网络连接不上的解决方法的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。方法如下在Ubuntu 桌面左侧的启动器栏上点击终端的图标,或者简单地按下:Ctrl...
    99+
    2023-06-13
  • 如何解决Ubuntu14.04唤醒睡眠后鼠标键盘出现卡死情况
    本篇内容介绍了“如何解决Ubuntu14.04唤醒睡眠后鼠标键盘出现卡死情况”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!  解决方法:  ...
    99+
    2023-06-13
  • 怎么在JavaScript中实现休眠或等待
    这篇文章将为大家详细讲解有关怎么在JavaScript中实现休眠或等待,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。JavaScript有什么特点1、js属于一种解释性脚本语言;2、在绝大多...
    99+
    2023-06-14
  • Java唤醒本地应用的两种方法详解
    目录引言1. Runtime使用方式2. ProcessBuilder使用方式3. 小结引言 作为一个后端同学,经常被安全的小伙伴盯上,找一找安全漏洞;除了常说的注入之外,还有比较吓...
    99+
    2022-11-13
    Java唤醒本地应用 Java本地应用
  • Go语言文档解读:time.Sleep函数实现休眠
    Go语言文档解读:time.Sleep函数实现休眠,需要具体代码示例时间是计算机编程中不可或缺的一部分,经常需要在代码中控制线程或协程的执行时间。在Go语言中,time包提供了一系列函数来处理时间相关的操作,其中一个常用的函数就是time....
    99+
    2023-11-04
    文档解读 关键词提取: Go语言 timeSleep函数
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作