本文主要是介绍Android键值上报流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、介绍
在常用手机中,常用的键值有power,volume_up,volume_down,home,back,menu。其中power先跳过不管,它与唤醒睡眠流程相关,余下键值中volume_up和volume_down是在键值驱动中的实体键值,home,back,menu则是tp来模拟的虚拟键值。本次就用除去power之外的其他几个键值来探索下Android键值的上报流程。
二、驱动层
驱动层中,对应的驱动为volume_up和volume_down在keypad/kpd.c中,通过属性为KEY的方式注册到input子系统中,当按下这两个键值产生中断之后,就通过input来上报键值。home,back和menu这是通过tp来模拟的虚拟键值,在tp驱动,触发之后上报特定的tp坐标来表示。
三、应用层
1、InputManagerService
在开机时候会注册InputManagerService服务:
inputManager = new InputManagerService(context, wmHandler);inputManager.setWindowManagerCallbacks(wm.getInputMonitor());inputManager.start();
在inputManager中开启了两个线程:InputReader 和 InputDispatcher
InputReader:不停的通过EventHub来监测读取input上报的键值数据,将键值数据初步整理,封装之后,发送给InputDispatcher
InputDispatcher:不停的循环等待来自InputReader传过来的键值数据,在接收到键值数据之后,对键值进行整理分发,按键背光也会在该线程中点亮。
2、InputReader
在InputReader中,主要是一个 mReader->loopOnce();在不断循环,我们抽取它按键相关函数做讲解:
void InputReader::loopOnce() {
.......
.......
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
.......
.......
if (count) {
processEventsLocked(mEventBuffer, count);
}
........
........
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);
}
} // release lock
//ALOGD("loopOnce:: release lock 2" );
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
mQueuedListener->flush();
}
在该函数中,通过mEventHub->getEvents来打开input接口,监听input,如果没有键值数据上报,线程就在这里睡眠等待,到接受到数据之后,就通过函数mEventHub->getEvents对键值进行简单处理,封装成notify格式,通过notifyInputDevicesChanged将封装后的键值加入队列,最后通过mQueuedListener->flush()发送出去。
其中在getEvents中,首先会检查是不是第一次读取,如果是第一次的话,会首先浏览系统,打开所有需要监听的input设备,将打开的设备文件节点加入到epoll中监听,当有没有数据时候就睡眠在epoll_wait中,有数据时候就接收数据。
当接收到数据之后,就会随后调用函数processEventsLocked进行处理:
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {for (const RawEvent* rawEvent = rawEvents; count;) {int32_t type = rawEvent->type;size_t batchSize = 1;if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {int32_t deviceId = rawEvent->deviceId;while (batchSize < count) {if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT|| rawEvent[batchSize].deviceId != deviceId) {break;}batchSize += 1;}
#if DEBUG_RAW_EVENTSALOGD("BatchSize: %d Count: %d", batchSize, count);
#endifprocessEventsForDeviceLocked(deviceId, rawEvent, batchSize);} else {....................}
}
因为是处理的上报键值,于是进入函数processEventsForDeviceLocked中,该函数将调用不同键值处理的process中:
void InputReader::processEventsForDeviceLocked(int32_t deviceId,const RawEvent* rawEvents, size_t count) {ssize_t deviceIndex = mDevices.indexOfKey(deviceId);if (deviceIndex < 0) {ALOGW("Discarding event for unknown deviceId %d.", deviceId);return;}InputDevice* device = mDevices.valueAt(deviceIndex);if (device->isIgnored()) {//ALOGD("Discarding event for ignored deviceId %d.", deviceId);return;}device->process(rawEvents, count);
}
通过device->process来轮询,选择对应的封装函数:
void InputDevice::process(const RawEvent* rawEvents, size_t count) {......................mapper->process(rawEvent);......................
}
该mapper->process在之前通过createDeviceLocked将各类的键值处理函数加入到了Mapper中:
InputDevice* InputReader::createDeviceLocked(int32_t deviceId,const InputDeviceIdentifier& identifier, uint32_t classes) {InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),identifier, classes);....................// Keyboard-like devices.uint32_t keyboardSource = 0;int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {keyboardSource |= AINPUT_SOURCE_KEYBOARD;}if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;}if (classes & INPUT_DEVICE_CLASS_DPAD) {keyboardSource |= AINPUT_SOURCE_DPAD;}if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {keyboardSource |= AINPUT_SOURCE_GAMEPAD;}if (keyboardSource != 0) {device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));}..................return device;
}
键值上报的处理函数对应就是如上的KeyboardInputMapper,所以mapper->process就会对应调用到KeyboardInputMapper->process对上报的键值进行处理。
KeyboardInputMapper::process ---> processKey来将键值封装成notify格式。
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);
}
之后就是通过getInputDevicesLocked, mPolicy->notifyInputDevicesChanged,进行加入队列之类操作,最后mQueuedListener->flush()来发送出去。
3、InputDispatcher
InputDispatcher也是类似的通过mDispatcher->dispatchOnce();来循环分发InputReader传过来的键值,对应函数如下:
void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LONG_LONG_MAX;{ // acquire lockAutoMutex _l(mLock);mDispatcherIsAliveCondition.broadcast();// Run a dispatch loop if there are no pending commands.// The dispatch loop might enqueue commands to run afterwards.if (!haveCommandsLocked()) {dispatchOnceInnerLocked(&nextWakeupTime);}// Run all pending commands if there are any.// If any commands were run then force the next poll to wake up immediately.if (runCommandsLockedInterruptible()) {nextWakeupTime = LONG_LONG_MIN;}} // release lock// Wait for callback or timeout or wake. (make sure we round up, not down)nsecs_t currentTime = now();int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);mLooper->pollOnce(timeoutMillis);
}
这个函数会用dispatchOnceInnerLocked对InputReader的函数传的键值进行处理,之后runCommandsLockedInterruptible调用CommandEntry中加入的操作函数。
dispatchOnceInnerLocked会点亮按键背光,并根据键值做对应处理:
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime){......................pokeUserActivityLocked(mPendingEvent); //点亮按键背光......................case EventEntry::TYPE_KEY: {KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);if (isAppSwitchDue) {if (isAppSwitchKeyEventLocked(typedEntry)) {resetPendingAppSwitchLocked(true);isAppSwitchDue = false;} else if (dropReason == DROP_REASON_NOT_DROPPED) {dropReason = DROP_REASON_APP_SWITCH;}}if (dropReason == DROP_REASON_NOT_DROPPED&& isStaleEventLocked(currentTime, typedEntry)) {dropReason = DROP_REASON_STALE;}if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {dropReason = DROP_REASON_BLOCKED;}done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);break;}........................
}
pokeUserActivityLocked会进入PowerManagerService中,最终通过mButtonLight.turnOff();和mButtonLight.turnOn();来关闭或者点亮按键背光。之后调用dispatchKeyLocked来做进一步的处理:
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,DropReason* dropReason, nsecs_t* nextWakeupTime) {..............................// Handle case where the policy asked us to try again later last time.if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {if (currentTime < entry->interceptKeyWakeupTime) {if (entry->interceptKeyWakeupTime < *nextWakeupTime) {*nextWakeupTime = entry->interceptKeyWakeupTime;}return false; // wait until next wakeup}entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;entry->interceptKeyWakeupTime = 0;}// Give the policy a chance to intercept the key.if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {CommandEntry* commandEntry = postCommandLocked(& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);if (mFocusedWindowHandle != NULL) {commandEntry->inputWindowHandle = mFocusedWindowHandle;}commandEntry->keyEntry = entry;entry->refCount += 1;return false; // wait for the command to run} else {entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;}} else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {if (*dropReason == DROP_REASON_NOT_DROPPED) {*dropReason = DROP_REASON_POLICY;}}// Clean up if dropping the event.if (*dropReason != DROP_REASON_NOT_DROPPED) {setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);return true;}// Identify targets.Vector<InputTarget> inputTargets;int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,entry, inputTargets, nextWakeupTime);if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {return false;}setInjectionResultLocked(entry, injectionResult);if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {return true;}addMonitoringTargetsLocked(inputTargets);// Dispatch the key.dispatchEventLocked(currentTime, entry, inputTargets);return true;
}
这个函数在KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN的时候会将doInterceptKeyBeforeDispatchingLockedInterruptible函数通过postCommandLocked加入到commandEntry中,然后直接false返回。当处理按键键值(如volume_up)时候,第一次就会在这里直接返回。之后就会进入到
InputReader::loopOnce() ---> runCommandsLockedInterruptible()中调用commandEntry中的函数,也就是刚刚加入的 doInterceptKeyBeforeDispatchingLockedInterruptible:
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry) {KeyEntry* entry = commandEntry->keyEntry;KeyEvent event;initializeKeyEvent(&event, entry);mLock.unlock();nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,&event, entry->policyFlags);mLock.lock();if (delay < 0) {entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;} else if (!delay) {entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;} else {entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;entry->interceptKeyWakeupTime = now() + delay;}entry->release();
}
这个函数中,首先将entry中的键值属性赋值到event中,然后调用函数interceptKeyBeforeDispatching,对应函数为:
PhoneWindowManager---->interceptKeyBeforeDispatchingd 处理上报的按键键值,在这个函数中如果键值是Home则,对应对应处理该键值,并返回-1,如果是其他键值(如volume)之类的,这不做任何处理,直接返回0.之后delay被赋值为interceptKeyBeforeDispatching的返回值,到返回值是-1的时候,设置interceptKeyResult为KeyEntry::INTERCEPT_KEY_RESULT_SKIP,如果返回值是0,则设置为INTERCEPT_KEY_RESULT_CONTINUE。之后进入InputDispatcher::dispatchOnce()的第二次循环。从新进入到InputDispatcher::dispatchKeyLocked中:
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,DropReason* dropReason, nsecs_t* nextWakeupTime) {..............................// Handle case where the policy asked us to try again later last time.if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {if (currentTime < entry->interceptKeyWakeupTime) {if (entry->interceptKeyWakeupTime < *nextWakeupTime) {*nextWakeupTime = entry->interceptKeyWakeupTime;}return false; // wait until next wakeup}entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;entry->interceptKeyWakeupTime = 0;}// Give the policy a chance to intercept the key.if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {CommandEntry* commandEntry = postCommandLocked(& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);if (mFocusedWindowHandle != NULL) {commandEntry->inputWindowHandle = mFocusedWindowHandle;}commandEntry->keyEntry = entry;entry->refCount += 1;return false; // wait for the command to run} else {entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;}} else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {if (*dropReason == DROP_REASON_NOT_DROPPED) {*dropReason = DROP_REASON_POLICY;}}// Clean up if dropping the event.if (*dropReason != DROP_REASON_NOT_DROPPED) {setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);return true;}// Identify targets.Vector<InputTarget> inputTargets;int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,entry, inputTargets, nextWakeupTime);if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {return false;}setInjectionResultLocked(entry, injectionResult);if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {return true;}addMonitoringTargetsLocked(inputTargets);// Dispatch the key.dispatchEventLocked(currentTime, entry, inputTargets);return true;
}
这一次进入dispatchKeyLocked函数之后,如果键值是home,则entry->interceptKeyResult的属性为:KeyEntry::INTERCEPT_KEY_RESULT_SKIP,之后就会在setInjectionResultLocked中做表示键值处理完成的操作,拦截键值,不往上层发送;如果属性为KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE,则接着通过findFocusedWindowTargetsLocked找到目前焦点所在的activity,然后dispatchEventLocked来上报键值:
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
#if DEBUG_DISPATCH_CYCLEALOGD("dispatchEventToCurrentInputTargets");
#endifALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to truepokeUserActivityLocked(eventEntry);for (size_t i = 0; i < inputTargets.size(); i++) {const InputTarget& inputTarget = inputTargets.itemAt(i);ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);if (connectionIndex >= 0) {sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);} else {
#if DEBUG_FOCUS ALOGD("Dropping event delivery to target with channel '%s' because it ""is no longer registered with the input dispatcher.",inputTarget.inputChannel->getName().string());
#endif}}
}
在这个函数中,又一次的调用了pokeUserActivityLocked(eventEntry);来点亮按键背光,所以如果想要关闭按键时候的按键背光,就需要去掉这两处的pokeUserActivityLocked(eventEntry)函数,之后就通过connection往上层发送键值了。
四、 按键背光
在上面的流程分析中,已经知道了在Android上报键值的时候,点亮按键背光是通过函数pokeUserActivityLocked(eventEntry);主要流程如下:
pokeUserActivityLocked ---> doPokeUserActivityLockedInterruptible --->android_server_PowerManagerService_userActivity ---> userActivityInternal(PowerManagerService) ---> userActivityInternal ---> updatePowerStateLocked() ---> updateDisplayPowerStateLocked -->mButtonLight.turnOff() / mButtonLight.setBrightness(screenBrightness);
最后通过mButtonLight进入到LightsService中,通过setLight_native进入jni层,做对应的点亮或者关闭按键背光处理,进入LightsService之后,按键背光的处理就和呼吸灯的处理差不多了,只是把选择熟悉换成了button。对了表示点亮按键背光之后,按键背光亮多长时间定义在:SCREEN_BUTTON_LIGHT_DURATION = 8 * 1000;
所以当我们需要关闭到按键背光时候,一般来说有两种办法,第一种就是Android上报键值流程中去掉对函数pokeUserActivityLocked(eventEntry);,注意一共需要去掉两处,如果只去掉第一处的话,会出现home不会点亮按键背光,其他键值可以点亮;只去掉第二处的话,情况相反。
五、屏蔽按键方法
1.在某些情况下我们可能需要一些特定的按键失效,在键值流程中直接拦截掉键值,使键值失效的方法很多,这里介绍三种办法:
2.修改按键的kl映射文件
找到Android中对应的键值映射对应的kl文件,然后直接修改它的映射值。比如我们要屏蔽volnme_up的按键,就做如下修改:
.........
.........
key 105 DPAD_LEFT
key 106 DPAD_RIGHT
key 115 VOLUME_UP WAKE_DROPPED
key 114 VOLUME_DOWN WAKE_DROPPED
key 113 MUTE WAKE_DROPPED
.......
.......
正常情况下,VOLUME_UP和linux对应的键值映射为115,我们可以直接修改它的映射为一个无用的值,比如:
key 500 VOLUME_UP WAKE_DROPPED
这样VOLUME_UP键值就失效了,不过这样还是会上传键值,会点亮按键背光。
2.在kpd.c,按键驱动里面,直接去掉将对应按键加入input的步骤。
3.首先确定需要屏蔽的键值是多少,然后在InputReader或者是InputDispatcher中直接再加上一个拦截规则,丢弃对应的键值就好。
这篇关于Android键值上报流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!