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>
  #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休眠唤醒的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android线程间通信Handler源码详解

    目录 前言 01. 用法 02.源码 03.结语 前言 在[Android]线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤.那么接下来,我们也按照这三个步骤,从源码中去探析一下它们具体是如何实现的.本篇是关于创建源码的分析. 01. 用法 先回顾一下,在主线程和非主线程是如何创建 Handler 的. //主线程 private val mHandler: Handler = object : Handler(Looper.getMainLo

  • Android Handler runWithScissors 梳理流程解析

    目录 前言 runWithScissors 梳理流程 存在的问题 总结 前言 看 WMS 代码的时候看到了 Handler.runWithScissors 方法,所以来恶补一下 public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPoli

  • Android进阶Handler应用线上卡顿监控详解

    目录 引言 1 Handler消息机制 1.1 方案确认 1.2 Looper源码 1.3 Blockcanary原理分析 1.4 Handler监控的缺陷 2 字节码插桩实现方法耗时监控 2.1 字节码插桩流程 2.2 引入ASM实现字节码插桩 2.3 Blockcanary的优化策略 引言 在上一篇文章中# Android进阶宝典 -- KOOM线上APM监控最全剖析,我详细介绍了对于线上App内存监控的方案策略,其实除了内存指标之外,经常有用户反馈卡顿问题,其实这种问题是最难定位的,因为不

  • Android线程间通信 Handler使用详解

    目录 前言 01.定义 02.使用 第一步.创建 第二步.发送消息 第一种是 post(Runnable) 第二种是 sendMessage(Message) 第三步.处理消息 03.结语 前言 Handler,可谓是面试题中的一个霸主了.在我<面试回忆录>中,几乎没有哪家公司,在面试的时候是不问这个问题的.简单一点,问问使用流程,内存泄漏等问题.复杂一点,纠其源码细节和底层 epoll 机制来盘你.所以其重要性,不言而喻了吧. 那么今天让我们来揭开 Handler 的神秘面纱.为了读者轻松易

  • Android IdleHandler使用方法详解

    正文 在Android中,Handler是一个使用的非常频繁的东西,输入事件机制和系统状态,都通过Handler来进行流转,而在Handler中,有一个很少被人提起但是却很有用的东西,那就是IdleHandler,它的源码如下. /** * Callback interface for discovering when a thread is going to block * waiting for more messages. */ public static interface IdleHa

  • 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 使用

  • Android 开发中使用Linux Shell实例详解

    Android 开发中使用Linux Shell实例详解 引言 Android系统是基于Linux内核运行的,而做为一名Linux粉,不在Android上面运行一下Linux Shell怎么行呢? 最近发现了一个很好的Android Shell工具代码,在这里分享一下. Shell核心代码 import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.

  • Android编程中的消息机制实例详解

    本文实例讲述了Android编程中的消息机制.分享给大家供大家参考,具体如下: 在分析Android消息机制之前,我们先来看一段代码: public class MainActivity extends Activity implements View.OnClickListener { private TextView stateText; private Button btn; @Override public void onCreate(Bundle savedInstanceState)

  • Android开发中滑动分页功能实例详解

    本文实例讲述了Android开发中滑动分页功能.分享给大家供大家参考,具体如下: android UI 往右滑动,滑动到最后一页就自动加载数据并显示 如图: Java代码: package cn.anycall.ju; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import andro

  • Android Handler leak分析及解决办法详解

    Android Handler leak 分析及解决办法 In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread's MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be ret

  • 如何在原有Android项目中快速集成React Native详解

    前言 RN经过一段时间发展,已经有充分数量的人尝试过了,就我身边就有几批,褒贬也不一: ① 做UI快 ② 还是有很多限制,不如原生Native ③ 入门简单,能让前端快速开发App ④ iOS&Android大部分代码通用 ⑤ code-push能做热更新,但是用不好依旧坑 ...... 在得到一些信息后,可以看出,要用RN高效率的做出比较不错的App是有可能的,单看投入度与最初设计是否合理,而且现在关于React Native的各种文档是相当丰富的,所以这个阶段想切入RN可能是一个不错的选择.

  • Android AIDL中Map参数传递的问题详解

    前言 AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言. 我们都知道aidl是支持map作为参数传递的,但前提是map不能是泛型并且数据类型必须是aidl所支持的String,int等的Map参数: interface IMyAidl { void test(Map<String,String> datas); } 本以为这样写就可以正常往下进行了,但是这样会有错,抛出如下异常: 上述错误中首先说明不知道如何

  • Android编程中context及全局变量实例详解

    本文实例讲述了Android编程中context及全局变量的用法.分享给大家供大家参考,具体如下: 今天在研究context的时候,对application和activity context有了一定的了解,下面是从网上复制过来的资料 Application context和Activity context的区别: 这是两种不同的context,也是最常见的两种.第一种中context的生命周期与Application的生命周期相关的,context随着Application的销毁而销毁,伴随ap

  • Android编程中光线传感器的调用方法详解

    本文实例讲述了Android编程中光线传感器的调用方法.分享给大家供大家参考,具体如下: 1.activity如果要使用传感器,就必须实现SensorEventListener接口 2.得到传感器管理对象(sensormanager) 3.使用sensormanager.registerlistener 方法注册指定的传感器 4.在sensoreventlistener 接口中的onsensorchanged和onaccuracychanged方法中完成其他具体工作 public class T

  • Android SDK中的Support兼容包详解

    背景 来自于知乎上邀请回答的一个问题Android中AppCompat和Holo的一个问题?, 看来很多人还是对这些兼容包搞不清楚,那么干脆写篇博客吧. Support Library 我们都知道Android一些SDK比较分裂,为此google官方提供了Android Support Library package 系列的包来保证高版本sdk开发的向下兼容性, 所以你可能经常看到v4,v7,v13这些数字,首先我们就来理清楚这些数字的含义,以及它们之间的区别. support-v4 用在API

随机推荐