Native looper 分析

2024-05-07 05:32
文章标签 分析 looper native

本文主要是介绍Native looper 分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://blog.csdn.net/windskier/article/details/6995546

Looper是android中很重要的概念,它是android application端线程间最主要的通信方式,同时它也是线程内部序列化处理的主要方式,Looper的核心其实是一个消息队列,通过不停的处理Looper消息队列中的消息来完成线程间的通信和线程内部序列化操作。任何线程想要使用消息机制特定的操作,那么必须在线程中创建一个Looper,java端的Looper如何使用不介绍了,所有有过android开发经验的人都知道怎么用,这篇文章主要介绍一下Native Looper,看它如何和JAVA层的Looper相互配合完成android中最主要的线程通信机制的。同时写这篇的目的是为了解释文章中事件传递过程中,native Looper是如何被复用实现管道通信的。

    上面说到JAVA Looper的核心其实是一个消息队列,并且我们分析一下Looper.java的代码,并没有任何和Native有关联的数据结构和操作,那么唯一能和Native Looper发生联系的就剩下这个JAVA的消息队列类型MessageQueue了,也果不其然,在MessageQueue中定义了一个成员    

[java] view plain copy
print ?
  1. private int mPtr; // used by native code  
    private int mPtr; // used by native code
它保存着对应的Native的消息队列实例的地址,用一个int类型的成员保存native实例,这是jni开发中常用到的方式。因此MessageQueue同样使用mPtr来表示native的消息队列,NativeMessageQueue@android_os_MessageQueue.cpp,看一下NativeMessageQueue的构造函数,其中定义了一个Native的Looper,

[cpp] view plain copy
print ?
  1. NativeMessageQueue::NativeMessageQueue() {  
  2.     mLooper = Looper::getForThread();  
  3.     if (mLooper == NULL) {  
  4.         mLooper = new Looper(false);  
  5.         Looper::setForThread(mLooper);  
  6.     }  
  7. }  
NativeMessageQueue::NativeMessageQueue() {mLooper = Looper::getForThread();if (mLooper == NULL) {mLooper = new Looper(false);Looper::setForThread(mLooper);}
}
    因此整个的结构很简单,JAVA Looper包含一个MessageQueue,MessageQueue对应的Native 实例是一个NativeMessageQueue实例,NativeMessageQueue在创建的时候生成一个Native Looper。

    其实Native Looper存在的意义就是作为JAVA Looper机制的开关器,

    1. 当消息队列中有消息存入时,唤醒Natvice Looper,至于如何发送向线程消息,这就用到了Handler,google的文档中说明的很详细,不介绍了;

    2. 当消息队列中没有消息时或者消息尚未到处理时间时,Natvice Looper block住整个线程。

    上述功能其实一句话就可以总结:创建了JAVA Looper的线程只有在有消息待处理时才处于活跃状态,无消息时block在等待消息写入的状态。
    下面我们就来分析Natvice Looper它是如何实现这个功能的,先从Natvice Looper入手,看它能干什么。

   1. Native Looper初始化

    我们看一下Natvice Looper的初始化过程都作了那些工作

    1. 创建一个pipe管道mWakeReadPipeFd和mWakeWritePipeFd,这个管道的作用就是为了将block住的线程唤醒。

    2. 创建一个epoll实例mEpollFd,用它来监听event触发,event有mWakeReadPipeFd上的 wake event;还有上一篇文章当中讲到的NativeInputQueue和InputDispatcher模块注册在各自Looper中,需要监听的的硬件设备的事件发生时的通知event以及事件消化完的通知event。

   在构造函数中只向mEpollFd添加对mWakeReadPipeFd的监听,NativeInputQueue和InputDispatcher模块注册的监听需要通过addFd()方法在各自的模块中注册,目前来看只有NativeInputQueue真正使用到当前线程的Native Looper,而InputDispatcher是自定义了一个Native Looper,参考上一篇文章。

[cpp] view plain copy
print ?
  1. Looper::Looper(bool allowNonCallbacks) :  
  2.         mAllowNonCallbacks(allowNonCallbacks),  
  3.         mResponseIndex(0) {  
  4.     int wakeFds[2];  
  5.     int result = pipe(wakeFds);  
  6.     LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);  
  7.   
  8.     mWakeReadPipeFd = wakeFds[0];  
  9.     mWakeWritePipeFd = wakeFds[1];  
  10.   
  11.     result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);  
  12.     LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",  
  13.             errno);  
  14.   
  15.     result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);  
  16.     LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",  
  17.             errno);  
  18.   
  19. #ifdef LOOPER_USES_EPOLL  
  20.     // Allocate the epoll instance and register the wake pipe.  
  21.     mEpollFd = epoll_create(EPOLL_SIZE_HINT);  
  22.     LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);  
  23.   
  24.     struct epoll_event eventItem;  
  25.     memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union  
  26.     eventItem.events = EPOLLIN;  
  27.     eventItem.data.fd = mWakeReadPipeFd;  
  28.     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);  
  29.     LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",  
  30.             errno);  
  31. #else  
  32.     ...........................................   
  33. }  
Looper::Looper(bool allowNonCallbacks) :mAllowNonCallbacks(allowNonCallbacks),mResponseIndex(0) {int wakeFds[2];int result = pipe(wakeFds);LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);mWakeReadPipeFd = wakeFds[0];mWakeWritePipeFd = wakeFds[1];result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",errno);result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",errno);#ifdef LOOPER_USES_EPOLL// Allocate the epoll instance and register the wake pipe.mEpollFd = epoll_create(EPOLL_SIZE_HINT);LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);struct epoll_event eventItem;memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field unioneventItem.events = EPOLLIN;eventItem.data.fd = mWakeReadPipeFd;result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",errno);
#else........................................... 
}

 2. Native Looper的业务逻辑

    这一部分我们深入分析一下Native Looper作为JAVA Looper机制的开关控制器的。

    JAVA Looper通过调用loop()不断的去检测消息队列中是否有消息需要处理,调用的是MessageQueue的next()方法,这个方法返回的是MessageQueue的存储的消息。


    loop()@Looper.java

[java] view plain copy
print ?
  1. Message msg = queue.next(); // might block  
   Message msg = queue.next(); // might block


[java] view plain copy
print ?
  1. final Message next() {  
  2.     int pendingIdleHandlerCount = -1// -1 only during first iteration  
  3.     int nextPollTimeoutMillis = 0;  
  4.   
  5.     for (;;) {  
  6.         if (nextPollTimeoutMillis != 0) {  
  7.             Binder.flushPendingCommands();  
  8.         }  
  9.         nativePollOnce(mPtr, nextPollTimeoutMillis);  
  10.   
  11.         synchronized (this) {  
  12.             // Try to retrieve the next message.  Return if found.  
  13.             final long now = SystemClock.uptimeMillis();  
  14.             final Message msg = mMessages;  
  15.             if (msg != null) {  
  16.                 final long when = msg.when;  
  17.                 if (now >= when) {  
  18.                     mBlocked = false;  
  19.                     mMessages = msg.next;  
  20.                     msg.next = null;  
  21.                     if (Config.LOGV) Log.v("MessageQueue""Returning message: " + msg);  
  22.                     return msg;  
  23.                 } else {  
  24.                     nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);  
  25.                 }  
  26.             } else {  
  27.                 nextPollTimeoutMillis = -1;  
  28.             }  
  29.   
  30.             // If first time, then get the number of idlers to run.  
  31.             if (pendingIdleHandlerCount < 0) {  
  32.                 pendingIdleHandlerCount = mIdleHandlers.size();  
  33.             }  
  34.             if (pendingIdleHandlerCount == 0) {  
  35.                 // No idle handlers to run.  Loop and wait some more.  
  36.                 mBlocked = true;  
  37.                 continue;  
  38.             }  
  39.   
  40.             if (mPendingIdleHandlers == null) {  
  41.                 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];  
  42.             }  
  43.             mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);  
  44.         }  
  45.   
  46.         // Run the idle handlers.  
  47.         // We only ever reach this code block during the first iteration.  
  48.         for (int i = 0; i < pendingIdleHandlerCount; i++) {  
  49.             final IdleHandler idler = mPendingIdleHandlers[i];  
  50.             mPendingIdleHandlers[i] = null// release the reference to the handler  
  51.   
  52.             boolean keep = false;  
  53.             try {  
  54.                 keep = idler.queueIdle();  
  55.             } catch (Throwable t) {  
  56.                 Log.wtf("MessageQueue""IdleHandler threw exception", t);  
  57.             }  
  58.   
  59.             if (!keep) {  
  60.                 synchronized (this) {  
  61.                     mIdleHandlers.remove(idler);  
  62.                 }  
  63.             }  
  64.         }  
  65.   
  66.         // Reset the idle handler count to 0 so we do not run them again.  
  67.         pendingIdleHandlerCount = 0;  
  68.   
  69.         // While calling an idle handler, a new message could have been delivered  
  70.         // so go back and look again for a pending message without waiting.  
  71.         nextPollTimeoutMillis = 0;  
  72.     }  
  73. }  
    final Message next() {int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}nativePollOnce(mPtr, nextPollTimeoutMillis);synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = SystemClock.uptimeMillis();final Message msg = mMessages;if (msg != null) {final long when = msg.when;if (now >= when) {mBlocked = false;mMessages = msg.next;msg.next = null;if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);return msg;} else {nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);}} else {nextPollTimeoutMillis = -1;}// If first time, then get the number of idlers to run.if (pendingIdleHandlerCount < 0) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount == 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf("MessageQueue", "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;}}

    在获取MessageQueue中消息过程中,我们发现next()是一个循环,除了获取消息队列之外,最重要的一点就是去监听Natvie Looper的event触发,调用nativePollOnce(mPtr, nextPollTimeoutMillis); 它最终会调用到pollInner()@Looper.cpp。我们来看看pollInner都干了些什么?

    1. 等待mEpollFd的事件触发,我们前面说过,这个事件触发有两种,第一个就是唤醒Native Looper的wake消息,另外一个就是复用Native Looper的其他消息,如NativeInputQueue和InputDispatcher的管道检测(目前android也就这两个模块使用到了)。

当epoll_wait()的等待时间不为0时,即JAVA Looper传递下来的nextPollTimeoutMillis值,那么整个线程就被block在这儿了。

    pollInner()@Looper.cpp

[cpp] view plain copy
print ?
  1. struct epoll_event eventItems[EPOLL_MAX_EVENTS];  
  2. int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
  3. bool acquiredLock = false;  
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);bool acquiredLock = false;
   2. 如果有事件触发发生,wake或者其他复用Looper的event,处理event,这样整个Native Looper就从block状态中解脱出来了,整个线程也就解脱出来了,JAVA Looper则会执行nativePollOnce(mPtr, nextPollTimeoutMillis);下面的语句,next()@MessageQueue.java后面的语句就不解释了。

    我们只需要注意nextPollTimeoutMillis值的设置,如果消息尚未到达处理时间,则nextPollTimeoutMillis值则为距离该消息处理时间的总时长,表明Native Looper只需要block到消息需要处理的时间就行了。如果没有消息待处理,那么将一直Native Looper将一直block住,等待wake event。

    那么什么时候会有wake event发生呢?只有在有新的消息被存储到MessageQueue时,会向Native Looper发起wake event。

    enqueueMessage()@MessageQueue.java

[cpp] view plain copy
print ?
  1. if (needWake) {  
  2.     nativeWake(mPtr);  
  3. }  
        if (needWake) {nativeWake(mPtr);}
     整个Native Looper的基本机制就是这样的,保证在线程无消息可处理时,能够尽可能的减少CPU的利用,将宝贵的CPU资源交给其他线程或者进程处理,这一点在移动设备中是很重要的。

 3. Native Looper扩展应用

Native Looper尽管通常情况下是和JAVA 层的Looper和MessageQueue配合使用的,作为JAVA Looper的开关控制器存在的,但是鉴于上面我们对Native Looper的分析,我们发现在native code开发时,我们完全也可以单独使用Native Looper,比如说在开发的native code中如果使用到了pipe或者socket通信的话,Native Looper将会是把利器,我们通过它能够很好的管理pipe或者socket等的通信。正如InputDispatcher所做的一样

这篇关于Native looper 分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/966453

相关文章

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

MySQL表锁、页面锁和行锁的作用及其优缺点对比分析

《MySQL表锁、页面锁和行锁的作用及其优缺点对比分析》MySQL中的表锁、页面锁和行锁各有特点,适用于不同的场景,表锁锁定整个表,适用于批量操作和MyISAM存储引擎,页面锁锁定数据页,适用于旧版本... 目录1. 表锁(Table Lock)2. 页面锁(Page Lock)3. 行锁(Row Lock

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专