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

相关文章

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

java Long 与long之间的转换流程

《javaLong与long之间的转换流程》Long类提供了一些方法,用于在long和其他数据类型(如String)之间进行转换,本文将详细介绍如何在Java中实现Long和long之间的转换,感... 目录概述流程步骤1:将long转换为Long对象步骤2:将Longhttp://www.cppcns.c

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

《spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔)》:本文主要介绍spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔),本文通过实例图... 目录需求背景需求拆解设计流程及作用域逻辑处理代码逻辑需求背景公司要求,通过公司网络代理访问的请求需要做请

使用JavaConfig配置Spring的流程步骤

《使用JavaConfig配置Spring的流程步骤》JavaConfig是Spring框架提供的一种基于Java的配置方式,它通过使用@Configuration注解标记的类来替代传统的XML配置文... 目录一、什么是 JavaConfig?1. 核心注解2. 与 XML 配置的对比二、JavaConf