欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

安卓输入事件的一般流程(常见key和touch需求分析)

程序员文章站 2022-07-08 08:22:30
input key and TouchKey常见debug 工具Android Key框架原理常见需求Touch常见debug 工具Android Key框架原理常见需求Key常见debug 工具dumpsys inputAndroid Key框架原理常见需求Touch常见debug 工具Android Key框架原理常见需求......



Android 输入事件的一般流程

EventHub

  1. EventHub 事件枢纽,可以多路复用监听/dev/input/目录 文件的变化;
  2. getEvents步骤:
    (1) . 开始循环执行,直至已经收集了任何事件,或者我们被显式地唤醒,立即返回。
    a. Reopen input devices if needed.
    b. Report any devices that had last been added/removed.
    c. scan devices if Needed, add devices / remove devices.
    d. product adding devices event if needed.
    e. product finishing scan devices event if needed.
    f. start grab the next input event really. //多路复用监听fd,
    g. readNotify()将修改设备列表,因此必须在处理所有其他事件之后完成,以确保在关闭设备之前读取所有剩余事件。
    h. 若上一个动作执行,Report added or removed devices immediately.
    i. 如果我们已经收集了任何事件,或者我们被显式地唤醒,立即返回。

多路复用监听事件补充:

 /*
		man help:
		EPOLLIN       连接到达;有数据来临;
		The associated file is available for read(2) operations.
		EPOLLOUT      有数据要写
		The associated file is available for write(2) operations.
		EPOLLRDHUP    表示读关闭。
		如果有EPOLLRDHUP,检测它就可以直到是对方关闭;否则就用上面方法。
		EPOLLHUP 表示读写都关闭。
		*/ if (eventItem.events & EPOLLIN) { ... } else if (eventItem.events & EPOLLHUP) { ALOGI("Removing device %s due to epoll hang-up event.", device->identifier.name.string()); deviceChanged = true; closeDeviceLocked(device); } else { ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events, device->identifier.name.string()); } 

事件枢纽EventHub重点代码(getEvents 函数):

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { ALOG_ASSERT(bufferSize >= 1); AutoMutex _l(mLock); struct input_event readBuffer[bufferSize]; RawEvent* event = buffer; size_t capacity = bufferSize; bool awoken = false; for (;;) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); // Reopen input devices if needed. if (mNeedToReopenDevices) { mNeedToReopenDevices = false; ALOGI("Reopening all input devices due to a configuration change."); closeAllDevicesLocked(); mNeedToScanDevices = true; break; // return to the caller before we actually rescan } // Report any devices that had last been added/removed. while (mClosingDevices) { Device* device = mClosingDevices; ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.string()); mClosingDevices = device->next; event->when = now; event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id; event->type = DEVICE_REMOVED; event += 1; delete device; mNeedToSendFinishedDeviceScan = true; if (--capacity == 0) { break; } } if (mNeedToScanDevices) { mNeedToScanDevices = false; scanDevicesLocked(); mNeedToSendFinishedDeviceScan = true; } while (mOpeningDevices != NULL) { Device* device = mOpeningDevices; ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.string()); mOpeningDevices = device->next; event->when = now; event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; event->type = DEVICE_ADDED; event += 1; mNeedToSendFinishedDeviceScan = true; if (--capacity == 0) { break; } } if (mNeedToSendFinishedDeviceScan) { mNeedToSendFinishedDeviceScan = false; event->when = now; event->type = FINISHED_DEVICE_SCAN; event += 1; if (--capacity == 0) { break; } } // Grab the next input event. bool deviceChanged = false; while (mPendingEventIndex < mPendingEventCount) { const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++]; if (eventItem.data.u32 == EPOLL_ID_INOTIFY) { if (eventItem.events & EPOLLIN) { mPendingINotify = true; } else { ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events); } continue; } if (eventItem.data.u32 == EPOLL_ID_WAKE) { if (eventItem.events & EPOLLIN) { ALOGV("awoken after wake()"); awoken = true; char buffer[16]; ssize_t nRead; do { nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer)); } else { ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.", eventItem.events); } continue; } ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32); if (deviceIndex < 0) { ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.", eventItem.events, eventItem.data.u32); continue; } Device* device = mDevices.valueAt(deviceIndex); if (eventItem.events & EPOLLIN) { int32_t readSize = read(device->fd, readBuffer, sizeof(struct input_event) * capacity); if (readSize == 0 || (readSize < 0 && errno == ENODEV)) { // Device was removed before INotify noticed. ALOGW("could not get event, removed? (fd: %d size: %" PRId32 " bufferSize: %zu capacity: %zu errno: %d)\n", device->fd, readSize, bufferSize, capacity, errno); deviceChanged = true; closeDeviceLocked(device); } else if (readSize < 0) { if (errno != EAGAIN && errno != EINTR) { ALOGW("could not get event (errno=%d)", errno); } } else if ((readSize % sizeof(struct input_event)) != 0) { ALOGE("could not get event (wrong size: %d)", readSize); } else { int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; size_t count = size_t(readSize) / sizeof(struct input_event); for (size_t i = 0; i < count; i++) { struct input_event& iev = readBuffer[i]; ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d", device->path.string(), (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); // Some input devices may have a better concept of the time // when an input event was actually generated than the kernel // which simply timestamps all events on entry to evdev. // This is a custom Android extension of the input protocol // mainly intended for use with uinput based device drivers. if (iev.type == EV_MSC) { if (iev.code == MSC_ANDROID_TIME_SEC) { device->timestampOverrideSec = iev.value; continue; } else if (iev.code == MSC_ANDROID_TIME_USEC) { device->timestampOverrideUsec = iev.value; continue; } } if (device->timestampOverrideSec || device->timestampOverrideUsec) { iev.time.tv_sec = device->timestampOverrideSec; iev.time.tv_usec = device->timestampOverrideUsec; if (iev.type == EV_SYN && iev.code == SYN_REPORT) { device->timestampOverrideSec = 0; device->timestampOverrideUsec = 0; } ALOGV("applied override time %d.%06d", int(iev.time.tv_sec), int(iev.time.tv_usec)); } // Use the time specified in the event instead of the current time // so that downstream code can get more accurate estimates of // event dispatch latency from the time the event is enqueued onto // the evdev client buffer. // // The event's timestamp fortuitously uses the same monotonic clock // time base as the rest of Android.  The kernel event device driver // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts(). // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a // system call that also queries ktime_get_ts(). event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL + nsecs_t(iev.time.tv_usec) * 1000LL; ALOGV("event time %" PRId64 ", now %" PRId64, event->when, now); // Bug 7291243: Add a guard in case the kernel generates timestamps // that appear to be far into the future because they were generated // using the wrong clock source. // // This can happen because when the input device is initially opened // it has a default clock source of CLOCK_REALTIME.  Any input events // enqueued right after the device is opened will have timestamps // generated using CLOCK_REALTIME.  We later set the clock source // to CLOCK_MONOTONIC but it is already too late. // // Invalid input event timestamps can result in ANRs, crashes and // and other issues that are hard to track down.  We must not let them // propagate through the system. // // Log a warning so that we notice the problem and recover gracefully. if (event->when >= now + 10 * 1000000000LL) { // Double-check.  Time may have moved on. nsecs_t time = systemTime(SYSTEM_TIME_MONOTONIC); if (event->when > time) { ALOGW("An input event from %s has a timestamp that appears to " "have been generated using the wrong clock source " "(expected CLOCK_MONOTONIC): " "event time %" PRId64 ", current time %" PRId64 ", call time %" PRId64 ".  " "Using current time instead.", device->path.string(), event->when, time, now); event->when = time; } else { ALOGV("Event time is ok but failed the fast path and required " "an extra call to systemTime: " "event time %" PRId64 ", current time %" PRId64 ", call time %" PRId64 ".", event->when, time, now); } } event->deviceId = deviceId; event->type = iev.type; event->code = iev.code; event->value = iev.value; event += 1; capacity -= 1; } if (capacity == 0) { // The result buffer is full.  Reset the pending event index // so we will try to read the device again on the next iteration. mPendingEventIndex -= 1; break; } } } else if (eventItem.events & EPOLLHUP) { ALOGI("Removing device %s due to epoll hang-up event.", device->identifier.name.string()); deviceChanged = true; closeDeviceLocked(device); } else { ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events, device->identifier.name.string()); } } // readNotify() will modify the list of devices so this must be done after // processing all other events to ensure that we read all remaining events // before closing the devices. if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) { mPendingINotify = false; readNotifyLocked(); deviceChanged = true; } // Report added or removed devices immediately. if (deviceChanged) { continue; } // Return now if we have collected any events or if we were explicitly awoken. if (event != buffer || awoken) { break; } // Poll for events.  Mind the wake lock dance! // We hold a wake lock at all times except during epoll_wait().  This works due to some // subtle choreography.  When a device driver has pending (unread) events, it acquires // a kernel wake lock.  However, once the last pending event has been read, the device // driver will release the kernel wake lock.  To prevent the system from going to sleep // when this happens, the EventHub holds onto its own user wake lock while the client // is processing events.  Thus the system can only sleep if there are no events // pending or currently being processed. // // The timeout is advisory only.  If the device is asleep, it will not wake just to // service the timeout. mPendingEventIndex = 0; mLock.unlock(); // release lock before poll, must be before release_wake_lock release_wake_lock(WAKE_LOCK_ID); int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis); acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock if (pollResult == 0) { // Timed out. mPendingEventCount = 0; break; } if (pollResult < 0) { // An error occurred. mPendingEventCount = 0; // Sleep after errors to avoid locking up the system. // Hopefully the error is transient. if (errno != EINTR) { ALOGW("poll failed (errno=%d)\n", errno); usleep(100000); } } else { // Some events occurred. mPendingEventCount = size_t(pollResult); } } // All done, return the number of events we read. return event - buffer; } 

InputRead

  1. InputManager 启动的一个线程主要用于循环去通过EventHub监听事件

InputDispatcher

  1. inputManager 启动的一个线程主要用于循环的派发事件;

InputManager

  1. 主要用来管理InputReader 和 InputDispatcher;
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ #define LOG_TAG "InputManager" //#define LOG_NDEBUG 0 #include "InputManager.h" #include <cutils/log.h> namespace android { 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(); } InputManager::InputManager( const sp<InputReaderInterface>& reader, const sp<InputDispatcherInterface>& dispatcher) : mReader(reader), mDispatcher(dispatcher) { initialize(); } InputManager::~InputManager() { stop(); } void InputManager::initialize() { mReaderThread = new InputReaderThread(mReader); mDispatcherThread = new InputDispatcherThread(mDispatcher); } 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; } status_t InputManager::stop() { status_t result = mReaderThread->requestExitAndWait(); if (result) { ALOGW("Could not stop InputReader thread due to error %d.", result); } result = mDispatcherThread->requestExitAndWait(); if (result) { ALOGW("Could not stop InputDispatcher thread due to error %d.", result); } return OK; } sp<InputReaderInterface> InputManager::getReader() { return mReader; } sp<InputDispatcherInterface> InputManager::getDispatcher() { return mDispatcher; } } // namespace android 

Key

常见debug 工具 或 命令

  1. input 自动化测试常用(命令每次重新执行通过app_process是用来调用java代码的,耗时1到2s ,可以做优化)
  2. dumpsys input 调试常用,可以查看当前按键和触屏情况;
  3. getevent 指令用于获取 input 输入事件,包括了添加和删除设备的事件(注:不影响input 输入事件的正常派发;
  4. 打开开发者选项的命令:
    settings put secure user_setup_complete 1
    settings put global device_provisioned 1
    am start -n com.android.launcher/com.android.launcher2.Launcher
  5. 控制log的相关命令:
    关闭所有log: setprop persist.log.tag S
    打开指定TAG的log: setprop persist.log.tag.TAG V
  6. kill -3 PID命令获取java应用堆栈信息,一般可以用来调查APP无法接受按键和触屏事件的内部原因
    注:关于getevent的更多信息;
android:/ # getevent -h  Usage: getevent [-t] [-n] [-s switchmask] [-S] [-v [mask]] [-d] [-p] [-i] [-l] [-q] [-c count] [-r] [device] -t: show time stamps #加上事件戳 -n: don't print newlines #一行输出,但是存在输出缓冲区 -s: print switch states for given bits #-s:显示指定位的开关状态 -S: print all switch states #显示所有位的开关状态 -v: verbosity mask (errs=1, dev=2, name=4, info=8, vers=16, pos. events=32, props=64) #根据 mask 值掩码计算,显示相关信息,执行后会一直显示上报数据 -d: show HID descriptor, if available #如果设备可用,显示设备隐藏的描述信息 -p: show possible events (errs, dev, name, pos. events) #显示设备支持的事件类型和编码方式 -i: show all device info and possible events #显示设备的所有信息和支持的事件; -l: label event types and names in plain text #以文本形式记录事件类型和名称,比 -t 更清楚直观 -q: quiet (clear verbosity mask) #清除掩码,输出完整设备信息 -c: print given number of events then exit #输出指定数量的事件就推出 -r: print rate events are received #输出事件接收速率,设备支持的能力; 

Android 框架Key原理

dumpsys input: 更多内容可以浏览:https://blog.csdn.net/qq_34262886/article/details/107860216

Event Hub State:
  BuiltInKeyboardId: 5
  Devices:
    -1: Virtual
      Classes: 0x40000023
      Path: <virtual> Enabled: true Descriptor: a718a782d34bc767f4689c232d64d527998ea7fd
      Location:
      ControllerNumber: 0
      UniqueId: <virtual> Identifier: bus=0x0000, vendor=0x0000, product=0x0000, version=0x0000
      KeyLayoutFile: /system/usr/keylayout/Generic.kl
      KeyCharacterMapFile: /system/usr/keychars/Virtual.kcm
      ConfigurationFile:
      HaveKeyboardLayoutOverlay: false Input Reader State:
  Device -1: Virtual
    Generation: 2
    IsExternal: false HasMic: false Sources: 0x00000301
    KeyboardType: 2
    Keyboard Input Mapper:
      Parameters:
        HasAssociatedDisplay: false OrientationAware: false HandlesKeyRepeat: false KeyboardType: 2
      Orientation: 0
      KeyDowns: 0 keys currently down
      MetaState: 0x0
      DownTime: 0 

根据个人的项目经验, KeyLayoutFile条目中看到加载的按键映射文件,都是根据厂商ID和产品ID匹配对应文件的,例如vendor为0x3697,product 为0x0001,那么对应的配置文件为/system/usr/keylayout/Vendor_3697_Product_0001.kl.

具体流程:鄙人图画的丑,借用大神的图镇塔;(转:wbo4958
:https://www.jianshu.com/p/2bff4ecd86c9)
安卓输入事件的一般流程(常见key和touch需求分析)

常见需求

  1. 增加按键设备;
  2. 对按键进行优先级管理;
  3. 组合按键;
  4. 按键派发以及响应问题的调查等;
  5. 特殊模式下(例如recovery模式)如何使能自定义或新增按键的派发;
  6. 利用input原理的应用:
    1. 修改安卓原始脚本,加快自动化测试的速度;
    2. 简单的市场应用,比如连点器.

Touch

常见debug 工具

  1. input 自动化测试常用(命令每次重新执行通过app_process是用来调用java代码的,耗时1到2s ,可以做优化)
  2. dumpsys input 调试常用,可以查看当前按键和触屏情况,以及FocusedApplication和FocusedWindow;
  3. getevent 指令用于获取 input 输入事件,包括了添加和删除设备的事件(注:不影响input 输入事件的正常派发;
  4. 打开开发者选项的命令:
    settings put secure user_setup_complete 1
    settings put global device_provisioned 1
    am start -n com.android.launcher/com.android.launcher2.Launcher
  5. 控制log的相关命令:
    关闭所有log: setprop persist.log.tag S
    打开指定TAG的log: setprop persist.log.tag.TAG V
  6. kill -3 PID命令获取java应用堆栈信息,一般可以用来调查APP无法接受按键和触屏事件的内部原因
    注:关于getevent的更多信息;

Android 框架Touch原理

常见需求

  1. Android 窗口无法正常接收触屏事件;(Android 对触屏事件的派发机制;)
  2. 画面迁移过程中 事件如何处理;
  3. 单点,多点的支持问题;
  4. 从接收事件inputReader到应用响应事件的过程中都可以添加手势识别的接口(Android 官网)

期待

希望大家遇到问题都能及时地解决,不管是自我发现还是他人提醒,总之最终的结果都是一样的。

本文地址:https://blog.csdn.net/qq_34262886/article/details/107814976