开发者

Android Handler中的休眠唤醒实现详解

目录
  • Handler中的奇奇怪怪
  • linux相关
    • eventfd
    • 相关操作
    • eventfd demo
  • Epoll
    • epoll API
      • int epoll_create(int size)
      • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
      • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
      • epoll 使用示例
    • Handler 中的 epoll 源码分析
      • nativeInit
      • nativePollOnce
      • nativeWake
    • 结束

      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>pRdQTpUb;
        #include <IOStream>
        #include &javascriptlt;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);
             android       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

      Android Handler中的休眠唤醒实现详解

      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);
              }
          }
      

      结果

      Android Handler中的休眠唤醒实现详解

      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) {
            javascriptNativeMessageQueue* 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",
       开发者_Go学习                 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层,若大家都空闲,方法会阻塞到natwww.devze.comive的epoll_wait()方法中,等待唤醒。 单纯用wait和notify,只能处理java层的消息,对于系统的消息不能处理。

      以上就是Android Handler中的休眠唤醒实现详解的详细内容,更多关于Android Handler休眠唤醒的资料请关注我们其它相关文章!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜