首页 文章 精选 留言 我的

精选列表

搜索[安全机制],共10000篇文章
优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(24)

4.应用程序注销键盘消息接收通道的过程分析 当Activity窗口创建时,它会向InputManager注册键盘消息接收通道,而当Activity窗口销毁时,它就会向InputManager注销前面注册的键盘消息接收通道了,本节内容就来看看应用程序注销键盘消息接收通道的过程。 当我们按下键盘上的Back键时,当前激活的Activity窗口就会被失去焦点,但是这时候它还没有被销毁,它的状态被设置为Stopped;当新的Activity窗口即将要显示时,它会通知WindowManagerService,这时候WindowManagerService就会处理当前处理Stopped状态的Activity窗口了,要执行的操作就是销毁它们了,在销毁的时候,就会注销它们之前所注册的键盘消息接收通道。 新的Activity窗口通知WindowManagerService它即将要显示的过程比较复杂,但是它与我们本节要介绍的内容不是很相关,因此,这里就略过大部过程了,我们从ActvitiyRecord的windowsVisible函数开始分析。注意,这里的ActivityRecord是新的Activity窗口在ActivityManangerService的代表,而那些处于Stopped状态的Activity窗口 会放在ActivityStack类的一个等待可见的mWaitingVisibleActivities列表里面,事实于,对于那些Stopped状态的Activity窗口来说,它们是等待销毁,而不是等待可见。 像前面一样,我们先来看一张应用程序注销键盘消息接收通道的过程的序列图,然后根据这个序列图来详细分析互一个步骤: Step 1. ActivityRecord.windowsVisible 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityRecord.java文件中: classActivityRecordextendsIApplicationToken.Stub{ ...... booleannowVisible;//isthisactivity'swindowvisible? booleanidle;//hastheactivitygoneidle? ...... publicvoidwindowsVisible(){ synchronized(service){ ...... if(!nowVisible){ nowVisible=true; if(!idle){ ....... }else{ //Ifthisactivitywasalreadyidle,thenwenowneedto //makesureweperformthefullstopofanyactivities //thatarewaitingtodoso.Thisisbecausewewon't //dothatwhiletheyarestillwaitingforthisoneto //becomevisible. finalintN=stack.mWaitingVisibleActivities.size(); if(N>0){ for(inti=0;i<N;i++){ ActivityRecordr=(ActivityRecord) stack.mWaitingVisibleActivities.get(i); r.waitingVisible=false; ...... } stack.mWaitingVisibleActivities.clear(); Messagemsg=Message.obtain(); msg.what=ActivityStack.IDLE_NOW_MSG; stack.mHandler.sendMessage(msg); } } ...... } } } ...... } 应用程序中的每一个Activity在ActivityManagerService都有一个代表ActivityRecord,它们以堆栈的形式组织在ActivityManaerService中的ActivityStack中。一个即将要显示,但是还没有显示的Activity,它在ActivityManagerService中的ActivityRecord的成员变量nowVisible为false,而成员变量idle为ture,表示这个即将要显示的Activity窗口处于空闲状态。因此,在上面的这个函数中,会执行下面的语句: finalintN=stack.mWaitingVisibleActivities.size(); if(N>0){ for(inti=0;i<N;i++){ ActivityRecordr=(ActivityRecord) stack.mWaitingVisibleActivities.get(i); r.waitingVisible=false; ...... } stack.mWaitingVisibleActivities.clear(); Messagemsg=Message.obtain(); msg.what=ActivityStack.IDLE_NOW_MSG; stack.mHandler.sendMessage(msg); } 前面我们说过,当用户按下键盘上的Back键时,当前激活的Activity记录就被放在ActivityStack对象stack的成员变量mWaitingVisibleActivities中了,这时候就要对它进行处理了。首先是将它们的Activity记录的waitingVisible设置为false,然后就把它们从ActivityStack对象stack的成员变量mWaitingVisibleActivities清空,最后向ActivityStack对象stack发送一个ActivityStack.IDLE_NOW_MSG消息。这个消息最终是由ActivityStack类的activityIdleInternal函数来处理的。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966649,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(9)

Step 22. poll 这是一个Linux系统的文件操作系统调用,它用来查询指定的文件列表是否有有可读写的,如果有,就马上返回,否则的话,就阻塞线程,并等待驱动程序唤醒,重新调用poll函数,或超时返回。在我们的这个场景中,就是要查询是否有键盘事件发生,如果有的话,就返回,否则的话,当前线程就睡眠等待键盘事件的发生了。 这样,InputManager的启动过程就分析完了,下面我们再分析应用程序注册键盘消息接收通道的过程。 2.应用程序注册键盘消息接收通道的过程分析 InputManager启动以后,就开始负责监控键盘输入事件了。当InputManager监控到键盘输入事件时,它应该把这个键盘事件分发给谁呢?当然是要把这个键盘消息分发给当前激活的Activity窗口了,不过,当前激活的Activity窗口还需要主动注册一个键盘消息接收通道到InputManager中去,InputManager才能把这个键盘消息分发给它处理。那么,当前被激活的Activity窗口又是什么时候去注册这个键盘消息接收通道的呢?在前面一篇文章Android应用程序启动过程源代码分析中,我们分析Android应用程序的启动过程时,在Step 33中分析到ActivityThread类的handleLaunchActivity函数中,我们曾经说过,当函数handleLaunchActivity调用performLaunchActivity函数来加载这个完毕应用程序的默认Activity后,再次回到handleLaunchActivity函数时,会调用handleResumeActivity函数来使这个Activity进入Resumed状态。在调用handleResumeActivity函数的过程中,ActivityThread会通过android.view.WindowManagerImpl类为该Activity创建一个ViewRoot实例,并且会通过调用ViewRoot类的setView成员函数把与该Activity关联的View设置到这个ViewRoot中去,而Activity正是通过ViewRoot类的setView成员函数来注册键盘消息接收通道的。 有了这些背影知识后,接下来,我们就可以从ViewRoot.setView函数开始分析应用程序注册键盘消息接收通道的过程了。首先看一下这个注册过程的序列图,然后再详细分析每一个步骤: Step 1. ViewRoot.setView 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... publicvoidsetView(Viewview,WindowManager.LayoutParamsattrs, ViewpanelParentView){ ...... synchronized(this){ if(mView==null){ ...... //Schedulethefirstlayout-before-addingtothewindow //manager,tomakesurewedotherelayoutbeforereceiving //anyothereventsfromthesystem. requestLayout(); mInputChannel=newInputChannel(); try{ res=sWindowSession.add(mWindow,mWindowAttributes, getHostVisibility(),mAttachInfo.mContentInsets, mInputChannel); }catch(RemoteExceptione){ ...... }finally{ ...... } ...... if(viewinstanceofRootViewSurfaceTaker){ mInputQueueCallback= ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); } if(mInputQueueCallback!=null){ mInputQueue=newInputQueue(mInputChannel); mInputQueueCallback.onInputQueueCreated(mInputQueue); }else{ InputQueue.registerInputChannel(mInputChannel,mInputHandler, Looper.myQueue()); } ...... } } } } 这个函数中与注册键盘消息接收通道(InputChannel)相关的逻辑主要有三处,一是调用requestLayout函数来通知InputManager,这个Activity窗口是当前被激活的窗口,二是调用sWindowSession(WindowManagerService内部类Session的远程接口)的add成员函数来把键盘消息接收通道的一端注册在InputManager中,三是调用InputQueue的registerInputChannel成员函数来把键盘消息接收通道的另一端注册在本应用程序的消息循环(Looper)中。这样,当InputManager监控到有键盘消息时,就会先找到当前被激活的窗口,然后找到其在InputManager中对应的键盘消息接收通道,通过这个通道在InputManager中的一端来通知在应用程序消息循环中的另一端,就把键盘消息分发给当前激活的Activity窗口了。 在接下来的内容中,我们首先描述requestLayout函数是如何告诉InputManager当前的Activity窗口便是激活窗口的,接着再回过头来分析应用程序是如何把键盘消息接收通道的一端注册到InputManager中去的,最后分析应用程序是如何键盘消息接收通道的另一端注册到本应用程序的消息循环中去了。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966622,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(21)

Step 30.ViewRoot.dispatchFinishedEvent 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... publicvoiddispatchFinishedEvent(intseq,booleanhandled){ Messagemsg=obtainMessage(FINISHED_EVENT); msg.arg1=seq; msg.arg2=handled?1:0; sendMessage(msg); } ...... } 和前面的Step 26一样,ViewRoot不是直接处理这个键盘事件,而是把它作为一个消息(FINISHED_EVENT)放在消息队列中去,最后,这个消息由ViewRoot的handleFinishedEvent函数来处理。 Step 31.ViewRoot.handleFinishedEvent 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... voidhandleFinishedEvent(intseq,booleanhandled){ finalKeyEventevent=(KeyEvent)retrievePendingEvent(seq); ...... if(event!=null){ finalbooleansendDone=seq>=0; if(!handled){ deliverKeyEventToViewHierarchy(event,sendDone); return; }elseif(sendDone){ ...... }else{ ...... } } } ...... } 如果InputMethodManager没有处理这个键盘事件,那么ViewRoot就会调用deliverKeyEventToViewHierarchy函数来把这个键盘事件分发给当前激活的Activity窗口来处理。 Step 32.ViewRoot.deliverKeyEventToViewHierarchy 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... privatevoiddeliverKeyEventToViewHierarchy(KeyEventevent,booleansendDone){ try{ if(mView!=null&&mAdded){ ...... booleankeyHandled=mView.dispatchKeyEvent(event); } ...... }finally{ if(sendDone){ finishInputEvent(); } } } ...... } 这个函数首先会调用ViewRoot类的成员变量mView的dispatchKeyEvent来处理这个键盘事件,然后最调用ViewRoot类的finishInputEvent来处理手尾工作。 ViewRoot类的成员变量mView的类型为DecorView,它是由ActivityThread类第一次Resume当前的Activity窗口时创建的,具体可以参考ActivityThread类的handleResumeActivity成员函数,这里就不关注了。 Step 33. DecorView.dispatchKeyEvent 这个函数定义在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java文件中,它是PhoneWindow类的一个内部类: publicclassPhoneWindowextendsWindowimplementsMenuBuilder.Callback{ ...... privatefinalclassDecorViewextendsFrameLayoutimplementsRootViewSurfaceTaker{ ...... @Override publicbooleandispatchKeyEvent(KeyEventevent){ ...... finalCallbackcb=getCallback(); finalbooleanhandled=cb!=null&&mFeatureId<0?cb.dispatchKeyEvent(event) :super.dispatchKeyEvent(event); ...... } ...... } ...... } 这里通过getCallback函数返回的是当前应用程序的激活的Activity窗口的Window.Callback接口,一般它不为NULL,因此,这个函数会调用Activity类的dispatchKeyEvent来处理这个键盘事件。 Step 34. Activity.dispatchKeyEvent 这个函数定义在frameworks/base/core/java/android/app/Activity.java文件中: publicclassActivityextendsContextThemeWrapper implementsLayoutInflater.Factory, Window.Callback,KeyEvent.Callback, OnCreateContextMenuListener,ComponentCallbacks{ ...... publicbooleandispatchKeyEvent(KeyEventevent){ ...... Viewdecor=mDecor; if(decor==null)decor=win.getDecorView(); returnevent.dispatch(this,decor!=null ?decor.getKeyDispatcherState():null,this); } ...... } 这里,Activity不是直接处理这个键盘事件,而是通过KeyEvent的dispatch转发一下。注意,KeyEvent的成中函数dispatch的第一个参数的类型是KeyEvent.Callback,而Activity实现了这个接口,因此,这里可以传this引用过去。 Step 35. KeyEvent.dispatch 这个函数定义在frameworks/base/core/java/android/view/KeyEvent.java文件中: publicclassKeyEventextendsInputEventimplementsParcelable{ ...... publicfinalbooleandispatch(Callbackreceiver,DispatcherStatestate, Objecttarget){ switch(mAction){ caseACTION_DOWN:{ ...... booleanres=receiver.onKeyDown(mKeyCode,this); ...... returnres; } caseACTION_UP: ...... returnreceiver.onKeyUp(mKeyCode,this); caseACTION_MULTIPLE: finalintcount=mRepeatCount; finalintcode=mKeyCode; if(receiver.onKeyMultiple(code,count,this)){ returntrue; } ...... returnfalse; } returnfalse; } ...... } 这里就根据一个键是按下(ACTION_DOWN)、还是松开(ACTION_UP)或者是一个相同的键被多次按下和松开(ACTION_MULTIPLE)等不同事件类型来分别调用Activity的onKeyDown、onKeyUp和onKeyMultiple函数了。 Activity窗口处理完这个键盘事件后,层层返回,最后回到Step 32中,调用finishInputEvent事件来处理一些手尾工,下面我们将会看到这些手尾工是什么。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966644,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(14)

3.InputManager分发键盘消息给应用程序的过程分析 在分析InputManager分发键盘消息给应用程序的过程之前,我们先假设现在没有键盘事件发生,因此,InputManager中的InputReader正在睡眠等待键盘事件的发生,而InputManager中的InputDispatcher正在等待InputReader从睡眠中醒过来并且唤醒它,而应用程序也正在消息循环中等待InputDispatcher从睡眠中醒过来并且唤醒它。这时候,用户按下键盘中的一个键,于是,一系列唤醒的事件就依次发生了,一直到应用程序中正在显示的Activity得到通知,有键盘事件发生了。我们先来看这个过程的序列图,然后再详细分析每一个步骤: Step 1. InputReader.pollOnce Step 2. EventHub.getEvent 这两个函数分别定义在frameworks/base/libs/ui/InputReader.cpp和frameworks/base/libs/ui/EventHub.cpp文件中,前面我们在分析InputManager的启动过程的Step 17和Step 18时,已经看到过这两个函数了。InputReaderThread线程会不民地循环调用InputReader.pollOnce函数来读入键盘事件,而实际的键盘事件读入操作是由EventHub.getEvent函数来进行的。如果当前没有键盘事件发生,InputReaderThread线程就会睡眠在EventHub.getEvent函数上,而当键盘事件发生后,就会把这个事件封装成一个RawEvent对象,然后返回到pollOnce函数中,执行process函数进一步处理: voidInputReader::loopOnce(){ RawEventrawEvent; mEventHub->getEvent(&rawEvent); ...... process(&rawEvent); } Step 3. InputReader.process 这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中: voidInputReader::process(constRawEvent*rawEvent){ switch(rawEvent->type){ caseEventHubInterface::DEVICE_ADDED: addDevice(rawEvent->deviceId); break; caseEventHubInterface::DEVICE_REMOVED: removeDevice(rawEvent->deviceId); break; caseEventHubInterface::FINISHED_DEVICE_SCAN: handleConfigurationChanged(rawEvent->when); break; default: consumeEvent(rawEvent); break; } } 当键盘事件发生时,rawEvent->type的值为EV_KEY,这是一个宏定义,具体可以参考bionic/libc/kernel/common/linux/input.h文件: #defineEV_KEY0x01 因此,接下来会调用consumeEvent函数进一步处理。 Step 4.InputReader.consumeEvent 这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中: voidInputReader::consumeEvent(constRawEvent*rawEvent){ int32_tdeviceId=rawEvent->deviceId; {//acquiredeviceregistryreaderlock RWLock::AutoRLock_rl(mDeviceRegistryLock); ssize_tdeviceIndex=mDevices.indexOfKey(deviceId); if(deviceIndex<0){ LOGW("DiscardingeventforunknowndeviceId%d.",deviceId); return; } InputDevice*device=mDevices.valueAt(deviceIndex); if(device->isIgnored()){ //LOGD("DiscardingeventforignoreddeviceId%d.",deviceId); return; } device->process(rawEvent); }//releasedeviceregistryreaderlock } 首先从rawEvent中取得触发键盘事件设备对象device,然后调用它的process函数进行处理。 Step 5. InputDevice.process 这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中: voidInputDevice::process(constRawEvent*rawEvent){ size_tnumMappers=mMappers.size(); for(size_ti=0;i<numMappers;i++){ InputMapper*mapper=mMappers[i]; mapper->process(rawEvent); } } 这里的mMapper成员变量保存了一系列输入设备事件处理象,例如负责处理键盘事件的KeyboardKeyMapper对象、负责处理轨迹球事件的TrackballInputMapper对象以及负责处理触摸屏事件的TouchInputMapper对象, 它们是在InputReader类的成员函数createDevice中创建的。这里查询每一个InputMapper对象是否要对当前发生的事件进行处理。由于发生的是键盘事件,真正会对该事件进行处理的只有KeyboardKeyMapper对象。 Step 6.KeyboardInputMapper.process 这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中: voidKeyboardInputMapper::process(constRawEvent*rawEvent){ switch(rawEvent->type){ caseEV_KEY:{ int32_tscanCode=rawEvent->scanCode; if(isKeyboardOrGamepadKey(scanCode)){ processKey(rawEvent->when,rawEvent->value!=0,rawEvent->keyCode,scanCode, rawEvent->flags); } break; } } } 这个函数首先会检查一下键盘扫描码是否正确,如果正确的话,就会调用processKey函数进一步处理。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966632,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(15)

Step 7.KeyboardInputMapper.processKey 这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中: voidKeyboardInputMapper::processKey(nsecs_twhen,booldown,int32_tkeyCode, int32_tscanCode,uint32_tpolicyFlags){ int32_tnewMetaState; nsecs_tdownTime; boolmetaStateChanged=false; {//acquirelock AutoMutex_l(mLock); if(down){ //Rotatekeycodesaccordingtoorientationifneeded. //Note:getDisplayInfoisnon-reentrantsowecancontinueholdingthelock. if(mAssociatedDisplayId>=0){ int32_torientation; if(!getPolicy()->getDisplayInfo(mAssociatedDisplayId,NULL,NULL,&orientation)){ return; } keyCode=rotateKeyCode(keyCode,orientation); } //Addkeydown. ssize_tkeyDownIndex=findKeyDownLocked(scanCode); if(keyDownIndex>=0){ //keyrepeat,besuretousesamekeycodeasbeforeincaseofrotation keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode; }else{ //keydown if((policyFlags&POLICY_FLAG_VIRTUAL) &&mContext->shouldDropVirtualKey(when,getDevice(),keyCode,scanCode)){ return; } mLocked.keyDowns.push(); KeyDown&keyDown=mLocked.keyDowns.editTop(); keyDown.keyCode=keyCode; keyDown.scanCode=scanCode; } mLocked.downTime=when; }else{ //Removekeydown. ssize_tkeyDownIndex=findKeyDownLocked(scanCode); if(keyDownIndex>=0){ //keyup,besuretousesamekeycodeasbeforeincaseofrotation keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode; mLocked.keyDowns.removeAt(size_t(keyDownIndex)); }else{ //keywasnotactuallydown LOGI("Droppingkeyupfromdevice%sbecausethekeywasnotdown." "keyCode=%d,scanCode=%d", getDeviceName().string(),keyCode,scanCode); return; } } int32_toldMetaState=mLocked.metaState; newMetaState=updateMetaState(keyCode,down,oldMetaState); if(oldMetaState!=newMetaState){ mLocked.metaState=newMetaState; metaStateChanged=true; } downTime=mLocked.downTime; }//releaselock if(metaStateChanged){ getContext()->updateGlobalMetaState(); } getDispatcher()->notifyKey(when,getDeviceId(),AINPUT_SOURCE_KEYBOARD,policyFlags, down?AKEY_EVENT_ACTION_DOWN:AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM,keyCode,scanCode,newMetaState,downTime); } 这个函数首先对对按键作一些处理,例如,当某一个DPAD键被按下时,根据当时屏幕方向的不同,它所表示的意义也不同,因此,这里需要根据当时屏幕的方向来调整键盘码: //Rotatekeycodesaccordingtoorientationifneeded. //Note:getDisplayInfoisnon-reentrantsowecancontinueholdingthelock. if(mAssociatedDisplayId>=0){ int32_torientation; if(!getPolicy()->getDisplayInfo(mAssociatedDisplayId,NULL,NULL,&orientation)){ return; } keyCode=rotateKeyCode(keyCode,orientation); } 如果这个键是一直按着不放的,不管屏幕的方向如何,必须保证后面的键盘码和前面的一样: //Addkeydown. ssize_tkeyDownIndex=findKeyDownLocked(scanCode); if(keyDownIndex>=0){ //keyrepeat,besuretousesamekeycodeasbeforeincaseofrotation keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode; }else{ //keydown if((policyFlags&POLICY_FLAG_VIRTUAL) &&mContext->shouldDropVirtualKey(when,getDevice(),keyCode,scanCode)){ return; } mLocked.keyDowns.push(); KeyDown&keyDown=mLocked.keyDowns.editTop(); keyDown.keyCode=keyCode; keyDown.scanCode=scanCode; } 如果是第一次按下某个键,还必须把它保存在mLocked.keyDowns里面,就是为了处理上面讲的当这个键盘一直按着不放的时候屏幕方向发生改变的情况。 如果是松开键盘上的某个键,就把它从mLocked.keyDowns里面删除: //Removekeydown. ssize_tkeyDownIndex=findKeyDownLocked(scanCode); if(keyDownIndex>=0){ //keyup,besuretousesamekeycodeasbeforeincaseofrotation keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode; mLocked.keyDowns.removeAt(size_t(keyDownIndex)); }else{ //keywasnotactuallydown LOGI("Droppingkeyupfromdevice%sbecausethekeywasnotdown." "keyCode=%d,scanCode=%d", getDeviceName().string(),keyCode,scanCode); return; } 当然,对键盘事件的这些处理不是本文的重点,本文的重点是分析从键盘事件到当前激活的Activity窗口接收到这个键盘消息的过程。 最后,KeyboardInputMappger函数通知InputDispatcher,有键盘事件发生了: getDispatcher()->notifyKey(when,getDeviceId(),AINPUT_SOURCE_KEYBOARD,policyFlags, down?AKEY_EVENT_ACTION_DOWN:AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM,keyCode,scanCode,newMetaState,downTime); 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966633,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(4)

Step 18. EventHub.getEvent 这个函数定义在frameworks/base/libs/ui/EventHub.cpp文件中: boolEventHub::getEvent(RawEvent*outEvent) { outEvent->deviceId=0; outEvent->type=0; outEvent->scanCode=0; outEvent->keyCode=0; outEvent->flags=0; outEvent->value=0; outEvent->when=0; //NotethatweonlyallowonecallertogetEvent(),sodon'tneed //todolockinghere...onlywhenadding/removingdevices. if(!mOpened){ mError=openPlatformInput()?NO_ERROR:UNKNOWN_ERROR; mOpened=true; mNeedToSendFinishedDeviceScan=true; } for(;;){ //Reportanydevicesthathadlastbeenadded/removed. if(mClosingDevices!=NULL){ device_t*device=mClosingDevices; LOGV("Reportingdeviceclosed:id=0x%x,name=%s\n", device->id,device->path.string()); mClosingDevices=device->next; if(device->id==mFirstKeyboardId){ outEvent->deviceId=0; }else{ outEvent->deviceId=device->id; } outEvent->type=DEVICE_REMOVED; outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC); deletedevice; mNeedToSendFinishedDeviceScan=true; returntrue; } if(mOpeningDevices!=NULL){ device_t*device=mOpeningDevices; LOGV("Reportingdeviceopened:id=0x%x,name=%s\n", device->id,device->path.string()); mOpeningDevices=device->next; if(device->id==mFirstKeyboardId){ outEvent->deviceId=0; }else{ outEvent->deviceId=device->id; } outEvent->type=DEVICE_ADDED; outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC); mNeedToSendFinishedDeviceScan=true; returntrue; } if(mNeedToSendFinishedDeviceScan){ mNeedToSendFinishedDeviceScan=false; outEvent->type=FINISHED_DEVICE_SCAN; outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC); returntrue; } //Grabthenextinputevent. for(;;){ //Consumebufferedinputevents,ifany. if(mInputBufferIndex<mInputBufferCount){ conststructinput_event&iev=mInputBufferData[mInputBufferIndex++]; constdevice_t*device=mDevices[mInputDeviceIndex]; LOGV("%sgot:t0=%d,t1=%d,type=%d,code=%d,v=%d",device->path.string(), (int)iev.time.tv_sec,(int)iev.time.tv_usec,iev.type,iev.code,iev.value); if(device->id==mFirstKeyboardId){ outEvent->deviceId=0; }else{ outEvent->deviceId=device->id; } outEvent->type=iev.type; outEvent->scanCode=iev.code; if(iev.type==EV_KEY){ status_terr=device->layoutMap->map(iev.code, &outEvent->keyCode,&outEvent->flags); LOGV("iev.code=%dkeyCode=%dflags=0x%08xerr=%d\n", iev.code,outEvent->keyCode,outEvent->flags,err); if(err!=0){ outEvent->keyCode=AKEYCODE_UNKNOWN; outEvent->flags=0; } }else{ outEvent->keyCode=iev.code; } outEvent->value=iev.value; //Useaneventtimestampinthesametimebaseas //java.lang.System.nanoTime()andandroid.os.SystemClock.uptimeMillis() //asexpectedbytherestofthesystem. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC); returntrue; } //Finishreadingalleventsfromdevicesidentifiedinpreviouspoll(). //ThiscodeassumesthatmInputDeviceIndexisinitially0andthatthe //reventsmemberofpollfdisinitializedto0whenthedeviceisfirstadded. //SincemFDs[0]isusedforinotify,weprocessregulareventsstartingatindex1. mInputDeviceIndex+=1; if(mInputDeviceIndex>=mFDCount){ break; } conststructpollfd&pfd=mFDs[mInputDeviceIndex]; if(pfd.revents&POLLIN){ int32_treadSize=read(pfd.fd,mInputBufferData, sizeof(structinput_event)*INPUT_BUFFER_SIZE); if(readSize<0){ if(errno!=EAGAIN&&errno!=EINTR){ LOGW("couldnotgetevent(errno=%d)",errno); } }elseif((readSize%sizeof(structinput_event))!=0){ LOGE("couldnotgetevent(wrongsize:%d)",readSize); }else{ mInputBufferCount=readSize/sizeof(structinput_event); mInputBufferIndex=0; } } } ...... mInputDeviceIndex=0; //Pollforevents.Mindthewakelockdance! //Weholdawakelockatalltimesexceptduringpoll().Thisworksduetosome //subtlechoreography.Whenadevicedriverhaspending(unread)events,itacquires //akernelwakelock.However,oncethelastpendingeventhasbeenread,thedevice //driverwillreleasethekernelwakelock.Topreventthesystemfromgoingtosleep //whenthishappens,theEventHubholdsontoitsownuserwakelockwhiletheclient //isprocessingevents.Thusthesystemcanonlysleepiftherearenoevents //pendingorcurrentlybeingprocessed. release_wake_lock(WAKE_LOCK_ID); intpollResult=poll(mFDs,mFDCount,-1); acquire_wake_lock(PARTIAL_WAKE_LOCK,WAKE_LOCK_ID); if(pollResult<=0){ if(errno!=EINTR){ LOGW("pollfailed(errno=%d)\n",errno); usleep(100000); } } } } 这个函数比较长,我们一步一步来分析。 首先,如果是第一次进入到这个函数中时,成员变量mOpened的值为false,于是就会调用openPlatformInput函数来打开系统输入设备,在本文中,我们主要讨论的输入设备就是键盘了。打开了这些输入设备文件后,就可以对这些输入设备进行是监控了。如果不是第一次进入到这个函数,那么就会分析当前有没有输入事件发生,如果有,就返回这个事件,否则就会进入等待状态,等待下一次输入事件的发生。在我们这个场景中,就是等待下一次键盘事件的发生了。 我们先分析openPlatformInput函数的实现,然后回过头来分析这个getEvent函数的具体的实现。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966613,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(27)

Step 14.NativeInputQueue.unregisterInputChannel 这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中: status_tNativeInputQueue::unregisterInputChannel(JNIEnv*env,jobjectinputChannelObj){ sp<InputChannel>inputChannel=android_view_InputChannel_getInputChannel(env, inputChannelObj); ...... {//acquirelock AutoMutex_l(mLock); ssize_tconnectionIndex=getConnectionIndex(inputChannel); ...... sp<Connection>connection=mConnectionsByReceiveFd.valueAt(connectionIndex); mConnectionsByReceiveFd.removeItemsAt(connectionIndex); connection->status=Connection::STATUS_ZOMBIE; connection->looper->removeFd(inputChannel->getReceivePipeFd()); env->DeleteGlobalRef(connection->inputHandlerObjGlobal); connection->inputHandlerObjGlobal=NULL; ...... }//releaselock ...... returnOK; } 真正的注销工作就是这里实现的了,读者可以对照前面介绍应用程序注册键盘消息接收通道过程中的Step 21(NativeInputQueue.registerInputChannel)来分析,它首先是将在之前创建的Connection对象从NativeInputQueue中的mConnectionByReceiveFd向量中删除: ssize_tconnectionIndex=getConnectionIndex(inputChannel); ...... sp<Connection>connection=mConnectionsByReceiveFd.valueAt(connectionIndex); mConnectionsByReceiveFd.removeItemsAt(connectionIndex); 然后再把这个Client端InputChannel的前向管道的读端文件描述符从应用程序主线程中的Looper对象中删除: connection->looper->removeFd(inputChannel->getReceivePipeFd()); 这样,这个Activity窗口以后就不会接收到键盘事件了。 最后将Connection对象中的回调对象inputHandlerOjbGlobal对象删除: env->DeleteGlobalRef(connection->inputHandlerObjGlobal); connection->inputHandlerObjGlobal=NULL; 回忆一下前面我们在分析InputManager分发键盘消息给应用程序处理时,曾经说到,每当有键盘事件发生时,InputManager首先就会调用NativeInputQueue类的handleReceiveCallback函数。在这个handleReceiveCallback函数里面,NativeInputQueue会找到相应的Connection对象,然后把它里面的内部对象inputHandlerOjbGlobal作为参数来调用Java层的InputQueue类的dispatchKeyEvent函数来通知应用程序,有键盘事件发生了。在InputQueue类的dispatchKeyEvent函数里面,就是通过这个inputHandlerOjbGlobal对象来直正通知到当前激活的Activity窗口来处理这个键盘事件的。 注册在应用程序这一侧的Client端InputChannel被注销以后,回到前面的Step 11中,我们继续分析注销注册在InputManager这一侧的Server端InputChannel。Step 15. WindowManagerService.Session.remove 这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中: publicclassWindowManagerServiceextendsIWindowManager.Stub implementsWatchdog.Monitor{ ...... privatefinalclassSessionextendsIWindowSession.Stub implementsIBinder.DeathRecipient{ ...... publicvoidremove(IWindowwindow){ removeWindow(this,window); } ...... } ...... } 这个函数只是简单地调用其外部类WindowManagerService的removeWindow函数来进一步执行操作。 Step 16.WindowManagerService.removeWindow 这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中: publicclassWindowManagerServiceextendsIWindowManager.Stub implementsWatchdog.Monitor{ ...... publicvoidremoveWindow(Sessionsession,IWindowclient){ synchronized(mWindowMap){ WindowStatewin=windowForClientLocked(session,client,false); if(win==null){ return; } removeWindowLocked(session,win); } } ...... } 回忆一下前面我们在分析应用程序注册键盘消息管道的过程时,在Step 11(WindowManagerService.addWindow)中,WindowManagerService为这个即将要激活的Activity窗口创建了一个WindowState对象win,创建的时候,使用了从ViewRoot中传过来的两个参数,分别是一个Session对象session和一个IWindow对象client。 在这个函数中,ViewRoot传过来的两个参数session和client和上面说的两个参数是一致的,因此,这个函数首先通过参数session和client得到一个WindowState对象win,然后调用removeWindowLocked来把它从WindowManagerService删除。 Step 17.WindowManagerService.removeWindowLocked 这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中: publicclassWindowManagerServiceextendsIWindowManager.Stub implementsWatchdog.Monitor{ ...... publicvoidremoveWindowLocked(Sessionsession,WindowStatewin){ ...... win.disposeInputChannel(); ...... } ...... } 我们忽略了这个函数的其它逻辑,只关注注销之前注册的Server端InputChannel的逻辑,这里,注销的操作就是调用win的disposeInputChannel进行的了。 Step 18. WindowState.disposeInputChannel 这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中: publicclassWindowManagerServiceextendsIWindowManager.Stub implementsWatchdog.Monitor{ ...... privatefinalclassWindowStateimplementsWindowManagerPolicy.WindowState{ ...... voiddisposeInputChannel(){ if(mInputChannel!=null){ mInputManager.unregisterInputChannel(mInputChannel); mInputChannel.dispose(); mInputChannel=null; } } ...... } ...... } 上面说到,在前面分析应用程序注册键盘消息管道的过程时,在Step 11(WindowManagerService.addWindow)中,为当前这个Activity窗口创建了一个WindowState对象,接着创建了一个输入管道后,把Server端的InputChannel保存了在这个WindowState对象的成员变量mInputChannel中,因此,这里,就可以把它取回来,然后调用mInputManager对象的unregisterInputChannel函数来把它注销掉了。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966658,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(25)

Step 2. ActivityStack.activityIdleInternal 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... finalvoidactivityIdleInternal(IBindertoken,booleanfromTimeout, Configurationconfig){ ...... ArrayList<ActivityRecord>stops=null; ...... intNS=0; ...... synchronized(mService){ ...... //Atomicallyretrievealloftheotherthingstodo. stops=processStoppingActivitiesLocked(true); NS=stops!=null?stops.size():0; ...... } inti; ...... //Stopanyactivitiesthatarescheduledtodosobuthavebeen //waitingforthenextonetostart. for(i=0;i<NS;i++){ ActivityRecordr=(ActivityRecord)stops.get(i); synchronized(mService){ if(r.finishing){ finishCurrentActivityLocked(r,FINISH_IMMEDIATELY); }else{ ...... } } } ...... } ...... } 这个函数首先会调用processStoppingActivitiesLocked函数把所有处于Stopped状态的Activity取回来,然后逐个分析它们,如果它们的ActivityRecord中的finishing成员变量为true,就说明这个Activity需要销毁了,于是,就调用finishCurrentActivityLocked函数来销毁它们。 Step 3.ActivityStack.finishCurrentActivityLocked 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... privatefinalActivityRecordfinishCurrentActivityLocked(ActivityRecordr, intmode){ ...... returnfinishCurrentActivityLocked(r,index,mode); } privatefinalActivityRecordfinishCurrentActivityLocked(ActivityRecordr, intindex,intmode){ ...... //makesuretherecordiscleanedoutofotherplaces. mStoppingActivities.remove(r); mWaitingVisibleActivities.remove(r); ...... finalActivityStateprevState=r.state; r.state=ActivityState.FINISHING; if(mode==FINISH_IMMEDIATELY ||prevState==ActivityState.STOPPED ||prevState==ActivityState.INITIALIZING){ //Ifthisactivityisalreadystopped,wecanjustfinish //itrightnow. returndestroyActivityLocked(r,true)?null:r; }else{ ...... } returnr; } ...... } 从上面的Step 2中传进来的参数mode为FINISH_IMMEDIATELY,并且这个即将要被销毁的Activity的状态为Stopped,因此,接下来就会调用destroyActivityLocked函数来销毁它。 Step 4.ActivityStack.destroyActivityLocked 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... finalbooleandestroyActivityLocked(ActivityRecordr, booleanremoveFromApp){ ...... booleanremovedFromHistory=false; ...... finalbooleanhadApp=r.app!=null; if(hadApp){ ...... try{ ...... r.app.thread.scheduleDestroyActivity(r,r.finishing, r.configChangeFlags); }catch(Exceptione){ ...... } ...... }else{ ...... } ...... returnremovedFromHistory; } ...... } 在前面一篇文章Android应用程序启动过程源代码分析中,我们说到,每一个应用程序进程在ActivityManagerService中,都ProcessRecord记录与之对应,而每一个Activity,都是运行在一个进程上下文中,因此,在ActivityManagerService中,每一个ActivityRecord的app成员变量都应该指向一个ProcessRecord记录,于是,这里得到的hadApp为true。在ProcessRecord类中,有一个成员变量thread,它的类型为IApplicationThread。在文章Android应用程序启动过程源代码分析中,我们也曾经说过,每一个应用程序在启动的时候,它都会在内部创建一个ActivityThread对象,而在这个ActivityThread对象中,有一个成员变量mAppThread,它的类型为ApplicationThread,这是一个Binder对象,专门用来负责在应用程序和ActivityManagerService之间执行进程间通信工作的。应用程序在启动的时候,就会将这个Binder对象传递给ActivityManagerService,而ActivityManagerService就会把它保存在相应的ProcessRecord记录的thread成员变量中。因此,ProcessRecord记录的thread成员变量其实就是ApplicationThread对象的远程接口,于是,执行下面这个语句的时候: r.app.thread.scheduleDestroyActivity(r,r.finishing, r.configChangeFlags); 就会进入到ApplicationThread类中的scheduleDestroyActivity函数来。 Step 5.ApplicationThread.scheduleDestroyActivity 这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中: publicfinalclassActivityThread{ ...... privatefinalclassApplicationThreadextendsApplicationThreadNative{ ...... publicfinalvoidscheduleDestroyActivity(IBindertoken,booleanfinishing, intconfigChanges){ queueOrSendMessage(H.DESTROY_ACTIVITY,token,finishing?1:0, configChanges); } ...... } ...... } 这个函数调用外部类ActivityThread的queueOrSendMessage函数来往应用程序的消息队列中发送一个H.DESTROY_ACTIVITY消息,这个消息最终由ActivityThread类的handleDestroyActivity函数来处理。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966656,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(26)

Step 7.LocalWindowManager.removeViewImmediate 这个函数定义在frameworks/base/core/java/android/view/Window.java文件中: publicabstractclassWindow{ ...... privateclassLocalWindowManagerimplementsWindowManager{ ...... publicfinalvoidremoveViewImmediate(Viewview){ mWindowManager.removeViewImmediate(view); } ...... privatefinalWindowManagermWindowManager; } ...... } LocalWindowManager类的成员变量mWindowManager是一个WndowManagerImpl对象,这个函数只是简单地调用WndowManagerImpl类的removeViewImmediate来进一步处理。 Step 8.WndowManagerImpl.removeViewImmediate 这个函数定义在frameworks/base/core/java/android/view/WindowManagerImpl.java文件中: publicclassWindowManagerImplimplementsWindowManager{ ...... publicvoidremoveViewImmediate(Viewview){ synchronized(this){ intindex=findViewLocked(view,true); ViewRootroot=mRoots[index]; ...... root.die(true); ...... } } ...... } 这个函数首先是找到这个view所属的ViewRoot对象root,然后调用这个root对象的die函数来销毁它。 Step 9. ViewRoot.die 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... publicvoiddie(booleanimmediate){ if(immediate){ doDie(); }else{ ...... } } ...... } 上面Step 8传进来的immediate参数为true,因此,这里直接调用doDie函数来进一步处理。 Step 10.ViewRoot.doDie 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... voiddoDie(){ ...... synchronized(this){ ...... if(mAdded){ mAdded=false; dispatchDetachedFromWindow(); } } } ...... } 当我们把Activity窗口中的View添加到一个ViewRoot对象时,就会把它的成员变量mAdded设置为true,这样就表示这个ViewRoot中有View存在,于是,这里就会调用dispatchDetachedFromWindow函数来进一步处理。 Step 11.ViewRoot.ispatchDetachedFromWindow 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... voiddispatchDetachedFromWindow(){ ...... if(mInputChannel!=null){ if(mInputQueueCallback!=null){ ...... }else{ InputQueue.unregisterInputChannel(mInputChannel); } } try{ sWindowSession.remove(mWindow); }catch(RemoteExceptione){ } ...... } ...... } 前面在介绍应用程序注册键盘消息接收通道的过程时,在Step 18,我们说到,ViewRoot类中的mInputQueueCallback为null,表示由这个ViewRoot自己来管理键盘输入事件,因此,这里首先会调用InputQueue的unregisterInputChannel函数来注销注册在应用程序这一侧的Client端InputChannel,然后再调用sWindowSession的remove函数来注销注册在InputManager这一侧的Server端InputChannel,这个逻辑是和前面介绍应用程序注册键盘消息接收通道的逻辑相对应的,前面分别注册了这两个InputChannel,现在Activity要销毁了,当然就要把它们注销了。 我们先来看注销注册在应用程序这一侧的Client端InputChannel,然后再回过头来分析注销注册在InputManager这一侧的Server端InputChannel。 Step 12.InputQueue.unregisterInputChannel 这个函数定义在frameworks/base/core/java/android/view/InputQueue.java文件中: publicfinalclassInputQueue{ ...... publicstaticvoidunregisterInputChannel(InputChannelinputChannel){ ...... synchronized(sLock){ ...... nativeUnregisterInputChannel(inputChannel); } } ...... } 这个函数只是简单地调用本地方法nativeUnregisterInputChannel来执行具体的操作。 Step 13.InputQueue.nativeUnregisterInputChannel 这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中: staticvoidandroid_view_InputQueue_nativeUnregisterInputChannel(JNIEnv*env,jclassclazz, jobjectinputChannelObj){ status_tstatus=gNativeInputQueue.unregisterInputChannel(env,inputChannelObj); ...... } 这里调用NativeInputQueue的成员函数unregisterInputChannel来进一步处理。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966657,如需转载请自行联系原作者

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册