<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
從 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); }
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 設定的方式有兩種
從 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 出的輸入裝置的資訊中檢視。
從 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 所做的事情如下
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 其實只生成了兩類事件
看來我們得分兩部分來分析這兩類事件的生成以及處理過程,因此下一篇文章,我們分析合成事件的生成以及處理過程。
以上就是Input系統之InputReader概要性範例分析的詳細內容,更多關於Input系統InputReader概要性的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45