【Android源码 栏目提醒】:网学会员为需要Android源码 的朋友们搜集整理了Android应用程序键盘(Keyboard)消息处理机制分析 - 企业软件开发相关资料,希望对各位网友有所帮助!
Android应用程序键盘Keyboard消息处理机制分析 在
Android系统中键盘按键事件是由WindowManagerService服务来管理的然后再以消息的形式来分发给应用程序处理不过和普通消息不一样它是由硬件中断触发的在上一篇文章《
Android应用程序消息处理机制Looper、Handler分析》中我们分析了
Android应用程序的消息处理机制本文将结合这种消息处理机制来详细分析
Android应用程序是如何获得键盘按键消息的。
在系统启动的时候SystemServer会启动窗口管理服务WindowManagerServiceWindowManagerService在启动的时候就会通过系统输入管理器InputManager来总负责监控键盘消息。
这些键盘消息一般都是分发给当前激活的Activity窗口来处理的因此当前激活的Activity窗口在创建的时候会到WindowManagerService中去注册一个接收键盘消息的通道表明它要处理键盘消息而当InputManager监控到有键盘消息时就会分给给它处理。
当当前激活的Activity窗口不再处于激活状态时它也会到WindowManagerService中去反注册之前的键盘消息接收通道这样InputManager就不会再把键盘消息分发给它来处理。
由于本文的内容比较多在接下面的章节中我们将分为五个部分来详细描述
Android应用程序获得键盘按键消息的过程每一个部分都是具体描述键盘消息处理过程中的一个过程。
结合上面的键盘消息处理框架这四个过程分别是InputManager的启动过程、应用程序注册键盘消息接收通道的过程、InputManager分发键盘消息给应用程序的过程以及应用程序注销键盘消息接收通道的过程。
为了更好地理解
Android应用程序获得键盘按键消息的整个过程建议读者首先阅读
Android应用程序消息处理机制Looper、Handler分析一文理解了
Android应用程序的消息处理机制后就能很好的把握本文的内容。
1. InputManager的启动过程分析 前面说过
Android系统的键盘事件是由InputManager来监控的而InputManager是由窗口管理服务WindowManagerService来启动的。
从前面一篇文章
Android系统进程Zygote启动过程的源代码分析中我们知道在
Android系统中Zygote进程负责启动系统服务进程SystemServer而系统服务进程SystemServer负责启动系统中的各种关键服务例如我们在前面两篇文章
Android应用程序安装过程源代码分析和
Android系统默认Home应用程序Launcher的启动过程源代码分析中提到的Package管理服务PackageManagerService和Activity管理服务ActivityManagerService。
这里我们所讨论的窗口管理服务WindowManagerService也是由SystemServer来启动的具体的启动过程这里就不再详述了具体可以参考PackageManagerService和ActivityManagerService的启动过程。
了解了WindowManagerService的启动过程之后我们就可以继续分析InputManager的启动过程了。
我们先来看一下InputManager启动过程的序列图然后根据这个序列图来一步步分析它的启动过程 点击查看大图 Step 1. WindowManagerService.main 这个函数定义在frameworks/base/services/java/com/
android/server/WindowManagerService.java文件中 java view plaincopy 1. public class WindowManagerService extends IWindowManager.Stub 2. implements Watchdog.Monitor 3. ...... 4. 5. public static WindowManagerService mainContext context 6. PowerManagerService pm boolean haveInputMethods 7. WMThread thr new WMThreadcontext pm haveInputMethods 8. thr.start 9. 10. synchronized thr 11. while thr.mService null 12. try 13. thr.wait 14. catch InterruptedException e 15. 16. 17. return thr.mService 18. 19. 20. 21. ...... 22. 它通过一个线程WMThread实例来执行全局唯一的WindowManagerService实例的启动操作。
这里调用WMThread实例thr的start成员函数时会进入到WMThread实例thr的run函数中去。
Step 2. WMThread.run 这个函数定义在frameworks/base/services/java/com/
android/server/WindowManagerService.java文件中 java view plaincopy 1. public class WindowManagerService extends IWindowManager.Stub 2. implements Watchdog.Monitor 3. ...... 4. 5. static class WMThread extends Thread 6. ...... 7. 8. public void run 9. ...... 10. 11. WindowManagerService s new WindowManagerServicemContext mPM 12. mHaveInputMethods 13. ...... 14. 15. 16. 17. 18. ...... 19. 这里执行的主要操作就是创建一个WindowManagerService实例这样会调用到WindowManagerService构造函数中去。
Step 3. WindowManagerServiceltinitgt WindowManagerService类的构造函数定义在frameworks/base/services/java/com/
android/server/WindowManagerService.java文件中 java view plaincopy 1. public class WindowManagerService extends IWindowManager.Stub 2. implements Watchdog.Monitor 3. ...... 4. 5. final InputManager mInputManager 6. 7. ...... 8. 9. private WindowManagerServiceContext context PowerManagerService pm 10. boolean haveInputMethods 11. ...... 12. 13. mInputManager new InputManagercontext this 14. 15. ...... 16. 17. mInputManager.start 18. 19. ...... 20. 21. 22. 23. ...... 24. 这里我们只关心InputManager的创建过程而忽略其它无关部分。
首先是创建一个InputManager实例然后再调用它的start成员函数来监控键盘事件。
在创建InputManager实例的过程中会执行一些初始化工作因此我们先进入到InputManager类的构造函数去看看然后再回过头来分析它的start成员函数。
Step 4. InputManagerltinitgtjava Java层的InputManager类的构造函数定义在frameworks/base/services/java/com/
android/server/InputManager.java文件中 java view plaincopy 1. public class InputManager 2. ...... 3. 4. public InputManagerContext context WindowManagerService windowManagerService 5. this.mContext context 6. this.mWindowManagerService windowManagerService 7. 8. this.mCallbacks new Callbacks 9. 10. init 11. 12. 13. ...... 14. 这里只是简单地初始化InputManager类的一些成员变量然后调用init函数进一步执行初始化操作。
Step 5. InputManager.init 这个函数定义在frameworks/base/services/java/com/
android/server/InputManager.java文件中 java view plaincopy 1. public class InputManager 2. ...... 3. 4. private void init 5. Slog.iTAG quotInitializing input managerquot 6. nativeInitmCallbacks 7. 8. 9. ...... 10. 函数init通过调用本地方法nativeInit来执行C层的相关初始化操作。
Step 6. InputManager.nativeInit 这个函数定义在frameworks/base/services/jni vi com_
android_server_InputManager.cpp文件中 cpp view plaincopy 1. static void
android_server_InputManager_nativeInitJNIEnv env jclass clazz 2. jobject callbacks 3. if gNativeInputManager NULL 4. gNativeInputManager new NativeInputManagercallbacks 5. else 6. LOGEquotInput manager already initialized.quot 7. jniThrowRuntimeExceptionenv quotInput manager already initialized.quot 8. 9. 这个函数的作用是创建一个NativeInputManager实例并保存在gNativeInputManager变量中。
由于是第一次调用到这里因此gNativeInputManager为NULL于是就会new一个NativeInputManager对象出来这样就会执行NativeInputManager类的构造函数来执其它的初始化操作。
Step 7. NativeInputManagerltinitgt NativeInputManager类的构造函数定义在frameworks/base/services/jni vi com_
android_server_InputManager.cpp文件中 cpp view plaincopy 1. NativeInputManager::NativeInputManagerjobject callbacksObj : 2. mFilterTouchEvents-1 mFilterJumpyTouchEvents-1 mVirtualKeyQuietTime-1 3. mMaxEventsPerSecond-1 4. mDisplayWidth-1 mDisplayHeight-1 mDisplayOrientationROTATION_0 5. JNIEnv env jniEnv 6. 7. mCallbacksObj env-gtNewGlobalRefcallbacksObj 8. 9. spltEventHubgt eventHub new EventHub 10. mInputManager new InputManagereventHub this this 11. 这里只要是创建了一个EventHub实例并且把这个EventHub作为参数来创建InputManager对象。
注意这里的InputManager类是定义在C层的和前面在Java层的InputManager不一样不过它们是对应关系。
EventHub类是真正执行监控键盘事件操作的地方后面我们会进一步分析到现在我们主要关心InputManager实例的创建过程它会InputManager类的构造函数里面执行一些初始化操作。
Step 8. InputManagerltinitgtC C层的InputManager类的构造函数定义在frameworks/base/libs/ui/InputManager.cpp 文件中 cpp view plaincopy 1. InputManager::InputManager 2. const spltEventHubInterfacegtamp eventHub 3. const spltInputReaderPolicyInterfacegtamp readerPolicy 4. const spltInputDispatcherPolicyInterfacegtamp dispatcherPolicy 5. mDispatcher new InputDispatcherdispatcherPolicy 6. mReader new InputReadereventHub readerPolicy mDispatcher 7. initialize 8. 这里主要是创建了一个InputDispatcher对象和一个InputReader对象并且分别保存在成员变量mDispatcher和mReader中。
InputDispatcher类是负责把键盘消息分发给当前激活的Activity窗口的而InputReader类则是通过EventHub类来实现读取键盘事件的后面我们会进一步分析。
创建了这两个对象后还要调用initialize函数来执行其它的初始化操作。
Step 9. InputManager.initialize 这个函数定义在frameworks/base/libs/ui/InputManager.cpp 文件中 cpp view plaincopy 1. void InputManager::initialize 2. mReaderThread new InputReaderThreadmReader 3. mDispatcherThread new InputDispatcherThreadmDispatcher 4. 这个函数创建了一个InputReaderThread线程实例和一个InputDispatcherThread线程实例并且分别保存在成员变量mReaderThread和mDispatcherThread中。
这里的InputReader实列mReader就是通过这里的InputReaderThread线程实列mReaderThread来读取键盘事件的而InputDispatcher实例mDispatcher则是通过这里的InputDispatcherThread线程实例mDisptacherThread来分发键盘消息的。
至此InputManager的初始化工作就完成了在回到Step 3中继续分析InputManager的进一步启动过程之前我们先来作一个小结看看这个初始化过程都做什么事情 A. 在Java层中的WindowManagerService中创建了一个InputManager对象由它来负责管理
Android应用程序框架层的键盘消息处理 B. 在C层也相应地创建一个InputManager本地对象来负责监控键盘事件 C. 在C层中的InputManager对象中分别创建了一个InputReader对象和一个InputDispatcher对象前者负责读取系统中的键盘消息后者负责把键盘消息分发出去 D. InputReader对象和一个InputDispatcher对象分别是通过InputReaderThread线程实例和InputDispatcherThread线程实例来实键盘消息的读取和分发的。
有了这些对象之后万事就俱备了回到Step 3中调用InputManager类的start函数来执行真正的启动操作。
Step 10. InputManager.start 这个函数定义在frameworks/base/services/java/com/
android/server/InputManager.java文件中 cpp view plaincopy 1. public class InputManager 2. ...... 3. 4. public void start 5. Slog.iTAG quotStarting input managerquot 6. nativeStart 7. 8. 9. ...... 10. 这个函数通过调用本地方法nativeStart来执行进一步的启动操作。
Step 11. InputManager.nativeStart 这个函数定义在frameworks/base/services/jni vi com_
android_server_InputManager.cpp文件中 cpp view plaincopy 1. static void
android_server_InputManager_nativeStartJNIEnv env jclass clazz 2. if checkInputManagerUnitializedenv 3. return 4. 5. 6. status_t result gNativeInputManager-gtgetInputManager-gtstart 7. if result 8. jniThrowRuntimeExceptionenv quotInput manager could not be started.quot 9. 10. 这里的gNativeInputManager对象是在前面的Step 6中创建的通过它的getInputManager函数可以返回C层的InputManager对象接着调用这个InputManager对象的start函数。
Step 12. InputManager.start 这个函数定义在frameworks/base/libs/ui/InputManager.cpp 文件中 cpp view plaincopy 1. status_t InputManager::start 2. status_t result mDispatcherThread-gtrunquotInputDispatcherquot PRIORITY_URGENT_DISPLAY 3. if result 4. LOGEquotCould not start InputDispatcher thread due to error d.quot result 5. return result 6. 7. 8. result mReaderThread-gtrunquotInputReaderquot PRIORITY_URGENT_DISPLAY 9. if result 10. LOGEquotCould not start InputReader thread due to error d.quot result 11. 12. mDispatcherThread-gtrequestExit 13. return result 14. 15. 16. return OK 17. 这个函数主要就是分别启动一个InputDispatcherThread线程和一个InputReaderThread线程来读取和分发键盘消息的了。
这里的InputDispatcherThread线程对象mDispatcherThread和InputReaderThread线程对象是在前面的Step 9中创建的调用了它们的run函数后就会进入到它们的threadLoop函数中去只要threadLoop函数返回true函数threadLoop就会一直被循环调用于是这两个线程就起到了不断地读取和分发键盘消息的作用。
我们先来分析InputDispatcherThread线程分发消息的过程然后再回过头来分析InputReaderThread线程读取消息的过程。
Step 13. InputDispatcherThread.threadLoop 这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中 cpp view plaincopy 1. bool InputDispatcherThread::threadLoop 2. mDispatcher-gtdispatchOnce 3. return true 4. 这里的成员变量mDispatcher即为在前面Step 8中创建的InputDispatcher对象调用它的dispatchOnce成员函数执行一次键盘消息分发的操作。
Step 14. InputDispatcher.dispatchOnce 这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中 cpp view plaincopy 1. void InputDispatcher::dispatchOnce 2. nsecs_t keyRepeatTimeout mPolicy-gtgetKeyRepeatTimeout 3. nsecs_t keyRepeatDelay mPolicy-gtgetKeyRepeatDelay 4. 5. nsecs_t nextWakeupTime LONG_LONG_MAX 6. // acquire lock 7. AutoMutex _lmLock 8. dispatchOnceInnerLockedkeyRepeatTimeout keyRepeatDelay amp nextWakeupTime 9. 10. if runCommandsLockedInterruptible 11. nextWakeupTime LONG_LONG_MIN // force next poll to wake up immediately 12. 13. // release lock 14. 15. // Wait for callback or timeout or wake. make sure we round up not down 16. nsecs_t currentTime now 17. int32_t timeoutMillis 18. if nextWakeupTime gt currentTime 19. uint64_t timeout uint64_tnextWakeupTime - currentTime 20. timeout timeout 999999LL / 1000000LL 21. timeoutMillis timeout gt INT_MAX -1 : int32_ttimeout 22. else 23. timeoutMillis 0 24. 25. 26. mLooper-gtpollOncetimeoutMillis 27. 这个函数很简单把键盘消息交给dispatchOnceInnerLocked函数来处理这个过程我们在后面再详细分析然后调用mLooper-gtpollOnce函数等待下一次键盘事件的发生。
这里的成员变量mLooper的类型为Looper它定义在C层中具体可以参考前面
Android应用程序消息处理机制Looper、Handler分析一文。
Step 15. Looper.pollOnce 这个函数定义在frameworks/base/libs/utils/Looper.cpp文件中具体可以参考前面
Android应用程序消息处理机制Looper、Handler分析一文这里就不再详述了。
总的来说就是在Looper类中会创建一个管道当调用Looper类的pollOnce函数时如果管道中没有内容可读那么当前线程就会进入到空闲等待状态当有键盘事件发生时InputReader就会往这个管道中写入新的内容这样就会唤醒前面正在等待键盘事件发生的线程。
InputDispatcher类分发消息的过程就暂时分析到这里后面会有更进一步的分析现在我们回到Step 12中接着分析InputReader类读取键盘事件的过程。
在调用了InputReaderThread线程类的run就函数后同样会进入到InputReaderThread线程类的threadLoop函数中去。
Step 16. InputReaderThread.threadLoop 这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中 cpp view plaincopy 1. bool InputReaderThread::threadLoop 2. mReader-gtloopOnce 3. return true 4. 这里的成员变量mReader即为在前面Step 8中创建的InputReader对象调用它的loopOnce成员函数执行一次键盘事件的读取操作。
Step 17. InputReader.loopOnce 这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中 cpp view plaincopy 1. void InputReader::loopOnce 2. RawEvent rawEvent 3. mEventHub-gtgetEventamp rawEvent 4. 5. if DEBUG_RAW_EVENTS 6. LOGDquotInput event: device0xx type0xx scancoded keycoded valuedquot 7. rawEvent.deviceId rawEvent.type rawEvent.scanCode rawEvent.keyCode 8. rawEvent.value 9. endif 10. 11. proc.