Android应用程序消息处理机制(Looper、Handler)分析(2)
我们再回到NativeMessageQueue的构造函数中,看看JNI层的Looper对象的创建过程,即看看它的构造函数是如何实现的,这个Looper类实现在frameworks/base/libs/utils/Looper.cpp文件中:
- Looper::Looper(boolallowNonCallbacks):
- mAllowNonCallbacks(allowNonCallbacks),
- mResponseIndex(0){
- intwakeFds[2];
- intresult=pipe(wakeFds);
- ......
- mWakeReadPipeFd=wakeFds[0];
- mWakeWritePipeFd=wakeFds[1];
- ......
- #ifdefLOOPER_USES_EPOLL
- //Allocatetheepollinstanceandregisterthewakepipe.
- mEpollFd=epoll_create(EPOLL_SIZE_HINT);
- ......
- structepoll_eventeventItem;
- memset(&eventItem,0,sizeof(epoll_event));//zerooutunusedmembersofdatafieldunion
- eventItem.events=EPOLLIN;
- eventItem.data.fd=mWakeReadPipeFd;
- result=epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeReadPipeFd,&eventItem);
- ......
- #else
- ......
- #endif
- ......
- }
这个构造函数做的事情非常重要,它跟我们后面要介绍的应用程序主线程在消息队列中没有消息时要进入等待状态以及当消息队列有消息时要把应用程序主线程唤醒的这两个知识点息息相关。它主要就是通过pipe系统调用来创建了一个管道了:
- intwakeFds[2];
- intresult=pipe(wakeFds);
- ......
- mWakeReadPipeFd=wakeFds[0];
- 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要监控相应的文件描述符的什么事件:
- structepoll_eventeventItem;
- memset(&eventItem,0,sizeof(epoll_event));//zerooutunusedmembersofdatafieldunion
- eventItem.events=EPOLLIN;
- eventItem.data.fd=mWakeReadPipeFd;
- 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函数进入到消息循环中去了:
- publicclassLooper{
- ......
- publicstaticfinalvoidloop(){
- Looperme=myLooper();
- MessageQueuequeue=me.mQueue;
- ......
- while(true){
- Messagemsg=queue.next();//mightblock
- ......
- if(msg!=null){
- if(msg.target==null){
- //Notargetisamagicidentifierforthequitmessage.
- return;
- }
- ......
- msg.target.dispatchMessage(msg);
- ......
- msg.recycle();
- }
- }
- }
- ......
- }
这里就是进入到消息循环中去了,它不断地从消息队列mQueue中去获取下一个要处理的消息msg,如果消息的target成员变量为null,就表示要退出消息循环了,否则的话就要调用这个target对象的dispatchMessage成员函数来处理这个消息,这个target对象的类型为Handler,下面我们分析消息的发送时会看到这个消息对象msg是如设置的。
这个函数最关键的地方便是从消息队列中获取下一个要处理的消息了,即MessageQueue.next函数,它实现frameworks/base/core/java/android/os/MessageQueue.java文件中:
- publicclassMessageQueue{
- ......
- finalMessagenext(){
- intpendingIdleHandlerCount=-1;//-1onlyduringfirstiteration
- intnextPollTimeoutMillis=0;
- for(;;){
- if(nextPollTimeoutMillis!=0){
- Binder.flushPendingCommands();
- }
- nativePollOnce(mPtr,nextPollTimeoutMillis);
- synchronized(this){
- //Trytoretrievethenextmessage.Returniffound.
- finallongnow=SystemClock.uptimeMillis();
- finalMessagemsg=mMessages;
- if(msg!=null){
- finallongwhen=msg.when;
- if(now>=when){
- mBlocked=false;
- mMessages=msg.next;
- msg.next=null;
- if(Config.LOGV)Log.v("MessageQueue","Returningmessage:"+msg);
- returnmsg;
- }else{
- nextPollTimeoutMillis=(int)Math.min(when-now,Integer.MAX_VALUE);
- }
- }else{
- nextPollTimeoutMillis=-1;
- }
- //Iffirsttime,thengetthenumberofidlerstorun.
- if(pendingIdleHandlerCount<0){
- pendingIdleHandlerCount=mIdleHandlers.size();
- }
- if(pendingIdleHandlerCount==0){
- //Noidlehandlerstorun.Loopandwaitsomemore.
- mBlocked=true;
- continue;
- }
- if(mPendingIdleHandlers==null){
- mPendingIdleHandlers=newIdleHandler[Math.max(pendingIdleHandlerCount,4)];
- }
- mPendingIdleHandlers=mIdleHandlers.toArray(mPendingIdleHandlers);
- }
- //Runtheidlehandlers.
- //Weonlyeverreachthiscodeblockduringthefirstiteration.
- for(inti=0;i<pendingIdleHandlerCount;i++){
- finalIdleHandleridler=mPendingIdleHandlers[i];
- mPendingIdleHandlers[i]=null;//releasethereferencetothehandler
- booleankeep=false;
- try{
- keep=idler.queueIdle();
- }catch(Throwablet){
- Log.wtf("MessageQueue","IdleHandlerthrewexception",t);
- }
- if(!keep){
- synchronized(this){
- mIdleHandlers.remove(idler);
- }
- }
- }
- //Resettheidlehandlercountto0sowedonotrunthemagain.
- pendingIdleHandlerCount=0;
- //Whilecallinganidlehandler,anewmessagecouldhavebeendelivered
- //sogobackandlookagainforapendingmessagewithoutwaiting.
- nextPollTimeoutMillis=0;
- }
- }
- ......
- }
调用这个函数的时候,有可能会让线程进入等待状态。什么情况下,线程会进入等待状态呢?两种情况,一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。消息队列中的消息是按时间先后来排序的,后面我们在分析消息的发送时会看到。
执行下面语句是看看当前消息队列中有没有消息:
nativePollOnce(mPtr,nextPollTimeoutMillis);
这是一个JNI方法,我们等一下再分析,这里传入的参数mPtr就是指向前面我们在JNI层创建的NativeMessageQueue对象了,而参数nextPollTimeoutMillis则表示如果当前消息队列中没有消息,它要等待的时候,for循环开始时,传入的值为0,表示不等待。
当前nativePollOnce返回后,就去看看消息队列中有没有消息:
- finalMessagemsg=mMessages;
- if(msg!=null){
- finallongwhen=msg.when;
- if(now>=when){
- mBlocked=false;
- mMessages=msg.next;
- msg.next=null;
- if(Config.LOGV)Log.v("MessageQueue","Returningmessage:"+msg);
- returnmsg;
- }else{
- nextPollTimeoutMillis=(int)Math.min(when-now,Integer.MAX_VALUE);
- }
- }else{
- nextPollTimeoutMillis=-1;
- }
如果消息队列中有消息,并且当前时候大于等于消息中的执行时间,那么就直接返回这个消息给Looper.loop消息处理,否则的话就要等待到消息的执行时间:
- nextPollTimeoutMillis=(int)Math.min(when-now,Integer.MAX_VALUE);
如果消息队列中没有消息,那就要进入无穷等待状态直到有新消息了:
nextPollTimeoutMillis=-1;
-1表示下次调用nativePollOnce时,如果消息中没有消息,就进入无限等待状态中去。
这里计算出来的等待时间都是在下次调用nativePollOnce时使用的。
这里说的等待,是空闲等待,而不是忙等待,因此,在进入空闲等待状态前,如果应用程序注册了IdleHandler接口来处理一些事情,那么就会先执行这里IdleHandler,然后再进入等待状态。IdlerHandler是定义在MessageQueue的一个内部类:
- publicclassMessageQueue{
- ......
- /**
- *Callbackinterfacefordiscoveringwhenathreadisgoingtoblock
- *waitingformoremessages.
- */
- publicstaticinterfaceIdleHandler{
- /**
- *Calledwhenthemessagequeuehasrunoutofmessagesandwillnow
- *waitformore.Returntruetokeepyouridlehandleractive,false
- *tohaveitremoved.Thismaybecallediftherearestillmessages
- *pendinginthequeue,buttheyareallscheduledtobedispatched
- *afterthecurrenttime.
- */
- booleanqueueIdle();
- }
- ......
- }
它只有一个成员函数queueIdle,执行这个函数时,如果返回值为false,那么就会从应用程序中移除这个IdleHandler,否则的话就会在应用程序中继续维护着这个IdleHandler,下次空闲时仍会再执会这个IdleHandler。MessageQueue提供了addIdleHandler和removeIdleHandler两注册和删除IdleHandler。
更多相关文章
- JPush 推送消息给 Android
- Android(安卓)去除“Viewing full screen”弹窗
- Thread、Handler和HandlerThread关系详解
- Android(安卓)线程&线程池
- Android(安卓)Update Engine分析(五)服务端核心之Action机制
- Android(安卓)中keyEvent的消息处理
- Android(安卓)常见的几种内存泄漏 - Handler
- MQTT在Android中的使用
- ril