Step 18. InputDispatcher.registerInputChannel
这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
这个函数首先会通过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的注册过程,即为应用程序这一侧注册键盘消息接收通道:
这里的变量view一般不为RootViewSurfaceTaker的实例,因此,最后会执行下面语句:
它调用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文件中:
这个函数调用本地方法nativeRegisterInputChannel函数来执行进一步的操作。
Step 20. InputQueue.nativeRegisterInputChannel
这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
这里继续调用NativeInputQueue的registerInputChannel函数来执行真正的键盘消息接收通道的工作。
Step 21. NativeInputQueue.registerInputChannel
这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
这里注册应用程序的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分发键盘消息给应用程序的过程了。