Android应用程序键盘(Keyboard)消息处理机制分析(18)
Step 16.InputDispatcher.prepareDispatchCycleLocked 这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中: voidInputDispatcher::prepareDispatchCycleLocked(nsecs_tcurrentTime, constsp<Connection>&connection,EventEntry*eventEntry,constInputTarget*inputTarget, boolresumeWithAppendedMotionSample){ ...... //Resumethedispatchcyclewithafreshlyappendedmotionsample. //Firstwecheckthatthelastdispatchentryintheoutboundqueueisforthesame //motioneventtowhichweappendedthemotionsample.Ifwefindsuchadispatch //entry,andifitiscurrentlyinprogressthenwetrytostreamthenewsample. boolwasEmpty=connection->outboundQueue.isEmpty(); if(!wasEmpty&&resumeWithAppendedMotionSample){ ...... return; } //Thisisanewevent. //Enqueueanewdispatchentryontotheoutboundqueueforthisconnection. DispatchEntry*dispatchEntry=mAllocator.obtainDispatchEntry(eventEntry,//incrementsref inputTarget->flags,inputTarget->xOffset,inputTarget->yOffset); ...... //Enqueuethedispatchentry. connection->outboundQueue.enqueueAtTail(dispatchEntry); //Iftheoutboundqueuewaspreviouslyempty,startthedispatchcyclegoing. if(wasEmpty){ ...... startDispatchCycleLocked(currentTime,connection); } } 在开始处理键盘事件之前,这个函数会检查一下传进来的参数connection中的outboundQueue事件队列是否为空,如果不为空,就要看看当前要处理的事件和outboundQueue队列中的最后一个事件是不是同一个motion事件,如果是的话,并且从上面传进来的resumeWithAppendedMotionSample参数为true,这时候就要以流水线的方式来处理这些motion事件了。在我们这个情景中,要处理的是键盘事件,因此在上面Step 12中传进来的resumeWithAppendedMotionSample参数为false,因此,我们略过这种情况。 接下来,就会把当前的键盘事件封装成一个DispatchEntry对象,然后添加到connection对象的outboundQueue队列中去,表示当前键盘事件是一个待处理的键盘事件。 当connection中的outboundQueue事件队列不为空,即wasEmpty为false时,说明当前这个Activity窗口正在处键盘事件了,因此,就不需要调用startDispatchCycleLocked来启动Activity窗口来处理这个事件了,因为一旦这个Activity窗口正在处键盘事件,它就会一直处理下去,直到它里的connection对象的outboundQueue为空为止。当connection中的outboundQueue事件队列为空时,就需要调用startDispatchCycleLocked来通知这个Activity窗口来执行键盘事件处理的流程了。 Step 17.InputDispatcher.startDispatchCycleLocked 这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中: voidInputDispatcher::startDispatchCycleLocked(nsecs_tcurrentTime, constsp<Connection>&connection){ ...... DispatchEntry*dispatchEntry=connection->outboundQueue.headSentinel.next; //Markthedispatchentryasinprogress. dispatchEntry->inProgress=true; //Updatetheconnection'sinputstate. EventEntry*eventEntry=dispatchEntry->eventEntry; ...... //Publishtheevent. status_tstatus; switch(eventEntry->type){ caseEventEntry::TYPE_KEY:{ KeyEntry*keyEntry=static_cast<KeyEntry*>(eventEntry); //Applytargetflags. int32_taction=keyEntry->action; int32_tflags=keyEntry->flags; //Publishthekeyevent. status=connection->inputPublisher.publishKeyEvent(keyEntry->deviceId,keyEntry->source, action,flags,keyEntry->keyCode,keyEntry->scanCode, keyEntry->metaState,keyEntry->repeatCount,keyEntry->downTime, keyEntry->eventTime); ...... break; } ...... } //Sendthedispatchsignal. status=connection->inputPublisher.sendDispatchSignal(); ...... } 这个函数主要围绕传进来的Connection对象做两件事情,一是从它的outboundQueue队列中取出当前需要处理的键盘事件,然后把这个事件记录在它的内部对象inputPublisher中,二是通过它的内部对象inputPublisher通知它所关联的Activity窗口,现在有键盘事件需要处理了。第一件事情是通过调用它的InputPublisher对象的publishKeyEvent函数来完成的,而第二件事情是通过调用它的InputPublisher对象的sendDispatchSignal来完成的。我们先来看InputPublisher的成员函数publishKeyEvent的实现,然后再回来分析它的另外一个成员函数sendDispatchSignal的实现。 Step 18. InputPublisher.publishKeyEvent 这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputPublisher::publishKeyEvent( int32_tdeviceId, int32_tsource, int32_taction, int32_tflags, int32_tkeyCode, int32_tscanCode, int32_tmetaState, int32_trepeatCount, nsecs_tdownTime, nsecs_teventTime){ ...... status_tresult=publishInputEvent(AINPUT_EVENT_TYPE_KEY,deviceId,source); if(result<0){ returnresult; } mSharedMessage->key.action=action; mSharedMessage->key.flags=flags; mSharedMessage->key.keyCode=keyCode; mSharedMessage->key.scanCode=scanCode; mSharedMessage->key.metaState=metaState; mSharedMessage->key.repeatCount=repeatCount; mSharedMessage->key.downTime=downTime; mSharedMessage->key.eventTime=eventTime; returnOK; } 这个函数主要就是把键盘事件记录在InputPublisher类的成员变量mSharedMessage中了,这个mSharedMessage成员变量指向的是一个匿名共享内存。 这个匿名共享内存是什么时候创建的呢?前面我们在分析应用程序注册键盘消息接收通道的过程时,在Step 18中(InputDispatcher.registerInputChannel),在把Server端的InputChannel封装成一个 Connection对象时,会调用它的initialize成员函数来执行一些初始化工作,就是在这个时候创建这个匿名共享内存的了: sp<Connection>connection=newConnection(inputChannel); status_tstatus=connection->initialize(); 我们来看一下这个initialize函数的实现,它定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputPublisher::initialize(){ ...... intashmemFd=mChannel->getAshmemFd(); intresult=ashmem_get_size_region(ashmemFd); ...... mAshmemSize=(size_t)result; mSharedMessage=static_cast<InputMessage*>(mmap(NULL,mAshmemSize, PROT_READ|PROT_WRITE,MAP_SHARED,ashmemFd,0)); ...... mPinned=true; mSharedMessage->consumed=false; returnreset(); } InputPublisher的成员变量mChannel就是指注册在InputDispatcher中的Server端InputChannel了。我们知道,这个InputChannel除了拥有一个反向管道的读端文件描述符和一个前向管道的写端文件描述符之后,还有一个匿名共享文件描述符,这个匿名共享文件描述符就是用来创建匿名共享内存mSharedMessage的了。 这个匿名共享内存mSharedMessage的作用是什么呢?原来,在InputChannel中,前向管道和反向管道的作用只是用来在Server端和Client端之间相互通知有事件发生了,但是具体是什么样的事件,还需要去读取这个匿名共享内存的内容才知道。前面我们在分析应用程序注册键盘消息接收通道的过程时,在Step 14中(InputChannel.openInputChannelPair)创建Server端和Client端的InputChannel对时,创建一个匿名共享内存,这个匿名共享内存有两个文件描述符同时指向它,其中一个放在Server端的InputChannel中,另外一个放在Client端的InputChannel中。这样,当InputDispatcher通过Server端的InputChannel的前向管道来通知Client端有键盘事件发生时,Client端只要通过它的InputChannel中的匿名共享内存文件描述符去读取匿名共享内存中的内容,就可以知道发生了什么事情了。 回到Step 17中,接下来就是调用InputPublisher的成员函数sendDispatchSignal来通知Activity窗口处理键盘事件了。 Step 19.InputPublishe.sendDispatchSignal 这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputPublisher::sendDispatchSignal(){ ...... mWasDispatched=true; returnmChannel->sendSignal(INPUT_SIGNAL_DISPATCH); } 这个函数很简单,它通过调用内部成员变量mChannel的sendSignal函数来通知相应的Activity窗口来处理键盘事件。 Step 20. InputChannel.sendSignal 这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputChannel::sendSignal(charsignal){ ssize_tnWrite; do{ nWrite=::write(mSendPipeFd,&signal,1); }while(nWrite==-1&&errno==EINTR); if(nWrite==1){ ...... returnOK; } return-errno; } 这里所谓的发送信号通知,其实是通过向其内部一个管道的写端写入一个字符来实现的。前面我们分析应用程序注册键盘消息接收通道的过程时,在Step 21中(NativeInputQueue.registerInputChannel),它把一个InputChannel注册到应用程序主线程中的Looper对象中,然后应用程序的主线程就通过这个Looper对象睡眠等待在这个InputChannel中的前向管道中有新的内容可读了,这里的mSendPipeFd就是对应这个前向管道的写端。现在既然向这个前向管道的写端写入新的内容了,于是,应用程序的主线程就被唤醒了。 在前面分析应用程序注册键盘消息接收通道过程的Step 21中,我们也说过,当应用程序的主线程因为这个InputChannel中的前向管道的写端唤醒时,NativeInputQueue的成员函数handleReceiveCallback就会被回调,因此,接下来,应用程序的主线程就会被唤醒,然后执行NativeInputQueue的成员函数handleReceiveCallback。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966636,如需转载请自行联系原作者