網站首頁 編程語言 正文
InputReader 的創建
從 InputManagerService: 創建與啟動 可知,Input 系統的主要功能,主要集中在 native 層,并且Input 系統的 native 層又包含 InputReader, InputClassifer, InputDispatcher 三個子模塊。本文來分析 InputReader 從創建到啟動的基本流程,為后續分析 InputReader 的每一個功能打好基礎。
從 InputManagerService: 創建與啟動 可知, InputReader 的創建過程如下
// InputReaderFactory.cpp
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) {
return new InputReader(std::make_unique<EventHub>(), policy, listener);
}
InputReader 依賴 EventHub,因此首先要看下 EventHub 的創建過程
EventHub::EventHub(void)
: mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
mNextDeviceId(1),
mControllerNumbers(),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false),
mNeedToScanDevices(true), // mNeedToScanDevices 初始化為 true,表示需要掃描輸入設備
mPendingEventCount(0),
mPendingEventIndex(0),
mPendingINotify(false) {
ensureProcessCanBlockSuspend();
// 1. 創建 epoll
mEpollFd = epoll_create1(EPOLL_CLOEXEC);
// 2. 初始化 inotify
mINotifyFd = inotify_init();
// 監聽 /dev/input/ 目錄項的創建與刪除,其實就是監聽輸入設備的創建與刪除
mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
// ...
// 3. epoll 監聽 inotify 事件
// 可讀事件,表明有輸入設備的創建與刪除
struct epoll_event eventItem = {};
eventItem.events = EPOLLIN | EPOLLWAKEUP;
eventItem.data.fd = mINotifyFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
// 4. 創建管道
int wakeFds[2];
result = pipe(wakeFds);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
// 設置管道兩端為非阻塞
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
// 5. epoll 監聽管道讀端的事件
// 可讀事件,表明需要喚醒 InputReader 線程,觸發條件一般為配置更新
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
EventHub 創建過程如下
- 創建 epoll 實例。
- 初始化 inotify 實例,并用 epoll 監聽它的事件。當輸入設備添加/刪除時,epoll 就會收到 inotify 的可讀事件,因此 EventHub 和 InputReader 就可以動態地處理輸入設備的添加/刪除。
- 創建管道。
- epoll 監聽管道的讀端的事件。當配置更新時,會向管道的寫端寫入數據,epoll 就會收到管道的可讀事件,如果此時 InputReader 線程處于休眠狀態,那么 InputReader 將被喚醒來處于配置更新。
epoll, inotify, pipe,它們的作用和使用方式,請讀者自行查閱 Unix/Linux 資料。
現在讓我們繼續看下 InputReader 的創建過程
InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener)
: mContext(this), // mContext 代表 InputReader 的環境
mEventHub(eventHub),
mPolicy(policy),
mGlobalMetaState(0),
mLedMetaState(AMETA_NUM_LOCK_ON),
mGeneration(1),
mNextInputDeviceId(END_RESERVED_ID),
mDisableVirtualKeysTimeout(LLONG_MIN),
mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
// InputReader 會把加工后的事件添加到 QueuedInputListener 隊列中,之后一起分發給 InputClassifier
mQueuedListener = new QueuedInputListener(listener);
{ // acquire lock
std::scoped_lock _l(mLock);
// 刷新配置
// 其實就是更新 InputReader::mConfig
refreshConfigurationLocked(0);
// 更新 InputReader::mGlobalMetaState
// 與鍵盤輸入設備的meta按鍵相關
updateGlobalMetaStateLocked();
} // release lock
}
InputReader 的構造函數很簡單,就是成員變量的初始化。其中需要重點看下 refreshConfigurationLocked(0) 是如何刷新 InputReader 配置
// 注意,此時參數 changes 為 0
void InputReader::refreshConfigurationLocked(uint32_t changes) {
// 通過 InputReaderPolicyInterface 獲取配置,保存到 InputReader::mConfig 中
mPolicy->getReaderConfiguration(&mConfig);
// EventHub 保存排除的設備
mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
if (!changes) return;
// ...
}
原來 InputReader::mConfig 代表的就是 InputReader 的配置,并且是通過 InputReaderPolicyInterface mPolicy 獲取配置的。
從 InputManagerService: 創建與啟動 可知,InputReaderPolicyInterface 接口的實現者是 NativeInputManager ,而 NativeInputManager 是 Input 系統的上層與底層溝通的橋梁,因此 InputReader 必定是通過 NativeInputManager 向上層獲取配置
void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
// 1. 通過JNI,向上層 InputManagerService 獲取配置,并保存到 outConfig 中
jint virtualKeyQuietTime = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getVirtualKeyQuietTimeMillis);
if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {
outConfig->virtualKeyQuietTime = milliseconds_to_nanoseconds(virtualKeyQuietTime);
}
outConfig->excludedDeviceNames.clear();
jobjectArray excludedDeviceNames = jobjectArray(env->CallStaticObjectMethod(
gServiceClassInfo.clazz, gServiceClassInfo.getExcludedDeviceNames));
if (!checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && excludedDeviceNames) {
jsize length = env->GetArrayLength(excludedDeviceNames);
for (jsize i = 0; i < length; i++) {
std::string deviceName = getStringElementFromJavaArray(env, excludedDeviceNames, i);
outConfig->excludedDeviceNames.push_back(deviceName);
}
env->DeleteLocalRef(excludedDeviceNames);
}
// Associations between input ports and display ports
// The java method packs the information in the following manner:
// Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
// Received data: ['inputPort1', '1', 'inputPort2', '2']
// So we unpack accordingly here.
outConfig->portAssociations.clear();
jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
gServiceClassInfo.getInputPortAssociations));
if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
jsize length = env->GetArrayLength(portAssociations);
for (jsize i = 0; i < length / 2; i++) {
std::string inputPort = getStringElementFromJavaArray(env, portAssociations, 2 * i);
std::string displayPortStr =
getStringElementFromJavaArray(env, portAssociations, 2 * i + 1);
uint8_t displayPort;
// Should already have been validated earlier, but do it here for safety.
bool success = ParseUint(displayPortStr, &displayPort);
if (!success) {
ALOGE("Could not parse entry in port configuration file, received: %s",
displayPortStr.c_str());
continue;
}
outConfig->portAssociations.insert({inputPort, displayPort});
}
env->DeleteLocalRef(portAssociations);
}
outConfig->uniqueIdAssociations.clear();
jobjectArray uniqueIdAssociations = jobjectArray(
env->CallObjectMethod(mServiceObj, gServiceClassInfo.getInputUniqueIdAssociations));
if (!checkAndClearExceptionFromCallback(env, "getInputUniqueIdAssociations") &&
uniqueIdAssociations) {
jsize length = env->GetArrayLength(uniqueIdAssociations);
for (jsize i = 0; i < length / 2; i++) {
std::string inputDeviceUniqueId =
getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i);
std::string displayUniqueId =
getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i + 1);
outConfig->uniqueIdAssociations.insert({inputDeviceUniqueId, displayUniqueId});
}
env->DeleteLocalRef(uniqueIdAssociations);
}
jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getHoverTapTimeout);
if (!checkAndClearExceptionFromCallback(env, "getHoverTapTimeout")) {
jint doubleTapTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getDoubleTapTimeout);
if (!checkAndClearExceptionFromCallback(env, "getDoubleTapTimeout")) {
jint longPressTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getLongPressTimeout);
if (!checkAndClearExceptionFromCallback(env, "getLongPressTimeout")) {
outConfig->pointerGestureTapInterval = milliseconds_to_nanoseconds(hoverTapTimeout);
// We must ensure that the tap-drag interval is significantly shorter than
// the long-press timeout because the tap is held down for the entire duration
// of the double-tap timeout.
jint tapDragInterval = max(min(longPressTimeout - 100,
doubleTapTimeout), hoverTapTimeout);
outConfig->pointerGestureTapDragInterval =
milliseconds_to_nanoseconds(tapDragInterval);
}
}
}
jint hoverTapSlop = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getHoverTapSlop);
if (!checkAndClearExceptionFromCallback(env, "getHoverTapSlop")) {
outConfig->pointerGestureTapSlop = hoverTapSlop;
}
// 2. 從 NativeInputManager::mLocked 更新配置,保存到 outConfig 中
// NativeInputManager::mLocked 的數據是上層經由 InputManagerService 傳入的
{ // acquire lock
AutoMutex _l(mLock);
outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
* POINTER_SPEED_EXPONENT);
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
outConfig->showTouches = mLocked.showTouches;
outConfig->pointerCapture = mLocked.pointerCapture;
outConfig->setDisplayViewports(mLocked.viewports);
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
outConfig->disabledDevices = mLocked.disabledInputDevices;
} // release lock
}
從整體看,獲取 InputReader 配置的方式有兩種
- 通過 JNI 向上層的 InputManagerService 獲取配置。
- 從 NativeInputManager::mLocked 獲取配置。
從 InputManagerService: 創建與啟動 可知,NativeInputManager::mLocked 是在 NativeInputManager 的構造函數中進行初始化的,但是它并不是不變的,而是上層經由 InputManagerService 進行操控的。
例如,mLocked.showTouches 對應開發者模式下的 Show taps 功能,InputManagerService 會監聽這個開關的狀態,相應地改變 mLocked.showTouches,并且會通知 InputReader 配置改變了,InputReader 在處理配置改變的過程時,會重新獲取 mLocked.showTouches 這個配置。
有 一部分 的配置是可以通過 adb shell dumpsys input 命令進行查看的
Input Manager State: Interactive: true System UI Lights Out: false Pointer Speed: 0 Pointer Gestures Enabled: true Show Touches: false Pointer Capture: Disabled, seq=0
而另外一部分配置,由于會對輸入設備進行配置,因此可以從 dump 出的輸入設備的信息中查看。
InputReader 的運行
從 InputManagerService: 創建與啟動 可知,InputReader 通過線程,循環調用 InputReader::loopOnce() 執行任務
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
{ // acquire lock
std::scoped_lock _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
// 1. 如果配置有改變,那么就刷新配置
uint32_t changes = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
// 刷新配置
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
// 2. 從 EventHub 獲取事件
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
std::scoped_lock _l(mLock);
mReaderIsAliveCondition.notify_all();
// 3. 處理事件
if (count) {
processEventsLocked(mEventBuffer, count);
}
if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
mNextTimeout = LLONG_MAX;
timeoutExpiredLocked(now);
}
}
// 4. 處理輸入設備改變
// 4.1 輸入設備改變,重新獲取輸入設備信息
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
inputDevices = getInputDevicesLocked();
}
} // release lock
// 4.2 通知監聽者,輸入設備改變了
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
// 5. 刷新隊列中緩存的事件
// 其實就是把事件分發給 InputClassifier
mQueuedListener->flush();
}
InputReader 所做的事情如下
- 如果配置改變了,那么就更新配置。
- 從 EventHub 獲取事件,并處理獲取到的事件。在處理事件的過程中,InputReader 會對事件進行加工,然后保存到 QueuedInputListener 緩存隊列中。
- 如果設備發生改變,那么重新獲取新的設備信息,并通知監聽者。
- QueuedInputListener 刷新緩存的事件,其實就是把 InputReader 加工后的事件分發給 InputClassifer。
EventHub 提供事件
InputReader 的本質就是處理從 EventHub 獲取的事件,然后分發給下一環。因為我們必須了解 EventHub::getEvents() 是如何為 InputReader 提供事件的
// EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
ALOG_ASSERT(bufferSize >= 1);
std::scoped_lock _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) {
// ...
}
// Report any devices that had last been added/removed.
for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
// ...
}
// 掃描輸入設備
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
// 為掃描后打開的每一個輸入設備,填充一個類型為 DEVICE_ADDED 的事件
while (!mOpeningDevices.empty()) {
std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
mOpeningDevices.pop_back();
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
event->type = DEVICE_ADDED;
event += 1;
// Try to find a matching video device by comparing device names
for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end();
it++) {
// ...
}
// 每次填充完事件,就把設備 Device 保存到 mDevices 中
auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
if (!inserted) {
ALOGW("Device id %d exists, replaced.", device->id);
}
// 表明你需要發送設備掃描完成事件
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;
// 處理 epoll 事件
while (mPendingEventIndex < mPendingEventCount) {
// 處理 inotify 事件,表明輸入設備新增或者刪除
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
if (eventItem.data.fd == mINotifyFd) {
if (eventItem.events & EPOLLIN) {
mPendingINotify = true;
} else {
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
}
continue;
}
// 處理管道事件,這是用來喚醒 InputReader 線程
if (eventItem.data.fd == mWakeReadPipeFd) {
if (eventItem.events & EPOLLIN) {
ALOGV("awoken after wake()");
awoken = true;
char wakeReadBuffer[16];
ssize_t nRead;
do {
nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer));
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer));
} else {
ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
eventItem.events);
}
continue;
}
// 接下來是處理設備的輸入事件
Device* device = getDeviceByFdLocked(eventItem.data.fd);
if (device == nullptr) {
continue;
}
if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) {
// ...
}
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];
event->when = processEventTimestamp(iev);
event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);
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.c_str());
deviceChanged = true;
closeDeviceLocked(*device);
} else {
ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
device->identifier.name.c_str());
}
}
// 處理設備改變
// mPendingEventIndex >= mPendingEventCount 表示處理完所有的輸入事件后,再處理設備的改變
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
mPendingINotify = false;
readNotifyLocked();
deviceChanged = true;
}
// 設備發生改變,那么跳過當前循環,在下一個循環的開頭處理設備改變
if (deviceChanged) {
continue;
}
// 如果有事件,或者被喚醒,那么終止循環,接下來 InputReader 會處理事件或者更新配置
if (event != buffer || awoken) {
break;
}
mPendingEventIndex = 0;
mLock.unlock(); // release lock before poll
// 此時沒有事件,并且也沒有被喚醒,那么超時等待 epoll 事件
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mLock.lock(); // reacquire lock after poll
if (pollResult == 0) {
// 處理超時...
}
if (pollResult < 0) {
// 處理錯誤...
} else {
// 保存待處理事件的數量
mPendingEventCount = size_t(pollResult);
}
}
// 返回事件的數量
return event - buffer;
}
EventHub::getEvent() 提供事件的過程很長,但是現在我們不必去了解所有的細節,我們要有從整體看局部的眼光。EventHub 其實只生成了兩類事件
- 設備的添加/刪除事件。這種事件不是通過操作設備而產生的,系統稱之為合成事件。
- 輸入事件。這種事件是通過操作設備產生的,例如手指在觸摸屏上滑動,系統稱之為元輸入事件。
看來我們得分兩部分來分析這兩類事件的生成以及處理過程,因此下一篇文章,我們分析合成事件的生成以及處理過程。
原文鏈接:https://juejin.cn/post/7164221690203865119
相關推薦
- 2022-08-13 二分查找思路及模板
- 2022-03-16 linux下FastDFS搭建圖片服務器_Linux
- 2022-11-18 Shell實現批量操作文件的方法詳解_linux shell
- 2022-11-07 React組件封裝中三大核心屬性詳細介紹_React
- 2023-10-28 如何給k8s集群里的資源打標簽_云其它
- 2022-07-22 mybatis源碼之集成spring原理詳解
- 2021-12-06 CentOS環境使用NFS遠程目錄掛載過程介紹_Linux
- 2022-04-16 C#基于Socket實現多人聊天功能_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支