当前位置: 首页 > 编程日记 > 正文

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. final Message next() {
  4. int pendingIdleHandlerCount = -1// -1 only during first iteration  
  5. int nextPollTimeoutMillis = 0;
  6. for (;;) {
  7. if (nextPollTimeoutMillis != 0) {
  8. Binder.flushPendingCommands();
  9. }
  10. nativePollOnce(mPtr, nextPollTimeoutMillis);
  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. // If first time, then get the number of idlers to run.  
  30. if (pendingIdleHandlerCount < 0) {
  31. pendingIdleHandlerCount = mIdleHandlers.size();
  32. }
  33. if (pendingIdleHandlerCount == 0) {
  34. // No idle handlers to run.  Loop and wait some more.  
  35. mBlocked = true;
  36. continue;
  37. }
  38. if (mPendingIdleHandlers == null) {
  39. mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
  40. }
  41. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
  42. }
  43. // Run the idle handlers.  
  44. // We only ever reach this code block during the first iteration.  
  45. for (int i = 0; i < pendingIdleHandlerCount; i++) {
  46. final IdleHandler idler = mPendingIdleHandlers[i];
  47. mPendingIdleHandlers[i] = null// release the reference to the handler  
  48. boolean keep = false;
  49. try {
  50. keep = idler.queueIdle();
  51. catch (Throwable t) {
  52. Log.wtf("MessageQueue""IdleHandler threw exception", t);
  53. }
  54. if (!keep) {
  55. synchronized (this) {
  56. mIdleHandlers.remove(idler);
  57. }
  58. }
  59. }
  60. // Reset the idle handler count to 0 so we do not run them again.  
  61. pendingIdleHandlerCount = 0;
  62. // While calling an idle handler, a new message could have been delivered  
  63. // so go back and look again for a pending message without waiting.  
  64. nextPollTimeoutMillis = 0;
  65. }
  66. }
  67. ......
  68. }

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

执行下面语句是看看当前消息队列中有没有消息:
    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。

相关文章:

以下一些使用ASP.NET和VISUAL STUDIO.NET2003的经验和技巧

作者&#xff1a;未知 请作者速与本人联系1,不要在VS里直接复制ASPX文件,因为复制的时候,两个文件会使用同一个类文件,要复制的话,应该建空文件,然后复制页面和代码2,发在项目完工的时候&#xff0c;要想得到一个干净的&#xff0c;仅有必需文件的项目&#xff0c;可以用复制…

微软推出智能语音评测服务,注重解决四大技术障碍

今年5月&#xff0c;在微软开发者大会上&#xff0c;他们宣布推出智能语音评测服务。基于Azure云的认知服务平台&#xff0c;该服务涵盖语音识别、语音合成等技术&#xff0c;主要应用于各种教师评估、作业练习和语言学习等教学场景。 通过市场调研和反馈&#xff0c;他们将语…

这些编程语言程序员工资最高!Java才第四

在众多行业中&#xff0c;程序员属于高薪职业。无论是在国外还是国内&#xff0c;程序员的薪金水平普遍高于其他行业的工作岗位。 高薪的诱惑和充满挑战性的工作&#xff0c;令程序员一直成为备受欢迎的职业。在今年年初&#xff0c;Glassdoor发布的一份调查报告指出&#xff…

仔细选择会话状态提供程序

ASP.NET 为存储应用程序的会话数据提供了三种不同的方法&#xff1a;进程内会话状态、作为 Windows 服务的进程外会话状态和 SQL Server 数据库中的进程外会话状态。每种方法都有自己的优点&#xff0c;但进程内会话状态是迄今为止速度最快的解决方案。如果只在会话状态中存储少…

10.VMware View 4.6安装与部署-view clint和view for ipad连接测试

安装基于 Windows 的 View Client,最终用户需要从物理机打开 View Client 来连接其虚拟桌面。您可以运行基于 Windows 的安装程序文件来安装 View Client 的所有组件。如果 View 管理员启用了某些显示选项&#xff0c;那么除了通过 View Client 访问虚拟桌面外&#xff0c;最终…

免费直播:主流深度框架对比:总有一款适合你~

常常有小伙伴在后台反馈&#xff1a;想了解深度学习该怎么学&#xff1f;自学难度大又没有效果&#xff0c;该怎么办&#xff1f;CSDN为了解决这个难题&#xff0c;联合唐宇迪老师为大家带来了一场精彩的直播【一节课掌握深度学习必备框架】。本次直播将带大家了解在开始深度学…

QCon2016旧金山大会焦点分享者确认

QCon旧金山大会是由InfoQ举办的连续十年的最大的英语会议&#xff0c;它将在今年11月7-9日在旧金山湾区举行。\\在QCon大会涵盖了一系列深入的技术&#xff0c;架构师、资深开发者所关注的国际事件&#xff0c;聚焦创新领域和软件发展趋势&#xff0c;QCon大会每年在美国、中国…

Git 看这一篇就够了

作者 |码农田小齐责编 | Carol封图 | CSDN 下载自视觉中国今天简单讲下 Git 的实现原理&#xff0c;知其所以然才能知其然&#xff1b;并且梳理了日常最常用的 12 个命令&#xff0c;分为三大类分享给你。本文的结构如下&#xff1a;作者和开发原由Git 的数据模型常用命令资源推…

当不使用会话状态时禁用它

并不是所有的应用程序或页都需要针对于具体用户的会话状态&#xff0c;您应该对任何不需要会话状态的应用程序或页禁用会话状态。 若要禁用页的会话状态&#xff0c;请将 Page 指令中的 EnableSessionState 属性设置为 false。例如&#xff0c;<% Page EnableSessionState&…

jepg图像的存储 转

先把代码贴上&#xff1a; extern "C" { #include "jpeglib.h" #pragma comment(lib,"libjpeg.lib") //把无压缩的图像数据&#xff08;纹理&#xff09;存储成jepg bool appSaveJpegRGB(char *filepath,unsigned char * data,int width,int hei…

使用Spring进行统一日志管理 + 统一异常管理

统一日志和异常管理配置好后&#xff0c;SSH项目中&#xff0c;代码以往散落的log.info() 和 try..catch..finally 再也不见踪影&#xff01; 统一日志异常实现类&#xff1a; [java] view plaincopy package com.pilelot.web.util; import org.apache.log4j.Logger; impor…

避免到服务器的不必要的往返过程

虽然您很可能希望尽量多地使用 Web 窗体页框架的那些节省时间和代码的功能&#xff0c;但在某些情况下却不宜使用 ASP.NET 服务器控件和回发事件处理。 通常&#xff0c;只有在检索或存储数据时&#xff0c;您才需要启动到服务器的往返过程。多数数据操作可在这些往返过程间的…

OPPO和微软合作,开放“召唤小冰”

6月24日&#xff0c;OPPO与微软共同宣布&#xff0c;OPPO手机智能助理Breeno语音开放“召唤小冰”能力一年之际&#xff0c;双方合作再次深化。微软小冰与OPPO Breeno团队加速合作&#xff0c;不断打造和更新更符合OPPO生态的AI技能&#xff0c;提升微软小冰在Breeno语音中的产…

如果你即将死去

如果你即将死去&#xff0c;你是否能够安息&#xff1b; 如果你即将死去&#xff0c;你是否还有事情不能放弃&#xff1b; 如果你即将死去&#xff0c;你的事业是否有人继续&#xff1b; 如果你即将死去&#xff0c;你的生平是否还有人惦记&#xff1b; 死亡是所有生命的终点&a…

设计模式(行为型模式)——备忘录模式(Memento)

2019独角兽企业重金招聘Python工程师标准>>> 本章讲讲第三类和第四类。 备忘录模式&#xff08;Memento&#xff09; 主要目的是保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对象&#xff0c;个人觉得叫备份模式更形象些&#xff0c;通俗的讲下&#xf…

当你累了,准备放弃时,看看这个吧!!!

当你累了&#xff0c;准备放弃时&#xff0c;看看这个吧&#xff01;&#xff01;&#xff01; 在朋友空间看到这篇文章&#xff0c;送给所有还在坚持的朋友~~每个人都背负着一个沉重的十字架&#xff0c;在缓慢而艰难地朝着目的地前进。途中&#xff0c;有一个人忽然停了下来。…

只在必要时保存服务器控件视图状态

自动视图状态管理是服务器控件的功能&#xff0c;该功能使服务器控件可以在往返过程上重新填充它们的属性值&#xff08;您不需要编写任何代码&#xff09;。但是&#xff0c;因为服务器控件的视图状态在隐藏的窗体字段中往返于服务器&#xff0c;所以该功能确实会对性能产生影…

超越英伟达的,不会是另一款GPU!中国公司发布首款数据流AI芯片

2020年6月23日&#xff0c;鲲云科技在深圳举行产品发布会&#xff0c;发布全球首款数据流AI芯片CAISA&#xff0c;定位于高性能AI推理&#xff0c;已完成量产。鲲云通过自主研发的数据流技术在芯片实测算力上实现了技术突破&#xff0c;较同类产品在芯片利用率上提升了最高11.6…

vim打开多窗口、多文件之间的切换

打开多个文件&#xff1a;一、vim还没有启动的时候&#xff1a;1.在终端里输入 vim file1 file2 ... filen便可以打开所有想要打开的文件2.vim已经启动输入:e file可以再打开一个文件&#xff0c;并且此时vim里会显示出file文件的内容。3.同时显示多个文件&#xff1a;:sp …

图灵奖得主Judea Pearl:从“大数据革命”到“因果革命”

整理 | 智源社区&#xff0c;龚鹤扬&高亦斌2020年6月21日&#xff0c;在第二届北京智源大会开幕式及全体会议上&#xff0c;图灵奖得主、贝叶斯网络奠基人Judea Pearl 做了名为《The New Science of Cause and Effect with reflections on data science and artificial int…

美国两政府网站被挂马 以性丑闻女星为诱饵

据安全厂商趋势科技称&#xff0c;美国两个政府网站近日发现被挂木马&#xff0c;这两家被挂马的网站都是以性丑闻女性为诱饵欺骗用户访问其它恶意网页。  圣伯纳迪诺县的宣传页面被发现感染了恶意木马&#xff0c;用户访问该网站时被重定向到域名Videosdivx.net下的一个网站…

除非有特殊的原因要关闭缓冲,否则使其保持打开

禁用 Web 窗体页的缓冲会导致大量的性能开销。

[React Native Android安利系列]搭建React Native Android环境

欢迎大家收看react-native-android系列教程&#xff0c;跟着本系列教程学习&#xff0c;可以熟练掌握react-native-android的开发&#xff0c;你值得拥有https://segmentfault.com/blog... (PS&#xff0c;和聊一聊系列写在一起也实在是没辙&#xff0c; 谁知道如何新建专栏&am…

继承QTreeWidgetItem发生error: 'staticMetaObject' is not a member of 'QTreeWidgetItem' 错误

点击打开链接 #ifndef QQUSERITEM_H就发生下列错误 #define QQUSERITEM_H #include <QTreeWidgetItem> class QQUserItem :public QTreeWidgetItem { Q_OBJECT public: explicit QQUserItem(QQUserItem *parent 0); signals: public slots: }; #endif // QQUSERITEM_H d…

使用 HttpResponse.Write 方法进行字符串串联

该方法提供非常有效的缓冲和连接服务。但是&#xff0c;如果您正在执行广泛的连接&#xff0c;请使用多个 Response.Write 调用。下面示例中显示的技术比用对 Response.Write 方法的单个调用连接字符串更快。 [C#] Response.Write("a"); Response.Write(myString); …

倒计时 8 天 | 完整议程大揭秘!来 20 个 AI 论坛,与百名大咖携手玩转人工智能...

2020年7月3—4日&#xff0c;由 CSDN 主办的第三届 AI 开发者大会&#xff08;AI ProCon 2020&#xff09;&#xff08;大会官网&#xff1a;https://aiprocon.csdn.net/&#xff09;将以线上直播的形式与大家相见。本次大会历时2天&#xff0c;一次性设立6大主题、20大精彩分论…

关于分页的解决方案收集

分页的html样式&#xff0c;可分为两种&#xff0c;pc和移动端 pc端的解决方案&#xff08;既有json版本又有get参数版本&#xff09;&#xff1a; http://laypage.layui.com/ 移动端的上拉刷新&#xff0c;下拉加载解决方案&#xff1a;(个人建议还是老实用会iscroll.js吧) ht…

MVC3+EF4.1学习系列(一)-------创建EF4.1 code first的第一个实例(强转)

文章索引和简介 基于EF4.1 code first 简单的CRUD 园子中已经有很多了 ~~ 真不想再写这个了 可是为了做一个完整的小demo 从开始 到后面的一些简单重构 还是决定认真把这个写出来 争取写些别人没写到的东西~~ 好了 开始~~ 这次要做的是个学校管理的demo&#xff08;通俗些&…

超越英伟达的,不会是另一款GPU?这家深圳公司发布全球首款数据流AI芯片

2020年6月23日&#xff0c;鲲云科技在深圳举行产品发布会&#xff0c;发布全球首款数据流AI芯片CAISA&#xff0c;定位于高性能AI推理&#xff0c;已完成量产。鲲云通过自主研发的数据流技术在芯片实测算力上实现了技术突破&#xff0c;较同类产品在芯片利用率上提升了最高11.6…

不要依赖代码中的异常

因为异常大大地降低性能&#xff0c;所以您不应该将它们用作控制正常程序流程的方式。如果有可能检测到代码中可能导致异常的状态&#xff0c;请执行这种操作。不要在处理该状态之前捕获异常本身。常见的方案包括&#xff1a;检查 null&#xff0c;分配给将分析为数字值的 Stri…