`
yanlijun250
  • 浏览: 748488 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

上接:Android应用程序键盘(Keyboard)消息处理机制分析(一)

-----
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文件中:

  1. publicfinalclassViewRootextendsHandlerimplementsViewParent,
  2. View.AttachInfo.Callbacks{
  3. ......
  4. publicvoidsetView(Viewview,WindowManager.LayoutParamsattrs,
  5. ViewpanelParentView){
  6. ......
  7. synchronized(this){
  8. if(mView==null){
  9. ......
  10. //Schedulethefirstlayout-before-addingtothewindow
  11. //manager,tomakesurewedotherelayoutbeforereceiving
  12. //anyothereventsfromthesystem.
  13. requestLayout();
  14. mInputChannel=newInputChannel();
  15. try{
  16. res=sWindowSession.add(mWindow,mWindowAttributes,
  17. getHostVisibility(),mAttachInfo.mContentInsets,
  18. mInputChannel);
  19. }catch(RemoteExceptione){
  20. ......
  21. }finally{
  22. ......
  23. }
  24. ......
  25. if(viewinstanceofRootViewSurfaceTaker){
  26. mInputQueueCallback=
  27. ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
  28. }
  29. if(mInputQueueCallback!=null){
  30. mInputQueue=newInputQueue(mInputChannel);
  31. mInputQueueCallback.onInputQueueCreated(mInputQueue);
  32. }else{
  33. InputQueue.registerInputChannel(mInputChannel,mInputHandler,
  34. Looper.myQueue());
  35. }
  36. ......
  37. }
  38. }
  39. }
  40. }

这个函数中与注册键盘消息接收通道(InputChannel)相关的逻辑主要有三处,一是调用requestLayout函数来通知InputManager,这个Activity窗口是当前被激活的窗口,二是调用sWindowSession(WindowManagerService内部类Session的远程接口)的add成员函数来把键盘消息接收通道的一端注册在InputManager中,三是调用InputQueue的registerInputChannel成员函数来把键盘消息接收通道的另一端注册在本应用程序的消息循环(Looper)中。这样,当InputManager监控到有键盘消息时,就会先找到当前被激活的窗口,然后找到其在InputManager中对应的键盘消息接收通道,通过这个通道在InputManager中的一端来通知在应用程序消息循环中的另一端,就把键盘消息分发给当前激活的Activity窗口了。

在接下来的内容中,我们首先描述requestLayout函数是如何告诉InputManager当前的Activity窗口便是激活窗口的,接着再回过头来分析应用程序是如何把键盘消息接收通道的一端注册到InputManager中去的,最后分析应用程序是如何键盘消息接收通道的另一端注册到本应用程序的消息循环中去了。

Step 2.ViewRoot.requestLayout

这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中:

  1. publicfinalclassViewRootextendsHandlerimplementsViewParent,
  2. View.AttachInfo.Callbacks{
  3. ......
  4. publicvoidrequestLayout(){
  5. ......
  6. mLayoutRequested=true;
  7. scheduleTraversals();
  8. }
  9. ......
  10. }
这个函数调用了scheduleTraversals函数来进一步执行操作,由于篇幅关系,我们就不详细描述scheduleTraversals函数了,简单来说,在scheduleTraversals函数中,会通过sendEmptyMessage(DO_TRAVERSAL)发送一个消息到应用程序的消息队列中,这个消息最终由ViewRoot的handleMessage函数处理,而ViewRoot的handleMessage函数把这个消息交给ViewRoot类的performTraversals来处理,在performTraversals函数中,又会调用ViewRoot类的relayoutWindow函数来进一步执行操作,最后在relayoutWindow函数中,就会通过WindowManagerService内部类Session的远程接口sWindowSession的relayout函数来进入到WindowManagerService中。

Step 3.WindowManagerService.Session.relayout

这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. privatefinalclassSessionextendsIWindowSession.Stub
  5. implementsIBinder.DeathRecipient{
  6. ......
  7. publicintrelayout(IWindowwindow,WindowManager.LayoutParamsattrs,
  8. intrequestedWidth,intrequestedHeight,intviewFlags,
  9. booleaninsetsPending,RectoutFrame,RectoutContentInsets,
  10. RectoutVisibleInsets,ConfigurationoutConfig,SurfaceoutSurface){
  11. //Log.d(TAG,">>>>>>ENTEREDrelayoutfrom"+Binder.getCallingPid());
  12. intres=relayoutWindow(this,window,attrs,
  13. requestedWidth,requestedHeight,viewFlags,insetsPending,
  14. outFrame,outContentInsets,outVisibleInsets,outConfig,outSurface);
  15. //Log.d(TAG,"<<<<<<EXITINGrelayoutto"+Binder.getCallingPid());
  16. returnres;
  17. }
  18. ......
  19. }
  20. ......
  21. }

这个函数只是简单地调用WindowManagerService的成员函数relayoutWIndow来进一步处理。

Step 4.WindowManagerService.relayoutWIndow

这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. publicintrelayoutWindow(Sessionsession,IWindowclient,
  5. WindowManager.LayoutParamsattrs,intrequestedWidth,
  6. intrequestedHeight,intviewVisibility,booleaninsetsPending,
  7. RectoutFrame,RectoutContentInsets,RectoutVisibleInsets,
  8. ConfigurationoutConfig,SurfaceoutSurface){
  9. ......
  10. synchronized(mWindowMap){
  11. ......
  12. mInputMonitor.updateInputWindowsLw();
  13. }
  14. ......
  15. }
  16. ......
  17. }
这个函数又会继续调用mInputMonitor的updateInputWindowsLw成员函数来更新当前的输入窗口,mInputMonitor是WindowManagerService的成员变量,它的类型为InputMonitor。

Step 5.InputMonitor.updateInputWindowsLw

这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. finalclassInputMonitor{
  5. ......
  6. /*Updatesthecachedwindowinformationprovidedtotheinputdispatcher.*/
  7. publicvoidupdateInputWindowsLw(){
  8. //Populatetheinputwindowlistwithinformationaboutallofthewindowsthat
  9. //couldpotentiallyreceiveinput.
  10. //Asanoptimization,wecouldtrytoprunethelistofwindowsbutthisturns
  11. //outtobedifficultbecauseonlythenativecodeknowsforsurewhichwindow
  12. //currentlyhastouchfocus.
  13. finalArrayList<WindowState>windows=mWindows;
  14. finalintN=windows.size();
  15. for(inti=N-1;i>=0;i--){
  16. finalWindowStatechild=windows.get(i);
  17. if(child.mInputChannel==null||child.mRemoved){
  18. //Skipthiswindowbecauseitcannotpossiblyreceiveinput.
  19. continue;
  20. }
  21. ......
  22. //Addawindowtoourlistofinputwindows.
  23. finalInputWindowinputWindow=mTempInputWindows.add();
  24. ......
  25. }
  26. //Sendwindowstonativecode.
  27. mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray());
  28. ......
  29. }
  30. ......
  31. }
  32. ......
  33. }
这个函数将当前系统中带有InputChannel的Activity窗口都设置为InputManager的输入窗口,但是后面我们会看到,只有当前激活的窗口才会响应键盘消息。

Step 6.InputManager.setInputWindows

这个函数定义在frameworks/base/services/java/com/android/server/InputManager.java文件中:

  1. publicclassInputManager{
  2. ......
  3. publicvoidsetInputWindows(InputWindow[]windows){
  4. nativeSetInputWindows(windows);
  5. }
  6. ......
  7. }
这个函数调用了本地方法nativeSetInputWindows来进一步执行操作。
Step 7.InputManager.nativeSetInputWindows

这个函数定义在frameworks/base/services/jni/com_android_server_InputManager.cpp文件中:

  1. staticvoidandroid_server_InputManager_nativeSetInputWindows(JNIEnv*env,jclassclazz,
  2. jobjectArraywindowObjArray){
  3. if(checkInputManagerUnitialized(env)){
  4. return;
  5. }
  6. gNativeInputManager->setInputWindows(env,windowObjArray);
  7. }
这里的gNativeInputManager我们前面分析InputManager的启动过程时已经见过了,这是一个本地InputManager对象,通过它进一步设置当前系统的输入窗口。

Step 8.NativeInputManager.setInputWindows

这个函数定义在frameworks/base/services/jni/com_android_server_InputManager.cpp文件中:

  1. voidNativeInputManager::setInputWindows(JNIEnv*env,jobjectArraywindowObjArray){
  2. Vector<InputWindow>windows;
  3. jsizelength=env->GetArrayLength(windowObjArray);
  4. for(jsizei=0;i<length;i++){
  5. jobjectinputTargetObj=env->GetObjectArrayElement(windowObjArray,i);
  6. if(!inputTargetObj){
  7. break;//foundnullelementindicatingendofusedportionofthearray
  8. }
  9. windows.push();
  10. InputWindow&window=windows.editTop();
  11. boolvalid=populateWindow(env,inputTargetObj,window);
  12. if(!valid){
  13. windows.pop();
  14. }
  15. env->DeleteLocalRef(inputTargetObj);
  16. }
  17. mInputManager->getDispatcher()->setInputWindows(windows);
  18. }
这个函数首先将Java层的Window转换成C++层的InputWindow,然后放在windows向量中,最后将这些输入窗口设置到InputDispatcher中去。

Step 9. InputDispatcher.setInputWindows

这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中:

  1. voidInputDispatcher::setInputWindows(constVector<InputWindow>&inputWindows){
  2. ......
  3. {//acquirelock
  4. AutoMutex_l(mLock);
  5. //Clearoldwindowpointers.
  6. sp<InputChannel>oldFocusedWindowChannel;
  7. if(mFocusedWindow){
  8. oldFocusedWindowChannel=mFocusedWindow->inputChannel;
  9. mFocusedWindow=NULL;
  10. }
  11. mWindows.clear();
  12. //Loopovernewwindowsandrebuildthenecessarywindowpointersfor
  13. //trackingfocusandtouch.
  14. mWindows.appendVector(inputWindows);
  15. size_tnumWindows=mWindows.size();
  16. for(size_ti=0;i<numWindows;i++){
  17. constInputWindow*window=&mWindows.itemAt(i);
  18. if(window->hasFocus){
  19. mFocusedWindow=window;
  20. break;
  21. }
  22. }
  23. ......
  24. }//releaselock
  25. ......
  26. }
这里InputDispatcher的成员变量mFocusedWindow就代表当前激活的窗口的。这个函数首先清空mFocusedWindow,然后再通过一个for循环检查当前的输入窗口中的哪一个窗口是获得焦点的,获得焦点的输入窗口即为当前激活的窗口。

这样,InputManager就把当前激活的Activity窗口保存在InputDispatcher中了,后面就可以把键盘消息分发给它来处理。

回到Step 1中的ViewRoot.setView函数中,接下来就调用下面语句来注册键盘消息接收通道的一端到InputManager中去:

  1. mInputChannel=newInputChannel();
  2. try{
  3. res=sWindowSession.add(mWindow,mWindowAttributes,
  4. getHostVisibility(),mAttachInfo.mContentInsets,
  5. mInputChannel);
  6. }catch(RemoteExceptione){
  7. ......
  8. }finally{
  9. ......
  10. }
前面说过,这里的sWindowSession是WindowManagerService内部类Session的一个远程接口,通过它可以进入到WindowManagerService中去。

Step 10.WindowManagerService.Session.add

这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. privatefinalclassSessionextendsIWindowSession.Stub
  5. implementsIBinder.DeathRecipient{
  6. ......
  7. publicintadd(IWindowwindow,WindowManager.LayoutParamsattrs,
  8. intviewVisibility,RectoutContentInsets,InputChanneloutInputChannel){
  9. returnaddWindow(this,window,attrs,viewVisibility,outContentInsets,
  10. outInputChannel);
  11. }
  12. ......
  13. }
  14. ......
  15. }
这里调用WindowManagerService类的addWindow函数来进一步执行操作。

Step 11.WindowManagerService.addWindow

这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. publicintaddWindow(Sessionsession,IWindowclient,
  5. WindowManager.LayoutParamsattrs,intviewVisibility,
  6. RectoutContentInsets,InputChanneloutInputChannel){
  7. ......
  8. WindowStatewin=null;
  9. synchronized(mWindowMap){
  10. ......
  11. win=newWindowState(session,client,token,
  12. attachedWindow,attrs,viewVisibility);
  13. ......
  14. if(outInputChannel!=null){
  15. Stringname=win.makeInputChannelName();
  16. InputChannel[]inputChannels=InputChannel.openInputChannelPair(name);
  17. win.mInputChannel=inputChannels[0];
  18. inputChannels[1].transferToBinderOutParameter(outInputChannel);
  19. mInputManager.registerInputChannel(win.mInputChannel);
  20. }
  21. ......
  22. }
  23. ......
  24. }
  25. ......
  26. }

这里的outInputChannel即为前面在Step 1中创建的InputChannel,它不为NULL,因此,这里会通过InputChannel.openInputChannelPair函数来创建一对输入通道,其中一个位于WindowManagerService中,另外一个通过outInputChannel参数返回到应用程序中:

  1. inputChannels[1].transferToBinderOutParameter(outInputChannel);

创建输入通道之前,WindowManagerService会为当前Activity窗口创建一个WindowState对象win,用来记录这个Activity窗口的状态信息。当创建这对输入管道成功以后,也会把其中的一个管道保存在这个WindowState对象win的成员变量mInputChannel中,后面要注销这个管道的时候,就是从这个WindownState对象中取回这个管道的:

  1. win.mInputChannel=inputChannels[0];

接下来我们就看一下InputChannel.openInputChannelPair函数的实现。

Step 12.InputChannel.openInputChannelPair

这个函数定义在frameworks/base/core/java/android/view/InputChannel.java文件中:

  1. publicfinalclassInputChannelimplementsParcelable{
  2. ......
  3. /**
  4. *Createsanewinputchannelpair.Onechannelshouldbeprovidedtotheinput
  5. *dispatcherandtheothertotheapplication'sinputqueue.
  6. *@paramnameThedescriptive(non-unique)nameofthechannelpair.
  7. *@returnApairofinputchannels.Theyaresymmetricandindistinguishable.
  8. */
  9. publicstaticInputChannel[]openInputChannelPair(Stringname){
  10. ......
  11. returnnativeOpenInputChannelPair(name);
  12. }
  13. ......
  14. }
这个函数调用本地方法nativeOpenInputChannelPair来进一步执行操作。

Step 13.InputChannel.nativeOpenInputChannelPair
这个函数定义在frameworks/base/core/jni/android_view_InputChannel.cpp文件中:

  1. staticjobjectArrayandroid_view_InputChannel_nativeOpenInputChannelPair(JNIEnv*env,
  2. jclassclazz,jstringnameObj){
  3. constchar*nameChars=env->GetStringUTFChars(nameObj,NULL);
  4. String8name(nameChars);
  5. env->ReleaseStringUTFChars(nameObj,nameChars);
  6. sp<InputChannel>serverChannel;
  7. sp<InputChannel>clientChannel;
  8. status_tresult=InputChannel::openInputChannelPair(name,serverChannel,clientChannel);
  9. if(result){
  10. LOGE("Couldnotopeninputchannelpair.status=%d",result);
  11. jniThrowRuntimeException(env,"Couldnotopeninputchannelpair.");
  12. returnNULL;
  13. }
  14. //TODOmorerobusterrorchecking
  15. jobjectserverChannelObj=android_view_InputChannel_createInputChannel(env,
  16. newNativeInputChannel(serverChannel));
  17. jobjectclientChannelObj=android_view_InputChannel_createInputChannel(env,
  18. newNativeInputChannel(clientChannel));
  19. jobjectArraychannelPair=env->NewObjectArray(2,gInputChannelClassInfo.clazz,NULL);
  20. env->SetObjectArrayElement(channelPair,0,serverChannelObj);
  21. env->SetObjectArrayElement(channelPair,1,clientChannelObj);
  22. returnchannelPair;
  23. }
这个函数根据传进来的参数name在C++层分别创建两个InputChannel,一个作为Server端使用,一个作为Client端使用,这里的Server端即是指InputManager,而Client端即是指应用程序。这两个本地的InputChannel是通过InputChannel::openInputChannelPair函数创建的,创建完成后,再相应地在Java层创建相应的两个InputChannel,然后返回。

Step 14.InputChannel.openInputChannelPair
这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中:

  1. status_tInputChannel::openInputChannelPair(constString8&name,
  2. sp<InputChannel>&outServerChannel,sp<InputChannel>&outClientChannel){
  3. status_tresult;
  4. intserverAshmemFd=ashmem_create_region(name.string(),DEFAULT_MESSAGE_BUFFER_SIZE);
  5. if(serverAshmemFd<0){
  6. ......
  7. }else{
  8. result=ashmem_set_prot_region(serverAshmemFd,PROT_READ|PROT_WRITE);
  9. if(result<0){
  10. ......
  11. }else{
  12. //Dupthefiledescriptorbecausetheserverandclientinputchannelobjectsthat
  13. //arereturnedmayhavedifferentlifetimesbuttheysharethesamesharedmemoryregion.
  14. intclientAshmemFd;
  15. clientAshmemFd=dup(serverAshmemFd);
  16. if(clientAshmemFd<0){
  17. ......
  18. }else{
  19. intforward[2];
  20. if(pipe(forward)){
  21. ......
  22. }else{
  23. intreverse[2];
  24. if(pipe(reverse)){
  25. ......
  26. }else{
  27. String8serverChannelName=name;
  28. serverChannelName.append("(server)");
  29. outServerChannel=newInputChannel(serverChannelName,
  30. serverAshmemFd,reverse[0],forward[1]);
  31. String8clientChannelName=name;
  32. clientChannelName.append("(client)");
  33. outClientChannel=newInputChannel(clientChannelName,
  34. clientAshmemFd,forward[0],reverse[1]);
  35. returnOK;
  36. }
  37. ......
  38. }
  39. ......
  40. }
  41. }
  42. }
  43. ......
  44. }
在阅读这个函数之前,我们首先了解一下C++层的InputChannel的构造函数:

  1. InputChannel::InputChannel(constString8&name,int32_tashmemFd,int32_treceivePipeFd,
  2. int32_tsendPipeFd):
  3. mName(name),mAshmemFd(ashmemFd),mReceivePipeFd(receivePipeFd),mSendPipeFd(sendPipeFd){
  4. ......
  5. }
为了创建一个InputChannel,我们需要准备四个参数,一个是输入通道的名称name,一个是匿名共享内存文件描述符,一个是管道的读端文件描述符,一个是管道的写端文件描述符。在上面的openInputChannelPair函数,输入通道的名称已经作为参数传递进来,因此,还需要创建匿名共享内存文件,还有管道。这里需要创建两个管道,一个称为前向管道(forward pipe),一个称为反向管道(reverse pipe),它们交叉使用在Server端和Client端的InputChannel中,这样就使入Server和Client可以互相通信了。

具体来说,Server端和Client端的InputChannel分别是这样构成的:

Server Input Channel: ashmem - reverse(read) - forward(write)

Client Input Channel: ashmem - forward(read) - reverse(write)
前面我们在Android应用程序消息处理机制(Looper、Handler)分析一文中学习Android应用程序的消息处理机制时知道,管道可以用作进程间通信,其中一个进程在管道的读端等待新的内空可读,另一个进程在管道的写端写入新的内容以唤醒在管道读端等待的进程,这样就实现了进程间通信。在我们这个情景中,Client端可以在前向管道(forward pipe)的读端睡眠等待新的内容可读,而Server端可以通过向前向管道(forward pipe)的写端写入新的内容来唤醒Client端,同样,把前向管道(forward pipe)换成反向管道(reverse pipe),也能实现Client端唤醒Server端。在后面我们分析InputDispatcher分发键盘消息时,会看到它们的用法。

有了这些背景知识后,相信上面的openInputChannelPair的代码就容易理解了,这里就不再详述了。

创建好了这两个输入通道后,回到Step 11中的WindowManagerService.addWindow函数中,一方面它把刚才创建的Client端的输入通道通过outInputChannel参数返回到应用程序中:

  1. inputChannels[1].transferToBinderOutParameter(outInputChannel);

另一方面,它还要把刚才创建的Server端的输入通道注册到InputManager中:

  1. mInputManager.registerInputChannel(win.mInputChannel);
Step 15. InputManager.registerInputChannel

这个函数定义在frameworks/base/services/java/com/android/server/InputManager.java文件中:

  1. publicclassInputManager{
  2. ......
  3. /**
  4. *Registersaninputchannelsothatitcanbeusedasaninputeventtarget.
  5. *@paraminputChannelTheinputchanneltoregister.
  6. */
  7. publicvoidregisterInputChannel(InputChannelinputChannel){
  8. if(inputChannel==null){
  9. thrownewIllegalArgumentException("inputChannelmustnotbenull.");
  10. }
  11. nativeRegisterInputChannel(inputChannel,false);
  12. }
  13. ......
  14. }
它通过调用本地方法nativeRegisterInputChannel来执行进一步的操作。

Step 16.InputManager.nativeRegisterInputChannel

这个函数定义在frameworks/base/services/jni/com_android_server_InputManager.cpp 文件中:

  1. staticvoidandroid_server_InputManager_nativeRegisterInputChannel(JNIEnv*env,jclassclazz,
  2. jobjectinputChannelObj,jbooleanmonitor){
  3. ......
  4. sp<InputChannel>inputChannel=android_view_InputChannel_getInputChannel(env,
  5. inputChannelObj);
  6. ......
  7. status_tstatus=gNativeInputManager->registerInputChannel(
  8. env,inputChannel,inputChannelObj,monitor);
  9. ......
  10. }
这里首先通过Java层的InputChannel对象获得C++层的InputChannel对象,它们之间的对应关系是在前面的Step 13中设置好的,接着调用NativeInputManager的registerInputChannel执行进一步的操作。

Step 17. NativeInputManager.registerInputChannel

这个函数定义在frameworks/base/services/jni/com_android_server_InputManager.cpp 文件中:

  1. status_tNativeInputManager::registerInputChannel(JNIEnv*env,
  2. constsp<InputChannel>&inputChannel,jobjectinputChannelObj,boolmonitor){
  3. ......
  4. status=mInputManager->getDispatcher()->registerInputChannel(inputChannel,monitor);
  5. ......
  6. }
这个函数主要是调用了InputDispatcher的registerInputChannel来真正执行注册输入通道的操作。

Step 18.InputDispatcher.registerInputChannel
这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中:

  1. status_tInputDispatcher::registerInputChannel(constsp<InputChannel>&inputChannel,boolmonitor){
  2. ......
  3. {//acquirelock
  4. AutoMutex_l(mLock);
  5. if(getConnectionIndexLocked(inputChannel)>=0){
  6. LOGW("Attemptedtoregisteralreadyregisteredinputchannel'%s'",
  7. inputChannel->getName().string());
  8. returnBAD_VALUE;
  9. }
  10. sp<Connection>connection=newConnection(inputChannel);
  11. status_tstatus=connection->initialize();
  12. if(status){
  13. LOGE("Failedtoinitializeinputpublisherforinputchannel'%s',status=%d",
  14. inputChannel->getName().string(),status);
  15. returnstatus;
  16. }
  17. int32_treceiveFd=inputChannel->getReceivePipeFd();
  18. mConnectionsByReceiveFd.add(receiveFd,connection);
  19. if(monitor){
  20. mMonitoringChannels.push(inputChannel);
  21. }
  22. mLooper->addFd(receiveFd,0,ALOOPER_EVENT_INPUT,handleReceiveCallback,this);
  23. runCommandsLockedInterruptible();
  24. }//releaselock
  25. returnOK;
  26. }
这个函数首先会通过getConnectionIndexLocked检查从参数传进来的InputChannel是否已经注册过了,如果已经注册过了,就返回一个BAD_VALUE值了,否则的话,就会创建一个Connection对象来封装即将要注册的inputChannel,我们可以不关心这个Connection对象的实现,接着还通过调用inputChannel->getReceivePipeFd获得一个管
道的读端描述符。回忆一下Step 14中的InputChannel.openInputChannelPair函数,我们创建了一个Server端的InputChannel,就是对应这里的inputChannel了,这个inputChannel的Receive Pipe Fd就是我们前面说的反向管道的读端描述符了。有了这个Receive Pipe Fd后,就以它作为Key值来把前面创建的Connection对象保存在InputDispatcher中,这样就基本完成键盘消息接收通道的注册了。但是,注册的工作还未完成,最后,还要把这个Receive Pipe Fd添加到InputDispatcher的成员变量mLooper中去,这里的成员变量mLooper的类型为Looper,我们在前面介绍InputManager的启动过程的Step 15中已经见过了,这里就不再详述了,不过这里仍然值得介绍一下它的addFd函数。

在前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析中,我们在介绍到Android应用程序的消息循环一节时,曾经说过,在Looper类内部,会创建一个管道,然后Looper会睡眠在这个管道的读端,等待另外一个线程来往这个管道的写端写入新的内容,从而唤醒等待在这个管道读端的线程,除此之外,Looper还可以同时睡眠等待在其它的文件描述符上,因为它是通过Linux系统的epoll机制来批量等待指定的文件有新的内容可读的。这些其它的文件描述符就是通过Looper类的addFd成函数添加进去的了,在添加的时候,还可以指定回调函数,即当这个文件描述符所指向的文件有新的内容可读时,Looper就会调用这个hanldeReceiveCallback函数,有兴趣的读者可以自己研究一下Looper类的addFd函数的实现,它位于frameworks/base/libs/utils/Looper.cpp文件中。

分析到这里,Server端的InputChannel就注册完成了。回忆一下前面介绍InputManager启动过程的Step 14,这时InputDispatcherThread同时睡眠在InputDispatcher的成员变量mLooper内部的管道的读端以及这里的Server端InputChannel里面的反向管道的读端上,mLooper内部的管道的读端等待键盘事件的发生而被唤醒,而Server端InputChannel里面的反向管道的读端等待Client端InputChannel里面的反向管道的写端被写入新的内容而被唤醒。

Server端的InputChannel注册完成后,回到Step 11中的WindowManagerService.addWindow函数,接下来就是把Client端的InputChannel转换成addWindow的参数outInputChannel中,然后返回到Step 1中的ViewRoot.setView函数中,继续执行Client端的InputChannel的注册过程,即为应用程序这一侧注册键盘消息接收通道:

  1. if(viewinstanceofRootViewSurfaceTaker){
  2. mInputQueueCallback=
  3. ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
  4. }
  5. if(mInputQueueCallback!=null){
  6. mInputQueue=newInputQueue(mInputChannel);
  7. mInputQueueCallback.onInputQueueCreated(mInputQueue);
  8. }else{
  9. InputQueue.registerInputChannel(mInputChannel,mInputHandler,
  10. Looper.myQueue());
  11. }

这里的变量view一般不为RootViewSurfaceTaker的实例,因此,最后会执行下面语句:

  1. InputQueue.registerInputChannel(mInputChannel,mInputHandler,
  2. Looper.myQueue());
它调用InputQueue的registerInputChannel函数为应用程序注册键盘消息接收通道,这里的mInputChannel即为我们在前面Step 14中创建的Client端的InputChannel;Looper.myQueue函数返回的便是应用程序主线程的消息队列,具体可以参考前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析;参数mInputHandler是一个回调对象,当有键盘事件发生时,这个mInputHandler的handleKey函数就会被调用,在后面的分析中,我们将会看到。

Step 19. InputQueue.registerInputChannel

这个函数定义在frameworks/base/core/java/android/view/InputQueue.java文件中:

  1. publicfinalclassInputQueue{
  2. ......
  3. publicstaticvoidregisterInputChannel(InputChannelinputChannel,InputHandlerinputHandler,
  4. MessageQueuemessageQueue){
  5. ......
  6. synchronized(sLock){
  7. ......
  8. nativeRegisterInputChannel(inputChannel,inputHandler,messageQueue);
  9. }
  10. }
  11. ......
  12. }
这个函数调用本地方法nativeRegisterInputChannel函数来执行进一步的操作。

Step 20.InputQueue.nativeRegisterInputChannel

这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:

  1. staticvoidandroid_view_InputQueue_nativeRegisterInputChannel(JNIEnv*env,jclassclazz,
  2. jobjectinputChannelObj,jobjectinputHandlerObj,jobjectmessageQueueObj){
  3. status_tstatus=gNativeInputQueue.registerInputChannel(
  4. env,inputChannelObj,inputHandlerObj,messageQueueObj);
  5. ......
  6. }
这里继续调用NativeInputQueue的registerInputChannel函数来执行真正的键盘消息接收通道的工作。

Step 21. NativeInputQueue.registerInputChannel

这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:

  1. status_tNativeInputQueue::registerInputChannel(JNIEnv*env,jobjectinputChannelObj,
  2. jobjectinputHandlerObj,jobjectmessageQueueObj){
  3. sp<InputChannel>inputChannel=android_view_InputChannel_getInputChannel(env,
  4. inputChannelObj);
  5. ......
  6. sp<Looper>looper=android_os_MessageQueue_getLooper(env,messageQueueObj);
  7. {//acquirelock
  8. AutoMutex_l(mLock);
  9. if(getConnectionIndex(inputChannel)>=0){
  10. LOGW("Attemptedtoregisteralreadyregisteredinputchannel'%s'",
  11. inputChannel->getName().string());
  12. returnBAD_VALUE;
  13. }
  14. uint16_tconnectionId=mNextConnectionId++;
  15. sp<Connection>connection=newConnection(connectionId,inputChannel,looper);
  16. status_tresult=connection->inputConsumer.initialize();
  17. if(result){
  18. LOGW("Failedtoinitializeinputconsumerforinputchannel'%s',status=%d",
  19. inputChannel->getName().string(),result);
  20. returnresult;
  21. }
  22. connection->inputHandlerObjGlobal=env->NewGlobalRef(inputHandlerObj);
  23. int32_treceiveFd=inputChannel->getReceivePipeFd();
  24. mConnectionsByReceiveFd.add(receiveFd,connection);
  25. looper->addFd(receiveFd,0,ALOOPER_EVENT_INPUT,handleReceiveCallback,this);
  26. }//releaselock
  27. ......
  28. returnOK;
  29. }
这里注册应用程序的InputChannel的逻辑和前面介绍的Step 18中在InputDispatcher中注册Server端的InputChannel是一样的,所不同的是,这里用的looper是应用程序主线程中的消息循环对象Looper,而添加到这个looper对象中的Receive Pipe Fd是前面在Step 14中创建的前向管道的读端文件描述符,而使用的回调函数是NativeInputQueue的成员函数handleReceiveCallback。

介绍到这里,应用程序注册键盘消息接收通道的过程就分析完成了。这个过程比较复杂,这里小结一下:

A. 即将会被激活的Activity窗口,会通知InputManager,它是当前激活的窗口,因此,一旦发生键盘事件的时候,InputManager就把这个键盘事件抛给这个Activity处理;

B. 应用程序会为这个Activity窗口和InputManager之间创建一个键盘消息接收通道,这个通道的一端由一个Server端的InputChannel构成,另一端由Client端的InputChannel构成,Server端的InputChannel注册在由InputManager所管理的InputDispatcher中,而Client端的InputChannel注册在由应用程序主线程的消息循环对象Looper中;

C. 注册在InputDispatcher中的InputChannel由一个反向管道的读端和一个前向管道的写端组成,而注册在应用程序主线程的消息循环对象Looper中的InputChannel由这个前向管道的读端和反向管道的写端组成,这种交叉结构使得当有键盘事件发生时,InputDispatcher可以把这个事件通知给应用程序。

应用程序注册好键盘消息接收通道后,接下来就开始分析InputManager分发键盘消息给应用程序的过程了。

----

转自:老罗的Android应用程序键盘(Keyboard)消息处理机制分析
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics