Native looper 分析

本文深入解析了Android系统中NativeLooper的工作原理及其实现细节,包括其初始化过程、业务逻辑及扩展应用等内容。

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
  1. 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
  1. NativeMessageQueue::NativeMessageQueue() {  
  2.     mLooper = Looper::getForThread();  
  3.     if (mLooper == NULL) {  
  4.         mLooper = new Looper(false);  
  5.         Looper::setForThread(mLooper);  
  6.     }  
  7. }  
    因此整个的结构很简单,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
  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. }  

 2. Native Looper的业务逻辑

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

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


    loop()@Looper.java

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


[java]  view plain copy
  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. }  

    在获取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
  1. struct epoll_event eventItems[EPOLL_MAX_EVENTS];  
  2. int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
  3. 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
  1. if (needWake) {  
  2.     nativeWake(mPtr);  
  3. }  
     整个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所做的一样。
<think>我们正在处理一个关于Java中`native int poll0`方法卡住的问题。根据引用[3],我们知道`poll0`是Native层的方法,与Java层的MessageQueue紧密相关。在NativeMessageQueue的构造函数中,会获取或创建当前线程的Native Looper。这个LooperNative层负责消息轮询。 `poll0`方法卡住可能意味着Native层的消息队列在等待消息时被阻塞,或者出现了死锁等情况。常见的原因包括: 1. 消息队列中没有消息,导致线程在`poll0`中等待。 2. 发生了死锁,例如在Native层的代码中出现了同步问题。 3. 与JNI相关的错误,如全局引用不足等。 根据引用[3]中的代码,我们可以推测`poll0`方法最终会调用Native Looper的`pollOnce`方法,该方法会等待直到有事件发生或超时。 解决方案可能包括: - 检查是否有消息被正确发送到该线程的消息队列。 - 确保在不需要等待时唤醒线程(例如,通过调用`wake`方法)。 - 检查Native层代码是否存在死锁。 另外,引用[2]提到了高并发下自旋导致的性能问题,虽然这里不是直接相关,但也提醒我们检查是否有类似的自旋或资源竞争问题。 由于用户的问题是关于`poll0`方法卡住,我们需要更深入地分析可能导致该问题的原因。 **可能的原因及解决方案:** 1. **消息队列中没有消息,线程阻塞等待:** - 这是正常现象,当消息队列为空时,线程会阻塞在`poll0`方法上,直到新消息到来。如果这是预期行为,则无需担心。但如果是长时间阻塞,可能需要检查是否有消息被发送到该线程的消息队列。 2. **死锁:** - 如果其他线程持有锁并且等待当前线程完成某个操作,而当前线程又在`poll0`中等待,则可能导致死锁。需要检查代码中同步块的使用。 3. **Native层资源问题:** - 如文件描述符泄漏导致epoll失败,或者信号量问题等。 4. **JNI调用问题:** - 在JNI调用中处理不当,如未正确处理异常,可能导致状态异常。 **调试建议:** - 使用Android Studio的Native调试工具,在`poll0`方法处设置断点,观察调用栈和线程状态。 - 使用`adb shell dumpsys`命令检查线程状态,看是否处于“WAIT”状态。 - 检查日志,看是否有关于Native崩溃或ANR(Application Not Responding)的日志。 **代码层面的检查:** 在Java层,我们可以尝试唤醒消息队列。例如,在适当的时机调用`MessageQueue`的`wake`方法(通过`Looper.myQueue().wake()`)来唤醒阻塞的线程。 如果问题仍然存在,可能需要检查Native层的Looper实现。但是,由于`poll0`是Android框架的Native方法,我们无法直接修改,因此需要检查我们的使用方式是否正确。 **示例:** 确保在不需要等待时,能够及时唤醒线程。例如,在退出应用时,需要释放所有资源并唤醒所有等待的线程。 ```java // 在适当的时候唤醒消息队列 Looper.myQueue().wake(); ``` **其他可能的原因:** - 如果是在主线程(UI线程)中发生,可能导致ANR。需要检查是否有耗时操作在UI线程执行,或者消息处理时间过长。 - 如果是在工作线程,检查是否在不需要该线程时没有退出,导致线程一直阻塞。 **总结:** 需要根据具体的场景和日志来定位问题。首先确认是正常等待还是异常死锁,然后检查消息发送和唤醒的逻辑。如果问题复杂,可能需要深入Native层调试。 由于引用[3]提供了NativeMessageQueue的构造函数,它使用了一个Looper,我们可以知道每个线程的LooperNative层也是独立的。因此,确保每个线程正确使用自己的消息队列很重要。 §§相关问题§§ 1. 如何调试Java层的消息队列阻塞问题? 2. 在Android中,如何正确使用Looper和MessageQueue以避免死锁? 3. Native层的LooperJava层的Looper有什么关系? 4. 在Android开发中,如何避免ANR(Application Not Responding)问题?</think>### Java中`native int poll0`方法卡住问题分析 `native int poll0`是Android消息队列(`MessageQueue`)中的核心本地方法,其阻塞可能导致线程卡死或ANR(Application Not Responding)。以下是关键原因及解决方案: #### 一、问题原因 1. **消息队列阻塞** - 当消息队列为空时,`poll0`会阻塞线程等待新消息(正常行为) - 阻塞超时时间由`nextPollTimeoutMillis`参数控制[^3] $$ \text{阻塞时间} = \min(\text{nextPollTimeoutMillis}, \text{MAX_TIMEOUT}) $$ 2. **同步屏障未解除** - 同步屏障(`sync barrier`)会阻塞普通消息处理 - 若未及时移除屏障(`removeSyncBarrier()`),将导致永久阻塞 3. **Native层资源竞争** - 如引用[2]所述,高并发时资源竞争可能引发线程空转 - 文件描述符(FD)泄漏导致`epoll_wait`异常 4. **死锁场景** ```java // 错误示例:主线程等待工作线程结果 new Thread(() -> { // 耗时操作 result = compute(); LockSupport.unpark(mainThread); // 唤醒主线程 }).start(); LockSupport.park(); // 主线程阻塞 // 此时若工作线程需通过主线程Handler发送消息,形成死锁 ``` #### 二、解决方案 1. **检查消息循环逻辑** - 确保每条同步屏障都有对应的移除操作 ```java int token = mQueue.postSyncBarrier(); // ...异步任务... mQueue.removeSyncBarrier(token); // 必须移除 ``` 2. **监控文件描述符** - 使用`adb shell lsof -p <PID>`检查FD泄漏 - 重点监控`/dev/event`和`/dev/binder`相关资源 3. **优化线程模型** - 避免跨线程依赖:如工作线程直接操作UI线程Handler - 使用`ConcurrentHashMap`替代同步锁(参考引用[2]优化思路) 4. **Native层调试** - 通过`ndk-stack`分析Native崩溃日志 - 检查`Looper.cpp`中`pollOnce`实现(引用[3]中的核心逻辑) #### 三、调试技巧 1. **ANR日志分析** ```bash adb pull /data/anr/traces.txt # 搜索"NativePollOnce"调用栈 ``` 2. **添加诊断日志** ```java // 在MessageQueue.java中插入日志 long start = SystemClock.uptimeMillis(); int result = poll0(ptr, timeout); Log.d("POLL0", "Blocked for: "+(SystemClock.uptimeMillis()-start)); ``` 3. **使用StrictMode** ```java StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectNetwork() .penaltyDeath() // 检测主线程阻塞操作 .build()); ``` > **关键结论**:`poll0`阻塞通常是消息机制的正常行为,异常阻塞多由同步屏障未移除、FD泄漏或线程死锁导致。需结合Native层日志和消息队列状态综合诊断[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值