Native looper 分析

来源:互联网 发布:网络语ht是什么意思 编辑:程序博客网 时间:2024/06/05 11:36

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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  1. Message msg = queue.next(); // might block  


[java] view plaincopy
  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 plaincopy
  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 plaincopy
  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所做的一样。
0 0
原创粉丝点击