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函数开始分析应用程序注册键盘消息接收通道的过程了。首先看一下这个注册过程的序列图,然后再详细分析每一个步骤:

Android应用程序键盘(Keyboard)消息处理机制分析(二)_第1张图片

点击查看大图

        Step 1. ViewRoot.setView

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

view plain
  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  
  3.     ......  
  4.   
  5.     public void setView(View view, WindowManager.LayoutParams attrs,  
  6.             View panelParentView) {  
  7.         ......  
  8.   
  9.         synchronized (this) {  
  10.             if (mView == null) {  
  11.                 ......  
  12.   
  13.                 // Schedule the first layout -before- adding to the window  
  14.                 // manager, to make sure we do the relayout before receiving  
  15.                 // any other events from the system.  
  16.                 requestLayout();  
  17.                 mInputChannel = new InputChannel();  
  18.                 try {  
  19.                     res = sWindowSession.add(mWindow, mWindowAttributes,  
  20.                         getHostVisibility(), mAttachInfo.mContentInsets,  
  21.                         mInputChannel);  
  22.                 } catch (RemoteException e) {  
  23.                     ......  
  24.                 } finally {  
  25.                     ......  
  26.                 }  
  27.   
  28.                 ......  
  29.   
  30.                 if (view instanceof RootViewSurfaceTaker) {  
  31.                     mInputQueueCallback =  
  32.                         ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();  
  33.                 }  
  34.                 if (mInputQueueCallback != null) {  
  35.                     mInputQueue = new InputQueue(mInputChannel);  
  36.                     mInputQueueCallback.onInputQueueCreated(mInputQueue);  
  37.                 } else {  
  38.                     InputQueue.registerInputChannel(mInputChannel, mInputHandler,  
  39.                         Looper.myQueue());  
  40.                 }  
  41.   
  42.                 ......  
  43.             }  
  44.         }  
  45.     }  
  46.   
  47. }  

        这个函数中与注册键盘消息接收通道(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文件中:

view plain
  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  
  3.     ......  
  4.   
  5.     public void requestLayout() {  
  6.         ......  
  7.         mLayoutRequested = true;  
  8.         scheduleTraversals();  
  9.     }  
  10.   
  11.     ......  
  12. }  
        这个函数调用了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 文件中:

view plain
  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     private final class Session extends IWindowSession.Stub  
  6.             implements IBinder.DeathRecipient {  
  7.         ......  
  8.   
  9.         public int relayout(IWindow window, WindowManager.LayoutParams attrs,  
  10.                 int requestedWidth, int requestedHeight, int viewFlags,  
  11.                 boolean insetsPending, Rect outFrame, Rect outContentInsets,  
  12.                 Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {  
  13.             //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid());  
  14.             int res = relayoutWindow(this, window, attrs,  
  15.                     requestedWidth, requestedHeight, viewFlags, insetsPending,  
  16.                     outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface);  
  17.             //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid());  
  18.             return res;  
  19.         }  
  20.   
  21.         ......  
  22.     }  
  23.   
  24.     ......  
  25. }  

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

        Step 4. WindowManagerService.relayoutWIndow

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

view plain
  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     public int relayoutWindow(Session session, IWindow client,  
  6.             WindowManager.LayoutParams attrs, int requestedWidth,  
  7.             int requestedHeight, int viewVisibility, boolean insetsPending,  
  8.             Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,  
  9.             Configuration outConfig, Surface outSurface) {  
  10.         ......  
  11.   
  12.         synchronized(mWindowMap) {  
  13.             ......  
  14.   
  15.             mInputMonitor.updateInputWindowsLw();  
  16.         }  
  17.   
  18.         ......  
  19.     }  
  20.   
  21.     ......  
  22. }  
         这个函数又会继续调用mInputMonitor的updateInputWindowsLw成员函数来更新当前的输入窗口,mInputMonitor是WindowManagerService的成员变量,它的类型为InputMonitor。

         Step 5. InputMonitor.updateInputWindowsLw

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

view plain
  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     final class InputMonitor {  
  6.         ......  
  7.   
  8.         /* Updates the cached window information provided to the input dispatcher. */  
  9.         public void updateInputWindowsLw() {  
  10.             // Populate the input window list with information about all of the windows that  
  11.             // could potentially receive input.  
  12.             // As an optimization, we could try to prune the list of windows but this turns  
  13.             // out to be difficult because only the native code knows for sure which window  
  14.             // currently has touch focus.  
  15.             final ArrayList<WindowState> windows = mWindows;  
  16.             final int N = windows.size();  
  17.             for (int i = N - 1; i >= 0; i--) {  
  18.                 final WindowState child = windows.get(i);  
  19.                 if (child.mInputChannel == null || child.mRemoved) {  
  20.                     // Skip this window because it cannot possibly receive input.  
  21.                     continue;  
  22.                 }  
  23.   
  24.                 ......  
  25.   
  26.                 // Add a window to our list of input windows.  
  27.                 final InputWindow inputWindow = mTempInputWindows.add();  
  28.   
  29.                 ......  
  30.             }  
  31.   
  32.             // Send windows to native code.  
  33.             mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray());  
  34.   
  35.             ......  
  36.         }  
  37.   
  38.         ......  
  39.     }  
  40.   
  41.     ......  
  42. }  
        这个函数将当前系统中带有InputChannel的Activity窗口都设置为InputManager的输入窗口,但是后面我们会看到,只有当前激活的窗口才会响应键盘消息。

        Step 6. InputManager.setInputWindows

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

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

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

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

        Step 8. NativeInputManager.setInputWindows

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

view plain
  1. void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) {  
  2.     Vector<InputWindow> windows;  
  3.   
  4.     jsize length = env->GetArrayLength(windowObjArray);  
  5.     for (jsize i = 0; i < length; i++) {  
  6.         jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i);  
  7.         if (! inputTargetObj) {  
  8.             break// found null element indicating end of used portion of the array  
  9.         }  
  10.   
  11.         windows.push();  
  12.         InputWindow& window = windows.editTop();  
  13.         bool valid = populateWindow(env, inputTargetObj, window);  
  14.         if (! valid) {  
  15.             windows.pop();  
  16.         }  
  17.   
  18.         env->DeleteLocalRef(inputTargetObj);  
  19.     }  
  20.   
  21.     mInputManager->getDispatcher()->setInputWindows(windows);  
  22. }  
        这个函数首先将Java层的Window转换成C++层的InputWindow,然后放在windows向量中,最后将这些输入窗口设置到InputDispatcher中去。

        Step 9. InputDispatcher.setInputWindows

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

view plain
  1. void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {  
  2.     ......  
  3.   
  4.     { // acquire lock  
  5.         AutoMutex _l(mLock);  
  6.   
  7.         // Clear old window pointers.  
  8.         sp<InputChannel> oldFocusedWindowChannel;  
  9.         if (mFocusedWindow) {  
  10.             oldFocusedWindowChannel = mFocusedWindow->inputChannel;  
  11.             mFocusedWindow = NULL;  
  12.         }  
  13.   
  14.         mWindows.clear();  
  15.   
  16.         // Loop over new windows and rebuild the necessary window pointers for  
  17.         // tracking focus and touch.  
  18.         mWindows.appendVector(inputWindows);  
  19.   
  20.         size_t numWindows = mWindows.size();  
  21.         for (size_t i = 0; i < numWindows; i++) {  
  22.             const InputWindow* window = & mWindows.itemAt(i);  
  23.             if (window->hasFocus) {  
  24.                 mFocusedWindow = window;  
  25.                 break;  
  26.             }  
  27.         }  
  28.   
  29.         ......  
  30.   
  31.     } // release lock  
  32.   
  33.     ......  
  34. }  
        这里InputDispatcher的成员变量mFocusedWindow就代表当前激活的窗口的。这个函数首先清空mFocusedWindow,然后再通过一个for循环检查当前的输入窗口中的哪一个窗口是获得焦点的,获得焦点的输入窗口即为当前激活的窗口。

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

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

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

        Step 10. WindowManagerService.Session.add

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

view plain
  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     private final class Session extends IWindowSession.Stub  
  6.             implements IBinder.DeathRecipient {  
  7.         ......  
  8.   
  9.         public int add(IWindow window, WindowManager.LayoutParams attrs,  
  10.                 int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {  
  11.             return addWindow(this, window, attrs, viewVisibility, outContentInsets,  
  12.                 outInputChannel);  
  13.         }  
  14.   
  15.         ......  
  16.     }  
  17.   
  18.     ......  
  19. }  
        这里调用WindowManagerService类的addWindow函数来进一步执行操作。

        Step 11. WindowManagerService.addWindow

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

view plain
  1. public class WindowManagerService extends IWindowManager.Stub  
  2.         implements Watchdog.Monitor {  
  3.     ......  
  4.   
  5.     public int addWindow(Session session, IWindow client,  
  6.             WindowManager.LayoutParams attrs, int viewVisibility,  
  7.             Rect outContentInsets, InputChannel outInputChannel) {  
  8.         ......  
  9.   
  10.         WindowState win = null;  
  11.   
  12.         synchronized(mWindowMap) {  
  13.             ......  
  14.   
  15.             win = new WindowState(session, client, token,  
  16.                 attachedWindow, attrs, viewVisibility);  
  17.   
  18.             ......  
  19.   
  20.             if (outInputChannel != null) {  
  21.                 String name = win.makeInputChannelName();  
  22.                 InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);  
  23.                 win.mInputChannel = inputChannels[0];  
  24.                 inputChannels[1].transferToBinderOutParameter(outInputChannel);  
  25.                 mInputManager.registerInputChannel(win.mInputChannel);  
  26.             }  
  27.   
  28.             ......  
  29.         }  
  30.   
  31.         ......  
  32.     }  
  33.   
  34.     ......  
  35. }  

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

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

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

view plain
  1. win.mInputChannel = inputChannels[0];  

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

        Step 12. InputChannel.openInputChannelPair

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

view plain
  1. public final class InputChannel implements Parcelable {  
  2.     ......  
  3.   
  4.     /** 
  5.     * Creates a new input channel pair.  One channel should be provided to the input 
  6.     * dispatcher and the other to the application's input queue. 
  7.     * @param name The descriptive (non-unique) name of the channel pair. 
  8.     * @return A pair of input channels.  They are symmetric and indistinguishable. 
  9.     */  
  10.     public static InputChannel[] openInputChannelPair(String name) {  
  11.         ......  
  12.   
  13.         return nativeOpenInputChannelPair(name);  
  14.     }  
  15.   
  16.     ......  
  17. }  
         这个函数调用本地方法nativeOpenInputChannelPair来进一步执行操作。

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

view plain
  1. static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,  
  2.         jclass clazz, jstring nameObj) {  
  3.      const char* nameChars = env->GetStringUTFChars(nameObj, NULL);  
  4.      String8 name(nameChars);  
  5.      env->ReleaseStringUTFChars(nameObj, nameChars);  
  6.   
  7.      sp<InputChannel> serverChannel;  
  8.      sp<InputChannel> clientChannel;  
  9.      status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);  
  10.   
  11.      if (result) {  
  12.          LOGE("Could not open input channel pair.  status=%d", result);  
  13.          jniThrowRuntimeException(env, "Could not open input channel pair.");  
  14.          return NULL;  
  15.      }  
  16.   
  17.      // TODO more robust error checking  
  18.      jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,  
  19.          new NativeInputChannel(serverChannel));  
  20.      jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,  
  21.          new NativeInputChannel(clientChannel));  
  22.   
  23.      jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);  
  24.      env->SetObjectArrayElement(channelPair, 0, serverChannelObj);  
  25.      env->SetObjectArrayElement(channelPair, 1, clientChannelObj);  
  26.      return channelPair;  
  27. }  
        这个函数根据传进来的参数name在C++层分别创建两个InputChannel,一个作为Server端使用,一个作为Client端使用,这里的Server端即是指InputManager,而Client端即是指应用程序。这两个本地的InputChannel是通过InputChannel::openInputChannelPair函数创建的,创建完成后,再相应地在Java层创建相应的两个InputChannel,然后返回。

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

view plain
  1. status_t InputChannel::openInputChannelPair(const String8& name,  
  2.         sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {  
  3.     status_t result;  
  4.   
  5.     int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE);  
  6.     if (serverAshmemFd < 0) {  
  7.         ......  
  8.     } else {  
  9.         result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE);  
  10.         if (result < 0) {  
  11.             ......  
  12.         } else {  
  13.             // Dup the file descriptor because the server and client input channel objects that  
  14.             // are returned may have different lifetimes but they share the same shared memory region.  
  15.             int clientAshmemFd;  
  16.             clientAshmemFd = dup(serverAshmemFd);  
  17.             if (clientAshmemFd < 0) {  
  18.                 ......  
  19.             } else {  
  20.                 int forward[2];  
  21.                 if (pipe(forward)) {  
  22.                     ......  
  23.                 } else {  
  24.                     int reverse[2];  
  25.                     if (pipe(reverse)) {  
  26.                         ......  
  27.                     } else {  
  28.                         String8 serverChannelName = name;  
  29.                         serverChannelName.append(" (server)");  
  30.                         outServerChannel = new InputChannel(serverChannelName,  
  31.                             serverAshmemFd, reverse[0], forward[1]);  
  32.   
  33.                         String8 clientChannelName = name;  
  34.                         clientChannelName.append(" (client)");  
  35.                         outClientChannel = new InputChannel(clientChannelName,  
  36.                             clientAshmemFd, forward[0], reverse[1]);  
  37.                         return OK;  
  38.                     }  
  39.                     ......  
  40.                 }  
  41.                 ......  
  42.             }  
  43.         }  
  44.     }  
  45.     ......  
  46. }  
        在阅读这个函数之前,我们首先了解一下C++层的InputChannel的构造函数:

view plain
  1. InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd,  
  2.     int32_t sendPipeFd) :  
  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参数返回到应用程序中:

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

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

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

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

view plain
  1. public class InputManager {  
  2.     ......  
  3.   
  4.     /** 
  5.     * Registers an input channel so that it can be used as an input event target. 
  6.     * @param inputChannel The input channel to register. 
  7.     */  
  8.     public void registerInputChannel(InputChannel inputChannel) {  
  9.         if (inputChannel == null) {  
  10.             throw new IllegalArgumentException("inputChannel must not be null.");  
  11.         }  
  12.   
  13.         nativeRegisterInputChannel(inputChannel, false);  
  14.     }  
  15.   
  16.     ......  
  17. }  
         它通过调用本地方法nativeRegisterInputChannel来执行进一步的操作。

         Step 16. InputManager.nativeRegisterInputChannel

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

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

        Step 17. NativeInputManager.registerInputChannel

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

view plain
  1. status_t NativeInputManager::registerInputChannel(JNIEnv* env,  
  2.         const sp<InputChannel>& inputChannel, jobject inputChannelObj, bool monitor) {  
  3.     ......  
  4.   
  5.     status = mInputManager->getDispatcher()->registerInputChannel(inputChannel, monitor);  
  6.       
  7.     ......  
  8. }  
        这个函数主要是调用了InputDispatcher的registerInputChannel来真正执行注册输入通道的操作。

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

view plain
  1. status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {  
  2.     ......  
  3.   
  4.     { // acquire lock  
  5.         AutoMutex _l(mLock);  
  6.   
  7.         if (getConnectionIndexLocked(inputChannel) >= 0) {  
  8.             LOGW("Attempted to register already registered input channel '%s'",  
  9.                 inputChannel->getName().string());  
  10.             return BAD_VALUE;  
  11.         }  
  12.   
  13.         sp<Connection> connection = new Connection(inputChannel);  
  14.         status_t status = connection->initialize();  
  15.         if (status) {  
  16.             LOGE("Failed to initialize input publisher for input channel '%s', status=%d",  
  17.                 inputChannel->getName().string(), status);  
  18.             return status;  
  19.         }  
  20.   
  21.         int32_t receiveFd = inputChannel->getReceivePipeFd();  
  22.         mConnectionsByReceiveFd.add(receiveFd, connection);  
  23.   
  24.         if (monitor) {  
  25.             mMonitoringChannels.push(inputChannel);  
  26.         }  
  27.   
  28.         mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);  
  29.   
  30.         runCommandsLockedInterruptible();  
  31.     } // release lock  
  32.     return OK;  
  33. }  
        这个函数首先会通过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 plain
  1. if (view instanceof RootViewSurfaceTaker) {  
  2.     mInputQueueCallback =  
  3.         ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();  
  4. }  
  5. if (mInputQueueCallback != null) {  
  6.     mInputQueue = new InputQueue(mInputChannel);  
  7.     mInputQueueCallback.onInputQueueCreated(mInputQueue);  
  8. else {  
  9.     InputQueue.registerInputChannel(mInputChannel, mInputHandler,  
  10.         Looper.myQueue());  
  11. }  

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

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

view plain
  1. public final class InputQueue {  
  2.     ......  
  3.   
  4.     public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,  
  5.             MessageQueue messageQueue) {  
  6.         ......  
  7.   
  8.         synchronized (sLock) {  
  9.             ......  
  10.   
  11.             nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);  
  12.         }  
  13.     }  
  14.   
  15.     ......  
  16. }  
         这个函数调用本地方法nativeRegisterInputChannel函数来执行进一步的操作。

         Step 20. InputQueue.nativeRegisterInputChannel

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

view plain
  1. static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,  
  2.         jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) {  
  3.     status_t status = gNativeInputQueue.registerInputChannel(  
  4.        env, inputChannelObj, inputHandlerObj, messageQueueObj);  
  5.   
  6.     ......  
  7. }  
        这里继续调用NativeInputQueue的registerInputChannel函数来执行真正的键盘消息接收通道的工作。

        Step 21. NativeInputQueue.registerInputChannel

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

view plain
  1. status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,  
  2.         jobject inputHandlerObj, jobject messageQueueObj) {  
  3.     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,  
  4.         inputChannelObj);  
  5.     ......  
  6.   
  7.     sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);  
  8.   
  9.     { // acquire lock  
  10.         AutoMutex _l(mLock);  
  11.   
  12.         if (getConnectionIndex(inputChannel) >= 0) {  
  13.             LOGW("Attempted to register already registered input channel '%s'",  
  14.                 inputChannel->getName().string());  
  15.             return BAD_VALUE;  
  16.         }  
  17.   
  18.         uint16_t connectionId = mNextConnectionId++;  
  19.         sp<Connection> connection = new Connection(connectionId, inputChannel, looper);  
  20.         status_t result = connection->inputConsumer.initialize();  
  21.         if (result) {  
  22.             LOGW("Failed to initialize input consumer for input channel '%s', status=%d",  
  23.                 inputChannel->getName().string(), result);  
  24.             return result;  
  25.         }  
  26.   
  27.         connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);  
  28.   
  29.         int32_t receiveFd = inputChannel->getReceivePipeFd();  
  30.         mConnectionsByReceiveFd.add(receiveFd, connection);  
  31.   
  32.         looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);  
  33.     } // release lock  
  34.   
  35.     ......  
  36.     return OK;  
  37. }  
        这里注册应用程序的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)消息处理机制分析(三)
转自:老罗的 Android应用程序键盘(Keyboard)消息处理机制分析

你可能感兴趣的:(windows,android,server,session,null,keyboard)