Android键值上报流程

2024-06-13 20:08
文章标签 android 流程 上报 键值

本文主要是介绍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键值上报流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1058293

相关文章

springboot启动流程过程

《springboot启动流程过程》SpringBoot简化了Spring框架的使用,通过创建`SpringApplication`对象,判断应用类型并设置初始化器和监听器,在`run`方法中,读取配... 目录springboot启动流程springboot程序启动入口1.创建SpringApplicat

通过prometheus监控Tomcat运行状态的操作流程

《通过prometheus监控Tomcat运行状态的操作流程》文章介绍了如何安装和配置Tomcat,并使用Prometheus和TomcatExporter来监控Tomcat的运行状态,文章详细讲解了... 目录Tomcat安装配置以及prometheus监控Tomcat一. 安装并配置tomcat1、安装

MySQL的cpu使用率100%的问题排查流程

《MySQL的cpu使用率100%的问题排查流程》线上mysql服务器经常性出现cpu使用率100%的告警,因此本文整理一下排查该问题的常规流程,文中通过代码示例讲解的非常详细,对大家的学习或工作有一... 目录1. 确认CPU占用来源2. 实时分析mysql活动3. 分析慢查询与执行计划4. 检查索引与表

Git提交代码详细流程及问题总结

《Git提交代码详细流程及问题总结》:本文主要介绍Git的三大分区,分别是工作区、暂存区和版本库,并详细描述了提交、推送、拉取代码和合并分支的流程,文中通过代码介绍的非常详解,需要的朋友可以参考下... 目录1.git 三大分区2.Git提交、推送、拉取代码、合并分支详细流程3.问题总结4.git push

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO