我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android nativePollOnce函数解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android nativePollOnce函数解析

nativePollOnce的实现函数是android_os_MessageQueue_nativePollOnce,代码如下:

android_os_MessageQueue.cpp


static void android_os_MessageQueue_nativePollOnce(JNIEnv*env, jobject obj,
    jintptr, jint timeoutMillis)
   NativeMessageQueue*nativeMessageQueue =
              reinterpret_cast<NativeMessageQueue*>(ptr);
  //取出NativeMessageQueue对象,并调用它的pollOnce
  nativeMessageQueue->pollOnce(timeoutMillis);
}
//分析pollOnce函数
void NativeMessageQueue::pollOnce(inttimeoutMillis) {
  mLooper->pollOnce(timeoutMillis); //重任传递到Looper的pollOnce函数
}

Looper的pollOnce函数如下:

Looper.cpp


inline int pollOnce(int timeoutMillis) {
    return pollOnce(timeoutMillis, NULL, NULL, NULL);
}

上面的函数将调用另外一个有4个参数的pollOnce函数,这个函数的原型如下:
int pollOnce(int timeoutMillis, int* outFd, int*outEvents, void** outData)
其中:

  1. timeOutMillis参数为超时等待时间。如果为-1,则表示无限等待,直到有事件发生为止。如果值为0,则无需等待立即返回。
  2. outFd用来存储发生事件的那个文件描述符。
  3. outEvents用来存储在该文件描述符[[1]上发生了哪些事件,目前支持可读、可写、错误和中断4个事件。这4个事件其实是从epoll事件转化而来。后面我们会介绍大名鼎鼎的epoll。
  4. outData用于存储上下文数据,这个上下文数据是由用户在添加监听句柄时传递的,它的作用和pthread_create函数最后一个参数param一样,用来传递用户自定义的数据。

另外,pollOnce函数的返回值也具有特殊的意义,具体如下:

  • 当返回值为ALOOPER_POLL_WAKE时,表示这次返回是由wake函数触发的,也就是管道写端的那次写事件触发的。
  • 返回值为ALOOPER_POLL_TIMEOUT表示等待超时。
  • 返回值为ALOOPER_POLL_ERROR,表示等待过程中发生错误。

返回值为ALOOPER_POLL_CALLBACK,表示某个被监听的句柄因某种原因被触发。这时,outFd参数用于存储发生事件的文件句柄,outEvents用于存储所发生的事件。
上面这些知识是和epoll息息相关的。
提示查看Looper的代码会发现,Looper采用了编译选项(即#if和#else)来控制是否使用epoll作为I/O复用的控制中枢。鉴于现在大多数系统都支持epoll,这里仅讨论使用epoll的情况。

1.epoll基础知识介绍

epoll机制提供了Linux平台上最高效的I/O复用机制,因此有必要介绍一下它的基础知识。
从调用方法上看,epoll的用法和select/poll非常类似,其主要作用就是I/O复用,即在一个地方等待多个文件句柄的I/O事件。
下面通过一个简单例子来分析epoll的工作流程。

epoll工作流程分析案例


 
int epollHandle = epoll_create(10);


 structepoll_event listenEvent; //先定义一个event
 
 listenEvent.events= EPOLLIN;//指定该句柄的可读事件
 //epoll_event中有一个联合体叫data,用来存储上下文数据,本例的上下文数据就是句柄自己
 listenEvent.data.fd= listenEvent;

 epoll_ctl(epollHandle,EPOLL_CTL_ADD,listener,&listenEvent);
 
struct epoll_eventresultEvents[10];
 inttimeout = -1;
 while(1)
 {
  
 int nfds= epoll_wait(epollHandle, resultEvents, 10, timeout);
 if(nfds== -1)
 {
  // epoll_wait发生了错误
 }
 elseif(nfds == 0)
 {
  //发生超时,期间没有发生任何事件
 }
 else
 {
  //resultEvents用于返回那些发生了事件的信息
  for(int i = 0; i < nfds; i++)
  {
    struct epoll_event & event =resultEvents[i];
    if(event & EPOLLIN)
   {
     
   }
    .......//其他处理 
  }
 }

}

epoll整体使用流程如上面代码所示,基本和select/poll类似,不过作为Linux平台最高效的I/O复用机制,这里有些内容供读者参考,
epoll的效率为什么会比select高?其中一个原因是调用方法。每次调用select时,都需要把感兴趣的事件复制到内核中,而epoll只在epll_ctl进行加入的时候复制一次。另外,epoll内部用于保存事件的数据结构使用的是红黑树,查找速度很快。而select采用数组保存信息,不但一次能等待的句柄个数有限,并且查找起来速度很慢。当然,在只等待少量文件句柄时,select和epoll效率相差不是很多,但笔者还是推荐使用epoll。
epoll等待的事件有两种触发条件,一个是水平触发(EPOLLLEVEL),另外一个是边缘触发(EPOLLET,ET为Edge Trigger之意),这两种触发条件的区别非常重要。读者可通过man epoll查阅系统提供的更为详细的epoll机制。
最后,关于pipe,还想提出一个小问题供读者思考讨论:
为什么Android中使用pipe作为线程间通讯的方式?对于pipe的写端写入的数据,读端都不感兴趣,只是为了简单的唤醒。POSIX不是也有线程间同步函数吗?为什么要用pipe呢?
关于这个问题的答案,可参见笔者一篇博文“随笔之如何实现一个线程池”。

2. pollOnce函数分析

下面分析带4个参数的pollOnce函数,代码如下:

Looper.cpp


int Looper::pollOnce(int timeoutMillis, int*outFd, int* outEvents,
void** outData) {
  intresult = 0;
  for (;;){ //一个无限循环
  //mResponses是一个Vector,这里首先需要处理response
    while (mResponseIndex < mResponses.size()) {
      const Response& response = mResponses.itemAt(mResponseIndex++);
      ALooper_callbackFunc callback = response.request.callback;
      if (!callback) {//首先处理那些没有callback的Response
        int ident = response.request.ident; //ident是这个Response的id
        int fd = response.request.fd;
        int events = response.events;
        void* data = response.request.data;
        ......
        if (outFd != NULL) *outFd = fd;
        if (outEvents != NULL) *outEvents = events;
        if (outData != NULL) *outData = data;
        //实际上,对于没有callback的Response,pollOnce只是返回它的
       //ident,并没有实际做什么处理。因为没有callback,所以系统也不知道如何处理
        return ident;
      }
    }
 
    if(result != 0) {
     if (outFd != NULL) *outFd = 0;
      if (outEvents != NULL) *outEvents = NULL;
      if (outData != NULL) *outData = NULL;
      return result;
    }
    //调用pollInner函数。注意,它在for循环内部
    result = pollInner(timeoutMillis);
  }
}

初看上面的代码,可能会让人有些丈二和尚摸不着头脑。但是把pollInner函数分析完毕,大家就会明白很多。pollInner函数非常长,把用于调试和统计的代码去掉,结果如下:

Looper.cpp


int Looper::pollInner(int timeoutMillis) {
  
  if(timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
    ......//根据Native Message的信息计算此次需要等待的时间
    timeoutMillis= messageTimeoutMillis;
   }
  intresult = ALOOPER_POLL_WAKE;
  mResponses.clear();
  mResponseIndex = 0;
#ifdef LOOPER_USES_EPOLL //我们只讨论使用epoll进行I/O复用的方式
  structepoll_event eventItems[EPOLL_MAX_EVENTS];
  //调用epoll_wait,等待感兴趣的事件或超时发生
  inteventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS,
                   timeoutMillis);
#else
   ......//使用别的方式进行I/O复用
#endif
  //从epoll_wait返回,这时候一定发生了什么事情
  mLock.lock();
  if(eventCount < 0) { //返回值小于零,表示发生错误
    if(errno == EINTR) {
      goto Done;
    }
    //设置result为ALLOPER_POLL_ERROR,并跳转到Done
    result = ALOOPER_POLL_ERROR;
    gotoDone;
  }
 
  //eventCount为零,表示发生超时,因此直接跳转到Done
  if(eventCount == 0) {
   result = ALOOPER_POLL_TIMEOUT;
    gotoDone;
  }
#ifdef LOOPER_USES_EPOLL
  //根据epoll的用法,此时的eventCount表示发生事件的个数
  for (inti = 0; i < eventCount; i++) {
    intfd = eventItems[i].data.fd;
    uint32_t epollEvents = eventItems[i].events;
    
    if(fd == mWakeReadPipeFd) {
      if (epollEvents & EPOLLIN) {
        //awoken函数直接读取并清空管道数据,读者可自行研究该函数
        awoken();
      }
     ......
    }else {
      
      ssize_t requestIndex = mRequests.indexOfKey(fd);
      if (requestIndex >= 0) {
        int events = 0;
        //将epoll返回的事件转换成上层LOOPER使用的事件
        if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
        if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
        if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
        if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
        //每处理一个Request,就相应构造一个Response
        pushResponse(events, mRequests.valueAt(requestIndex));
      } 
      ......
    }
  }
Done: ;
#else
   ......
#endif
  //除了处理Request外,还处理Native的Message
  mNextMessageUptime = LLONG_MAX;
  while(mMessageEnvelopes.size() != 0) {
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
    const MessageEnvelope& messageEnvelope =mMessageEnvelopes.itemAt(0);
    if(messageEnvelope.uptime <= now) {
      {
        sp<MessageHandler> handler = messageEnvelope.handler;
        Message message = messageEnvelope.message;
        mMessageEnvelopes.removeAt(0);
        mSendingMessage = true;
        mLock.unlock();
        //调用Native的handler处理Native的Message
        //从这里也可看出NativeMessage和Java层的Message没有什么关系
        handler->handleMessage(message);
      }
      mLock.lock();
      mSendingMessage = false;
      result = ALOOPER_POLL_CALLBACK;
    }else {
      mNextMessageUptime = messageEnvelope.uptime;
      break;
    }
  }
 
  mLock.unlock();
  //处理那些带回调函数的Response
  for(size_t i = 0; i < mResponses.size(); i++) {
    const Response& response = mResponses.itemAt(i);
    ALooper_callbackFunc callback = response.request.callback;
    if(callback) {//有了回调函数,就能知道如何处理所发生的事情了
      int fd = response.request.fd;
      int events = response.events;
      void* data = response.request.data;
      //调用回调函数处理所发生的事件
      int callbackResult = callback(fd, events, data);
      if (callbackResult == 0) {
        //callback函数的返回值很重要,如果为0,表明不需要再次监视该文件句柄
        removeFd(fd);
      }
      result = ALOOPER_POLL_CALLBACK;
    }
  }
  returnresult;
}

看完代码了,是否还有点模糊?那么,回顾一下pollInner函数的几个关键点:

  • 首先需要计算一下真正需要等待的时间。
  • 调用epoll_wait函数等待。
  • epoll_wait函数返回,这时候可能有三种情况:

发生错误,则跳转到Done处。
超时,这时候也跳转到Done处。
epoll_wait监测到某些文件句柄上有事件发生。

  • 假设epoll_wait因为文件句柄有事件而返回,此时需要根据文件句柄来分别处理:

如果是管道读这一端有事情,则认为是控制命令,可以直接读取管道中的数据。
如果是其他FD发生事件,则根据Request构造Response,并push到Response数组中。

  • 真正开始处理事件是在有Done标志的位置。

首先处理Native的Message。调用Native Handler的handleMessage处理该Message。
处理Response数组中那些带有callback的事件。
上面的处理流程还是比较清晰的,但还是有个一个拦路虎,那就是mRequests,下面就来清剿这个拦路虎。

3.添加监控请求

添加监控请求其实就是调用epoll_ctl增加文件句柄。下面通过从Native的Activity找到的一个例子来分析mRequests。

android_app_NativeActivity.cpp


static jint
loadNativeCode_native(JNIEnv* env, jobject clazz,jstring path,
             jstring funcName,jobject messageQueue,
             jstring internalDataDir, jstring obbDir,
             jstring externalDataDir, int sdkVersion,
             jobject jAssetMgr, jbyteArraysavedState)
{
 ......
 
 code->looper->addFd(code->mainWorkRead,0,
             ALOOPER_EVENT_INPUT,mainWorkCallback, code);
 ......
}

Looper的addFd代码如下所示:

Looper.cpp


int Looper::addFd(int fd, int ident, int events,
           ALooper_callbackFunccallback, void* data) {
  if (!callback) {
     //判断该Looper是否支持不带回调函数的文件句柄添加。一般不支持,因为没有回调函数
     //Looper也不知道如何处理该文件句柄上发生的事情
     if(! mAllowNonCallbacks) {
      return -1;
    }
   ......
  }
 
  #ifdefLOOPER_USES_EPOLL
  intepollEvents = 0;
  //将用户的事件转换成epoll使用的值
  if(events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
  if(events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
 
  {
    AutoMutex _l(mLock);
    Request request; //创建一个Request对象
    request.fd = fd; //保存fd
    request.ident = ident; //保存id
    request.callback = callback; //保存callback
    request.data = data; //保存用户自定义数据
 
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event));
    eventItem.events = epollEvents;
    eventItem.data.fd = fd;
    //判断该Request是否已经存在,mRequests以fd作为key值
    ssize_t requestIndex = mRequests.indexOfKey(fd);
    if(requestIndex < 0) {
      //如果是新的文件句柄,则需要为epoll增加该fd
      int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
      ......
      //保存Request到mRequests键值数组
      mRequests.add(fd, request);
    }else {
      //如果之前加过,那么就修改该监听句柄的一些信息
      int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, &eventItem);
      ......
      mRequests.replaceValueAt(requestIndex, request);
    }
  }
#else
  ......
#endif
  return1;
}

4.处理监控请求

我们发现在pollInner函数中,当某个监控fd上发生事件后,就会把对应的Request取出来调用。
pushResponse(events, mRequests.itemAt(i));
此函数如下:

Looper.cpp


void Looper::pushResponse(int events, constRequest& request) {
  Responseresponse;
  response.events = events;
  response.request = request; //其实很简单,就是保存所发生的事情和对应的Request
  mResponses.push(response); //然后保存到mResponse数组
}

根据前面的知识可知,并不是单独处理Request,而是需要先收集Request,等到Native Message消息处理完之后再做处理。这表明,在处理逻辑上,Native Message的优先级高于监控FD的优先级。
下面我们来了解如何添加Native的Message。

5. Native的sendMessage

Android 2.2中只有Java层才可以通过sendMessage往MessageQueue中添加消息,从4.0开始,Native层也支持sendMessage了[2]。sendMessage的代码如下:

Looper.cpp


void Looper::sendMessage(constsp<MessageHandler>& handler,
               constMessage& message) {
  //Native的sendMessage函数必须同时传递一个Handler
  nsecs_tnow = systemTime(SYSTEM_TIME_MONOTONIC);
  sendMessageAtTime(now, handler, message); //调用sendMessageAtTime
}
void Looper::sendMessageAtTime(nsecs_t uptime,
                   const sp<MessageHandler>& handler,
                   const Message& message) {
  size_t i= 0;
  { //acquire lock
    AutoMutex _l(mLock);
 
    size_t messageCount = mMessageEnvelopes.size();
    //按时间排序,将消息插入到正确的位置上
    while (i < messageCount &&
        uptime >= mMessageEnvelopes.itemAt(i).uptime) {
      i += 1;
    }
    
    MessageEnvelope messageEnvelope(uptime, handler, message);
    mMessageEnvelopes.insertAt(messageEnvelope, i, 1);
    //mSendingMessage和Java层中的那个mBlocked一样,是一个小小的优化措施
    if(mSendingMessage) {
      return;
    }
  }
  //唤醒epoll_wait,让它处理消息
  if (i ==0) {
    wake();
  }
}

1.注意,以后文件描述符也会简写为文件句柄。 ↩︎

2.我们这里略过了Android2.2到Android 4.0之间几个版本中的代码变化。 ↩︎

以上就是Android nativePollOnce函数解析的详细内容,更多关于Android nativePollOnce函数的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android nativePollOnce函数解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

android JSON解析数据 android解析天气预报

概要 笔者近期做到对天气预报JSON数据解析,在此小记。 天气预报接口:http://wthrcdn.etouch.cn/weather_minicitykey=101200101 JSON数据如下:{"desc": "OK","statu
2022-06-06

android解析JSON数据

JSONObject的使用 一、 JSON对象的使用:String content = "{'username': 'linux', 'password': '123456'}"; JSONObject jsonObject = new
2022-06-06

python中append函数解析

这篇文章将为大家详细讲解有关python中append函数解析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Python的优点有哪些1、简单易用,与C/C++、Java、C# 等传统语言相比,Python
2023-06-14

pcre函数详细解析

PCRE提供了19个接口函数,为了简单介绍,使用PCRE内带的测试程序(pcretest.c)示例用法
2022-11-15

Python-torch 之torch.clamp() 函数解析

torch.clamp()函数用于对输入张量进行截断操作,将张量中的每个元素限制在指定的范围内,这篇文章主要介绍了Pythontorch之torch.clamp()函数,需要的朋友可以参考下
2023-05-20

Android解析JSON数据的方法分析

本文实例讲述了Android解析JSON数据的方法。分享给大家供大家参考,具体如下: JSON作为一种“轻量”的数据结构传递数据,在JS中有广泛的应用 Google公司对JSON的解析提供了gson.jar这个包,它不依赖于其他任何JAR包
2022-06-06

Python函数的参数列表解析

这篇文章主要介绍了Python函数的参数列表,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-12-19

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录