在android系統(tǒng)中,鍵盤按鍵事件是由SystemServer服務(wù)來管理的;然后在以消息的形式分發(fā)給應(yīng)用程序處理。產(chǎn)生鍵盤按鍵事件則是有Linuxkernel的相關(guān)驅(qū)動來實現(xiàn)。
鍵盤消息有別于其他類型的消息;需要從Linuxkerneldrivers產(chǎn)生由上層app來處理。同時按鍵有著不同的映射值,因此從模塊獨立性角度各個獨立的模塊應(yīng)該擁有不同的鍵盤映射。這樣以來,kernel產(chǎn)生的按鍵事件必然回經(jīng)過不同的映射才到app。
1、kernel中同按鍵相關(guān)代碼
Android 使用標(biāo)準(zhǔn)的 linux 輸入事件設(shè)備(/dev/input/)和驅(qū)動按鍵定義在 linux 內(nèi)核include/linux/input.h 中,按鍵的定義形式如下(僅以BACKHOME MENU為例):

有了按鍵的定義,就需要產(chǎn)生相應(yīng)的按鍵事件了。在kernel/arch/arm/mach-msm/xxx/xxx/xxx.c會對BACK HOME和MENU進(jìn)行注冊。這里使用在屏幕上的坐標(biāo)來對按鍵進(jìn)行區(qū)分。這部分代碼會在系統(tǒng)啟動的時候,將相應(yīng)的數(shù)據(jù)存儲,以供framework查詢。
(這里以xxx代替,是因為針對不同的硬件,需要的Linux kernel不同)

當(dāng)然從核心板原理圖到kernel是屬于驅(qū)動范疇,不討論。
2、framework針對鍵盤事件的處理
上層對輸入事件的偵聽和分發(fā)是在InputManagerService 中實現(xiàn)
首先來看看InputManagerService的創(chuàng)建,
Step1
在SystemServer.java
- class ServerThread
extends
Thread {
- //省略。。
- public
void run()
{
- // Create a handler thread just for the window manager to enjoy.
- HandlerThread wmHandlerThread
= new HandlerThread("WindowManager");
- wmHandlerThread.start();
- Handler wmHandler
= new
Handler(wmHandlerThread.getLooper());
- //此處省略5k字。。
- Slog.i(TAG,
"Input Manager");
- inputManager
= new InputManagerService(context, wmHandler);
- }
- }
可以看到,在系統(tǒng)啟動的時候,會首先創(chuàng)建一個系統(tǒng)級別的Handler線程wmHandlerThread用于處理鍵盤消息(僅說明鍵盤消息)。然后在創(chuàng)建輸入管理服務(wù) inputManager,InputManagerService 的第二個參數(shù)就是用于處理按鍵消息的Handler。
Step2
在往下走到 InputManagerService.java的構(gòu)造函數(shù)。
- public InputManagerService(Context
context,
Handler handler)
{
- this.mContext
= context;
- this.mHandler
= new InputManagerHandler(handler.getLooper());
- mUseDevInputEventForAudioJack
=
- context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
- Slog.i(TAG,
"Initializing input manager, mUseDevInputEventForAudioJack="
- + mUseDevInputEventForAudioJack);
- mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
- }
這里做了重要的兩件事情,第一:將SystemServer級別的Handler賦值給 InputManagerService自己的消息處理Handler;第二:調(diào)用nativeInit繼續(xù)進(jìn)行初始化。
Step3
com_android_server_InputManagerService.cpp
- static jint nativeInit(JNIEnv* env, jclass clazz,
- jobject serviceObj, jobject contextObj, jobject messageQueueObj)
{
- sp<MessageQueue> messageQueue
= android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
- NativeInputManager* im
= new NativeInputManager(contextObj, serviceObj,
- messageQueue->getLooper());
- im->incStrong(serviceObj);
- return reinterpret_cast<jint>(im);
- }
這里nativeInit直接調(diào)用了 NativeInputManager的構(gòu)造函數(shù)
Step4
- NativeInputManager::NativeInputManager(jobject
contextObj,
- jobject serviceObj,
const sp<Looper>& looper)
:
- mLooper(looper)
{
- JNIEnv* env
= jniEnv();
- mContextObj = env->NewGlobalRef(contextObj);
- mServiceObj = env->NewGlobalRef(serviceObj);
- {
- AutoMutex _l(mLock);
- mLocked.systemUiVisibility
= ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
- mLocked.pointerSpeed
= 0;
- mLocked.pointerGesturesEnabled
= true;
- mLocked.showTouches
= false;
- }
- sp<EventHub> eventHub
= new EventHub();
- mInputManager = new InputManager(eventHub, this, this);
- }
這里需要特別注意最后兩行代碼。第一:創(chuàng)建了 EventHub;第二:創(chuàng)建 InputManager并將 EventHub作為參數(shù)傳入InputManager。
Step5
接下來繼續(xù)看看InputManager的構(gòu)造函數(shù)。
- InputManager::InputManager(
- const sp<EventHubInterface>& eventHub,
- const sp<InputReaderPolicyInterface>& readerPolicy,
- const sp<InputDispatcherPolicyInterface>& dispatcherPolicy)
{
- mDispatcher
= new InputDispatcher(dispatcherPolicy);
- mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
- initialize();
- }
- void InputManager::initialize()
{
- mReaderThread = new InputReaderThread(mReader);
- mDispatcherThread
= new InputDispatcherThread(mDispatcher);
- }
創(chuàng)建了InputDispatcher 和InputReader,并調(diào)用了initialize函數(shù)創(chuàng)建了InputReaderThread和InputDispatcherThread。InputDispatcher類是負(fù)責(zé)把鍵盤消息分發(fā)給當(dāng)前激活的Activity窗口的,而InputReader類則是通過 EventHub類來實現(xiàn)讀取鍵盤事件的,InputReader實列mReader就是通過這里的 InputReaderThread線程實列mReaderThread來讀取鍵盤事件的,而InputDispatcher實例mDispatcher
則是通過這里的InputDispatcherThread線程實例mDisptacherThread來分發(fā)鍵盤消息的。
到這里,相關(guān)的組件都已經(jīng)被創(chuàng)建了;
Step6
接下來看看他們是如何運行起來的。
在systemServer.java中創(chuàng)建inputManager之后。將InputManagerServer進(jìn)行注冊,并運行start()
- ServiceManager.addService(Context.INPUT_SERVICE,
inputManager);
- inputManager.start();
- //InputManager的start函數(shù):
- public
void start()
{
- Slog.i(TAG,
"Starting input manager");
- nativeStart(mPtr);
- //省略。。
- }
調(diào)用nativeStart繼續(xù)往下走。順帶說一下,這里的參數(shù)mPtr是指向nativeinputmanager service對象的,在InputManagerService構(gòu)造函數(shù)中由nativeInit賦值。
Step7
接下來又到了com_android_server_InputManagerService.cpp中。
- static void nativeStart(JNIEnv* env, jclass clazz,
jint ptr) {
- NativeInputManager* im
= reinterpret_cast<NativeInputManager*>(ptr);
- status_t result
= im->getInputManager()->start();
- if
(result) {
- jniThrowRuntimeException(env,
"Input manager could not be started.");
- }
- }
這里的im就是inputManager并且用到了上面?zhèn)飨聛淼膍Ptr來重新構(gòu)建。
Step8
繼續(xù)往下則會調(diào)用到InputManager.cpp 的start函數(shù)
- status_t InputManager::start()
{
- status_t result
= mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
- if
(result) {
- ALOGE("Could not start InputDispatcher thread due to error %d.", result);
- return result;
- }
- result = mReaderThread->run("InputReader",
PRIORITY_URGENT_DISPLAY);
- if
(result) {
- ALOGE("Could not start InputReader thread due to error %d.", result);
- mDispatcherThread->requestExit();
- return result;
- }
- return OK;
- }
這個函數(shù)主要就是分別啟動一個InputDispatcherThread線程和一個InputReaderThread線程來讀取和分發(fā)鍵 盤消息的了。這里的InputDispatcherThread線程對象mDispatcherThread和InputReaderThread線程對 象是在前面的Step9中創(chuàng)建的,調(diào)用了它們的run函數(shù)后,就會進(jìn)入到它們的threadLoop函數(shù)中去,只要threadLoop函數(shù)返回true,函數(shù) threadLoop就會一直被循環(huán)調(diào)用,于是這兩個線程就起到了不斷地讀取和分發(fā)鍵盤消息的作用。
Step9
在下來繼續(xù)看loopOnce()這個函數(shù)。
- void InputReader::loopOnce()
{
- //......
- size_t count = mEventHub->getEvents(timeoutMillis,
mEventBuffer, EVENT_BUFFER_SIZE);
- //......
- if
(count) {
- processEventsLocked(mEventBuffer, count);
- }
- //......
- // Flush queued events out
to the listener.
- // This must happen outside of the lock because the listener could potentially
call
- // back into the InputReader's methods, such as getScanCodeState,
or become blocked
- //
on another thread similarly waiting
to acquire the InputReader lock thereby
- // resulting
in a deadlock. This situation
is actually quite plausible because the
- // listener
is actually the input dispatcher, which calls into the
window manager,
- // which occasionally calls into the input reader.
- mQueuedListener->flush();
- }
這里面需要注意像神一樣的函數(shù) mEventHub->getEvents()。其實現(xiàn)原理,還有點不是很清楚;但是其功能就是負(fù)責(zé)鍵盤消息的讀取工作,如果當(dāng)前有鍵盤事件發(fā)生或者有鍵盤事件等待處理,通過mEventHub的 getEvent函數(shù)就可以得到這個事件,然后交給processEventsLocked 函數(shù)進(jìn)行處理。同樣需要特別注意最后一行;后面回解釋。我們還會回來的~~~
- /*
- * Wait
for events to become available
and returns them.
- * After returning, the EventHub holds onto a wake lock
until the next
call to getEvent.
- * This ensures that the device will
not go to sleep
while the event is being processed.
- *
If the device needs to remain awake longer than that,
then the caller is responsible
- *
for taking care of it (say, by poking the power manager user activity timer).
- *
- * The timeout
is advisory only.
If the device is asleep, it will
not wake just to
- * service the timeout.
- *
- * Returns the number of events obtained,
or 0 if the timeout expired.
- */
- virtual size_t getEvents(int timeoutMillis, RawEvent* buffer,
size_t bufferSize)
函數(shù)原型!
在成功獲取input Event之后,就會用到 processEventsLocked函數(shù)來處理Event
然后在調(diào)用到 processEventsForDeviceLocked(deviceId,rawEvent, batchSize);
最后在void InputDevice::process(constRawEvent* rawEvents, size_t count)
我就在想:問什么不直接到process函數(shù)呢?其實我覺得這里體現(xiàn)了設(shè)計模式中的單一職責(zé)原則;這種設(shè)計可以有效的控制函數(shù)粒度(有個類粒度,這里自創(chuàng)函數(shù)粒度)的大小,函數(shù)承擔(dān)的職責(zé)越多其復(fù)用的可能性就越小,并且當(dāng)期中某一個職責(zé)發(fā)生變化,可能會影響其他職責(zé)的運作!
Step 10
接下來繼續(xù)看InputDevice::process函數(shù)。
- void InputDevice::process(const RawEvent*
rawEvents, size_t count)
{
- //。。。。
- InputMapper* mapper
= mMappers[i];
- mapper->process(rawEvent);
- }
走到這里才算是真真正正的知道了有按鍵發(fā)生了,調(diào)用 KeyboardInputMapper::process(const RawEvent*)處理input event; KeyboardInputMapper 繼承自 InputMapper。那為什么調(diào)用的是 KeyboardInputMapper而不是SwitchInputMapper等等。。
請留意
- InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
- const InputDeviceIdentifier& identifier, uint32_t classes)
函數(shù)中的片段:
- if
(keyboardSource != 0)
{
- device->addMapper(new KeyboardInputMapper(device,
keyboardSource, keyboardType));
- }
這里Event Type有必要提一下,以下是一些常用的Event。在kernel/Documentation/input/event-codes.txt中有詳細(xì)的描述。
* EV_SYN:
- Used as markers to separate events. Eventsmay be separated in time or in space, such as with the multitouch protocol.
* EV_KEY:
- Used to describe state changes ofkeyboards, buttons, or other key-like devices.
* EV_REL:
- Used to describe relative axis value changes,e.g. moving the mouse 5 units to the left.
* EV_ABS:
- Used to describe absolute axis valuechanges, e.g. describing the coordinates of a touch on a touchscreen.
* EV_MSC:
- Used to describe miscellaneous input datathat do not fit into other types.
* EV_SW:
- Used to describe binary stateinput switches.
Step 11
- void KeyboardInputMapper::process(const RawEvent*
rawEvent) {
- switch (rawEvent->type)
{
- case EV_KEY:
{
- int32_t scanCode
= rawEvent->code;
- int32_t usageCode
= mCurrentHidUsage;
- mCurrentHidUsage
= 0;
- if
(isKeyboardOrGamepadKey(scanCode))
{
- int32_t keyCode;
- uint32_t flags;
- if
(getEventHub()->mapKey(getDeviceId(),
scanCode, usageCode,
&keyCode,
&flags))
{
- keyCode
= AKEYCODE_UNKNOWN;
- flags
= 0;
- }
- processKey(rawEvent->when, rawEvent->value
!= 0, keyCode, scanCode, flags);
- }
- break;
- }
- }
- }
在這里,先判斷isKeyboardOrGamepadKey(scanCode),然后在用getEventHub()->mapKey()檢測 提供的key是否正確,在然后就開始處理了processKey
Step 12
- void KeyboardInputMapper::processKey(nsecs_t when, bool down,
int32_t keyCode,
- int32_t scanCode, uint32_t policyFlags)
{
- //忽略到所有的。。只看最后兩行。。
- NotifyKeyArgs args(when, getDeviceId(), mSource,
policyFlags,
- down ? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP,
- AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
- getListener()->notifyKey(&args);
- }
不用多解釋了,直接notifyKey了。。但需要注意,這里的notifyKey 僅僅是 NotifyKeyArgs push到消息隊列中去;并沒有通知上層!那到底在那兒通知的呢?
還記不記得在void InputReader::loopOnce()這個函數(shù)的最后一行代碼,其實質(zhì)是在這個函數(shù)中通知上層有按鍵事件發(fā)生。
這個flush()很明顯,notify了之后,就delete,不存在了。問什么不是在getListener()->notifyKey(&args);的時候就真正的notify?我覺得可以做如下角度予以考慮:
第一:線程是最小的執(zhí)行單位;因此每當(dāng)inputThread.start()的時候,如果不flush,回造成數(shù)據(jù)混亂。
第二:flush操作是必須的,同時在loopOnce的最后操作也是最恰當(dāng)?shù)?。其實這里的Listener也就是充當(dāng)了一個事件分發(fā)者的角色。
這說明,到這里已經(jīng)完全識別了按鍵了,并按照自己的鍵盤映射映射了一個值保存在args中,notifyKey給上層應(yīng)用了。。
Step 13
其實針對BACK HOME MENU這三個按鍵來說,其實質(zhì)就是TouchScreen;因此在inputReader.cpp中獲取Touch映射是在函數(shù)boolTouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) 中。這里同上面的Step 12相同。

首先檢測不是多點Touch。然后使用const TouchInputMapper::VirtualKey*TouchInputMapper::findVirtualKeyHit( int32_t x, int32_t y)依據(jù)坐標(biāo)值查找出Touch的映射值。
到最后了啊。。。
呵呵,看看是怎么實現(xiàn)的。。
|