Android应用程序消息处理机制(Looper、Handler)分析(2)

简介:

我们再回到NativeMessageQueue的构造函数中,看看JNI层的Looper对象的创建过程,即看看它的构造函数是如何实现的,这个Looper类实现在frameworks/base/libs/utils/Looper.cpp文件中:

  1. Looper::Looper(bool allowNonCallbacks) :  
  2.     mAllowNonCallbacks(allowNonCallbacks),  
  3.     mResponseIndex(0) {  
  4.     int wakeFds[2];  
  5.     int result = pipe(wakeFds);  
  6.     ......  
  7.   
  8.     mWakeReadPipeFd = wakeFds[0];  
  9.     mWakeWritePipeFd = wakeFds[1];  
  10.   
  11.     ......  
  12.   
  13. #ifdef LOOPER_USES_EPOLL  
  14.     // Allocate the epoll instance and register the wake pipe.  
  15.     mEpollFd = epoll_create(EPOLL_SIZE_HINT);  
  16.     ......  
  17.   
  18.     struct epoll_event eventItem;  
  19.     memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union  
  20.     eventItem.events = EPOLLIN;  
  21.     eventItem.data.fd = mWakeReadPipeFd;  
  22.     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);  
  23.     ......  
  24. #else  
  25.     ......  
  26. #endif  
  27.   
  28.     ......  
  29. }  

   这个构造函数做的事情非常重要,它跟我们后面要介绍的应用程序主线程在消息队列中没有消息时要进入等待状态以及当消息队列有消息时要把应用程序主线程唤醒的这两个知识点息息相关。它主要就是通过pipe系统调用来创建了一个管道了:

  1. int wakeFds[2];  
  2. int result = pipe(wakeFds);  
  3. ......  
  4.   
  5. mWakeReadPipeFd = wakeFds[0];  
  6. mWakeWritePipeFd = wakeFds[1];  

    管道是Linux系统中的一种进程间通信机制,具体可以参考前面一篇文章Android学习启动篇推荐的一本书《Linux内核源代码情景分析》中的第6章--传统的Uinx进程间通信。简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。这个等待和唤醒的操作是如何进行的呢,这就要借助Linux系统中的epoll机制了。 Linux系统中的epoll机制为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。但是这里我们其实只需要监控的IO接口只有mWakeReadPipeFd一个,即前面我们所创建的管道的读端,为什么还需要用到epoll呢?有点用牛刀来杀鸡的味道。其实不然,这个Looper类是非常强大的,它除了监控内部所创建的管道接口之外,还提供了addFd接口供外界面调用,外界可以通过这个接口把自己想要监控的IO事件一并加入到这个Looper对象中去,当所有这些被监控的IO接口上面有事件发生时,就会唤醒相应的线程来处理,不过这里我们只关心刚才所创建的管道的IO事件的发生。

 

        要使用Linux系统的epoll机制,首先要通过epoll_create来创建一个epoll专用的文件描述符:mEpollFd = epoll_create(EPOLL_SIZE_HINT);  

   传入的参数EPOLL_SIZE_HINT是在这个mEpollFd上能监控的最大文件描述符数。

 

       接着还要通过epoll_ctl函数来告诉epoll要监控相应的文件描述符的什么事件:

  1. struct epoll_event eventItem;  
  2. memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union  
  3. eventItem.events = EPOLLIN;  
  4. eventItem.data.fd = mWakeReadPipeFd;  
  5. result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem); 

 

 

   这里就是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。
       C++层的这个Looper对象创建好了之后,就返回到JNI层的NativeMessageQueue的构造函数,最后就返回到Java层的消息队列MessageQueue的创建过程,这样,Java层的Looper对象就准备好了。有点复杂,我们先小结一下这一步都做了些什么事情:

 

       A. 在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;

       B. 在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;

       C. 在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。

       回到ActivityThread类的main函数中,在上面这些工作都准备好之后,就调用Looper类的loop函数进入到消息循环中去了:

  1. public class Looper {  
  2.     ......  
  3.   
  4.     public static final void loop() {  
  5.         Looper me = myLooper();  
  6.         MessageQueue queue = me.mQueue;  
  7.   
  8.         ......  
  9.   
  10.         while (true) {  
  11.             Message msg = queue.next(); // might block  
  12.             ......  
  13.   
  14.             if (msg != null) {  
  15.                 if (msg.target == null) {  
  16.                     // No target is a magic identifier for the quit message.  
  17.                     return;  
  18.                 }  
  19.   
  20.                 ......  
  21.   
  22.                 msg.target.dispatchMessage(msg);  
  23.                   
  24.                 ......  
  25.   
  26.                 msg.recycle();  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     ......  
  32. }  

 

  这里就是进入到消息循环中去了,它不断地从消息队列mQueue中去获取下一个要处理的消息msg,如果消息的target成员变量为null,就表示要退出消息循环了,否则的话就要调用这个target对象的dispatchMessage成员函数来处理这个消息,这个target对象的类型为Handler,下面我们分析消息的发送时会看到这个消息对象msg是如设置的。

 

        这个函数最关键的地方便是从消息队列中获取下一个要处理的消息了,即MessageQueue.next函数,它实现frameworks/base/core/java/android/os/MessageQueue.java文件中:

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

 

 调用这个函数的时候,有可能会让线程进入等待状态。什么情况下,线程会进入等待状态呢?两种情况,一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。消息队列中的消息是按时间先后来排序的,后面我们在分析消息的发送时会看到。

 

        执行下面语句是看看当前消息队列中有没有消息:
    nativePollOnce(mPtr, nextPollTimeoutMillis);  

  这是一个JNI方法,我们等一下再分析,这里传入的参数mPtr就是指向前面我们在JNI层创建的NativeMessageQueue对象了,而参数nextPollTimeoutMillis则表示如果当前消息队列中没有消息,它要等待的时候,for循环开始时,传入的值为0,表示不等待。

 

        当前nativePollOnce返回后,就去看看消息队列中有没有消息:

  1. final Message msg = mMessages;  
  2. if (msg != null) {  
  3.     final long when = msg.when;  
  4.     if (now >= when) {  
  5.         mBlocked = false;  
  6.         mMessages = msg.next;  
  7.         msg.next = null;  
  8.         if (Config.LOGV) Log.v("MessageQueue""Returning message: " + msg);  
  9.         return msg;  
  10.     } else {  
  11.         nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);  
  12.     }  
  13. else {  
  14.     nextPollTimeoutMillis = -1;  
  15. }  

 

 

 如果消息队列中有消息,并且当前时候大于等于消息中的执行时间,那么就直接返回这个消息给Looper.loop消息处理,否则的话就要等待到消息的执行时间:

 
 
  1. nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);   

 如果消息队列中没有消息,那就要进入无穷等待状态直到有新消息了:
nextPollTimeoutMillis = -1

  -1表示下次调用nativePollOnce时,如果消息中没有消息,就进入无限等待状态中去。

 

        这里计算出来的等待时间都是在下次调用nativePollOnce时使用的。

        这里说的等待,是空闲等待,而不是忙等待,因此,在进入空闲等待状态前,如果应用程序注册了IdleHandler接口来处理一些事情,那么就会先执行这里IdleHandler,然后再进入等待状态。IdlerHandler是定义在MessageQueue的一个内部类:

  1. public class MessageQueue {  
  2.     ......  
  3.   
  4.     /** 
  5.     * Callback interface for discovering when a thread is going to block 
  6.     * waiting for more messages. 
  7.     */  
  8.     public static interface IdleHandler {  
  9.         /** 
  10.         * Called when the message queue has run out of messages and will now 
  11.         * wait for more.  Return true to keep your idle handler active, false 
  12.         * to have it removed.  This may be called if there are still messages 
  13.         * pending in the queue, but they are all scheduled to be dispatched 
  14.         * after the current time. 
  15.         */  
  16.         boolean queueIdle();  
  17.     }  
  18.   
  19.     ......  
  20. }  

 

 它只有一个成员函数queueIdle,执行这个函数时,如果返回值为false,那么就会从应用程序中移除这个IdleHandler,否则的话就会在应用程序中继续维护着这个IdleHandler,下次空闲时仍会再执会这个IdleHandler。MessageQueue提供了addIdleHandler和removeIdleHandler两注册和删除IdleHandler。 





本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966591,如需转载请自行联系原作者

目录
相关文章
|
1天前
|
前端开发 开发工具 Android开发
安卓与iOS开发环境的差异性分析
在移动应用开发的广阔天地中,安卓与iOS两大平台各自占据着半壁江山。开发者在选择开发环境时往往需权衡两者的技术架构、开发工具及市场策略等多方面因素。本文深入探讨了安卓和iOS开发环境的主要差异,包括编程语言、IDE选择、API设计哲学等技术层面,以及用户群体、市场份额和分发渠道等市场层面的差异。通过对比分析,旨在为移动应用开发者提供清晰的决策依据,帮助他们在激烈的市场竞争中找到适合自身项目需求的最佳开发路径。
7 0
|
1天前
|
Java API 开发工具
如何将python应用编译到android运行
【6月更文挑战第27天】本文介绍在Ubuntu 20上搭建Android开发环境,包括安装JRE/JDK,设置环境变量,添加i386架构,安装依赖和编译工具。并通过`p4a`命令行工具进行apk构建和清理。
20 6
如何将python应用编译到android运行
|
2天前
|
IDE 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS平台各自扮演着不可或缺的角色。本文将深入探讨两大主流移动操作系统的开发环境,从编程语言、开发工具、用户界面设计以及跨平台框架等多个维度进行细致对比。我们将揭示它们在开发效率、用户体验和技术生态上的差异,旨在为开发者提供一份全面而实用的指南,帮助他们根据项目需求和目标受众作出更明智的技术选择。
6 0
|
3天前
|
API 开发工具 Android开发
安卓与iOS开发环境对比分析
本文将探讨安卓与iOS两大移动操作系统在开发环境中的差异,包括编程语言、工具链、API设计哲学、应用商店政策以及开发者社区支持等方面。通过比较分析,旨在帮助开发者更好地选择适合自己项目需求的平台,并理解不同环境下的开发挑战和机遇。
|
4天前
|
人工智能 API 语音技术
探索Gemini Pro AI在智能Android应用中的魅力
探索Gemini Pro AI在智能Android应用中的魅力
11 0
|
7天前
|
Java Linux API
微信API:探究Android平台下Hook技术的比较与应用场景分析
微信API:探究Android平台下Hook技术的比较与应用场景分析
|
7天前
|
安全 Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的对比分析
移动应用开发的两大阵营——安卓与iOS,各自拥有独特的开发环境、用户群体和市场定位。本文将深入探讨这两个操作系统在应用开发过程中的主要差异,包括编程语言、开发工具、用户界面设计、性能优化、安全性考量以及发布流程等方面。通过比较分析,旨在为开发者提供跨平台开发的见解和策略,以优化应用性能和提升用户体验。
11 0
|
7天前
|
缓存 Java Linux
Android 匿名内存深入分析
Android 匿名内存深入分析
10 0
|
10天前
|
XML BI 数据库
一个基于Android Studio的简易记事本应用
一个基于Android Studio的简易记事本应用
20 0
|
10天前
|
XML 存储 数据库
如何使用Android Studio创建一个基本的音乐播放器应用
如何使用Android Studio创建一个基本的音乐播放器应用
28 0