java looper_专题:Android应用程序消息处理机制(Looper、Handler)分析_51CTO.COM

Android应用程序是通过消息来驱动的,系统为每一个应用程序维护一个消息队例,应用程序的主线程不断地从这个消息队例中获取消息(Looper), 然后对这些消息进行处理(Handler),这样就实现了通过消息来驱动应用程序的执行,本文将详细分析Android应用程序的消息处理机制。

前面我们学习Android应用程序中的Activity启动(Android应用程序启动过程源代码分析和Android应用程序内部启动 Activity过程(startActivity)的源代码分析)、Service启动(Android系统在新进程中启动自定义服务过程 (startService)的原理分析和Android应用程序绑定服务(bindService)的过程源代码分析)以及广播发送(Android应 用程序发送广播(sendBroadcast)的过程分析)时,它们都有一个共同的特点,当ActivityManagerService需要与应用程序 进行并互时,如加载Activity和Service、处理广播待,会通过Binder进程间通信机制来知会应用程序,应用程序接收到这个请求时,它不是 马上就处理这个请求,而是将这个请求封装成一个消息,然后把这个消息放在应用程序的消息队列中去,然后再通过消息循环来处理这个消息。这样做的好处就是消 息的发送方只要把消息发送到应用程序的消息队列中去就行了,它可以马上返回去处理别的事情,而不需要等待消息的接收方去处理完这个消息才返回,这样就可以 提高系统的并发性。

实质上,这就是一种异步处理机制。

这样说可能还是比较笼统,我们以Android应用程序启动过程源代码分析一文中所介绍的应用程序启动过程的一个片断来具体看看是如何这种消息处理机制的。

在Android应用程序进程启动过程的源代码分析一文中,我们分析了Android应用程序进程的启动过程。

Android应用程序进程在启动的时候, 会在进程中加载ActivityThread类,并且执行这个类的main函数。

应用程序的消息循环过程就是在这个main函数里面实现的。

我们来看看这个函数的实现,它定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

函数prepareMainLooper做的事情其实就是在线程中创建一个Looper对象,这个Looper对象是存放在sThreadLocal成员变量里面的。

成员变量sThreadLocal的类型为ThreadLocal,表示这是一个线程局部变量,即保证每一个调用了 prepareMainLooper函数的线程里面都有一个独立的Looper对象。

在线程是创建Looper对象的工作是由prepare函数来完成的,而在创建Looper对象的时候,会同时创建一个消息队列MessageQueue,保存在Looper的成员变量mQueue中,后续消息就是存放 在这个队列中去。

消息队列在Android应用程序消息处理机制中最重要的组件,因此,我们看看它的创建过程,即它的构造函数的实现。

管道是Linux系统中的一种进程间通信机制,具体可以参考前面一篇文章Android学习启动篇推荐的一本书《Linux内核源代码情景分析》中的第6 章--传统的Uinx进程间通信。

简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件, 其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态, 而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。

这个等待和唤醒 的操作是如何进行的呢,这就要借助Linux系统中的epoll机制了。

Linux系统中的epoll机制为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著减 少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

这里就是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。

C++层的这个Looper对象创建好了之后,就返回到JNI层的NativeMessageQueue的构造函数,最后就返回到Java层的消息 队 列MessageQueue的创建过程,这样,Java层的Looper对象就准备好了。有点复杂,我们先小结一下这一步都做了些什么事情:

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

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

调用以下函数的时候,有可能会让线程进入等待状态。

什么情况下,线程会进入等待状态呢?

两种情况,一是当消息队列中没有消息时,它会使线程进入等待状态;;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。

消息队列中的消息是按时间先后来排序的,后面我们在分 析消息的发送时会看到。

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

[java] view plaincopynextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);

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

[java] view plaincopynextPollTimeoutMillis = -1;

执行完这些IdleHandler之后,线程下次调用nativePollOnce函数时,就不设置超时时间了。

因为,很有可能在执行IdleHandler的时候,已经有新的消息加入到消息队列中去了。

正因为如此,

要重置nextPollTimeoutMillis的值:

这里,首先是调用epoll_wait函数来看看epoll专用文件描述符mEpollFd所监控的文件描述符是否有IO事件发生,它设置监控的超时时间为timeoutMillis:

[cpp] view plaincopyint eventCount = epoll_wait(mEpollFd, eventItems,

EPOLL_MAX_EVENTS, timeoutMillis);

回忆一下前面的Looper的构造函数,我们在里面设置了要监控mWakeReadPipeFd文件描述符的EPOLLIN事件。

当mEpollFd所监控的文件描述符发生了要监控的IO事件后或者监控时间超时后,线程就从epoll_wait返回了,否则线程就会在epoll_wait函数中进入睡眠状态了。

返回后如果eventCount等于0,就说明是超时了:

应用程序的主线程准备就好消息队列并且进入到消息循环后,其它地方就可以往这个消息队列中发送消息了。

我们继续以文章开始介绍的Android应用程序启动过程源代码分析一文中的应用程序启动过为例,说明应用程序是如何把消息加入到应用程序的消息队列中去的。

在Android应用程序启动过程源代码分析这篇文章的Step 30中,ActivityManagerService通过调用ApplicationThread类的scheduleLaunchActivity函 数通知应用程序。

它可以加载应用程序的默认Activity了,这个函数定义在frameworks/base/core/java/android /app/ActivityThread.java文件中:

在queueOrSendMessage函数中,又进一步把上面传进来的参数封装成一个Message对象msg,然后通过mH.sendMessage 函数把这个消息对象msg加入到应用程序的消息队列中去。

这里的mH是ActivityThread类的成员变量,它的类型为H,继承于Handler 类。

这个H类就是通过其成员函数handleMessage函数来处理消息的了,后面我们分析消息的处理过程时会看到。

它定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值