EventHub與設備、Input事件的交互

關於EventHub的學習有一點前置知識很重要,就是epoll和inotify機制,可以參考這篇博文:http://www.cheelok.com/_inx/88。

在前面Input系列的博文中已經學習,來自底層的Input事件經過事件樞紐EventHub處理後,讓InputReader通過epoll和inotify從EventHub中讀取。那麼現在就來學習EventHub是如何讀取底層事件的吧。

EventHub註冊設備節點的監聽

EventHub::EventHub(void) :n mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),n mOpeningDevices(0), mClosingDevices(0),n mNeedToSendFinishedDeviceScan(false),n mNeedToReopenDevices(false), mNeedToScanDevices(true),n mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {n acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);nn // 創建epoll對象用於監聽是否有可讀事件,指定最大監聽個數為EPOLL_SIZE_HINTn mEpollFd = epoll_create(EPOLL_SIZE_HINT);n LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);nn // 創建inotify對象用於監聽設備節點DEVICE_PATH,即/dev/input,是否有變化(設備增刪),設備的增刪對應著設備節點的文件增刪n mINotifyFd = inotify_init();n int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);n LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d",n DEVICE_PATH, errno);nn struct epoll_event eventItem;n memset(&eventItem, 0, sizeof(eventItem));neventItem.events = EPOLLIN;n eventItem.data.u32 = EPOLL_ID_INOTIFY;n // 將inotify對象註冊到epoll中監聽是否有新的可讀的設備增刪事件n result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);n LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);nn // 創建管道,並將讀端交給epoll,喚醒端(寫端)交給InputReader,用於喚醒epoll,避免epoll阻塞在epoll_wait()中n int wakeFds[2];n result = pipe(wakeFds);n LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);nn mWakeReadPipeFd = wakeFds[0];n mWakeWritePipeFd = wakeFds[1];nn result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);n LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",n errno);nn result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);n LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",n errno);nn eventItem.data.u32 = EPOLL_ID_WAKE;n result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);n LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",n errno);nn int major, minor;n getLinuxRelease(&major, &minor);n // EPOLLWAKEUP was introduced in kernel 3.5n mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);n}n

總結下來就是這張圖:

![](cheelok.com/wp-content/)

EventHub->getEvents

按照上面的學習,我們知道:EventHub在創建時,創建了兩個Fd,mEpollFd和mINotifyFd。其中mINotifyFd用於監聽設備節點是否有設備文件的增刪,將mINotifyFd註冊到mEpollFd中,當發生新的設備增刪,設備節點下的設備文件也會隨之增刪,就會通知mEpollFd有新的可讀的設備增刪事件,通知EventHub對設備進行處理。

換言之,剛創建EventHub時,mEpollFd只監聽了mINotifyFd。

getEvents整個函數比較長,我就不貼所有代碼了,這個函數主要做了以下事情:

  1. 創建一個大小為bufferSize的input_event的緩衝區,用於存儲讀取的Input事件
  2. 判斷是否需要重新打開Input設備
  3. 處理最後被添加/刪除的Input設備,其中會為了處理添加的設備而進行設備掃描
  4. 判斷是否需要掃描設備
  5. 獲取Input事件
  6. 打開/關閉在步驟3中添加/刪除的Input設備
  7. 如果設備信息發生變化,則通知
  8. 喚醒epoll_wait,通知InputReader讀取事件,需要注意的是,整個喚醒過程都是加鎖的

對於我們來說,學習重點自然是步驟4了,但是在看它做了什麼之前不妨先想想,創建EventHub之後,第一次getEvents時,mEpollFd只監聽了mINotifyFd,那mEpollFd要怎麼獲取設備上的發生的Input事件呢?

掃描設備

我們在getEvents裡面可以看到,在獲取Input事件之前,會判斷是否需要掃描設備:

if (mNeedToScanDevices) {n mNeedToScanDevices = false;n scanDevicesLocked();n mNeedToSendFinishedDeviceScan = true;n}n

mNeedToScanDevices在創建EventHub時是默認賦值為true的,那麼第一次走getEvents肯定會走進來,scanDevicesLocked最終會調用scanDirLocked掃描/dev/input。在這個函數裡面就是循環掃描/dev/input下的設備文件了:

status_t EventHub::scanDirLocked(const char *dirname)n{n ……n dir = opendir(dirname);n ……n while((de = readdir(dir))) {n ……n openDeviceLocked(devname);n }n ……n}n

看起來掃描到設備文件後就會openDeviceLocked,在這個函數里其實沒做什麼特別的事情,就是添加設備以及各種處理,有一個地方需要關注下:

// Register with epoll.nstruct epoll_event eventItem;nmemset(&eventItem, 0, sizeof(eventItem));neventItem.events = EPOLLIN;nif (mUsingEpollWakeup) {n eventItem.events |= EPOLLWAKEUP;n}neventItem.data.u32 = deviceId;nif (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {n ALOGE("Could not add device fd to epoll instance. errno=%d", errno);n delete device;n return -1;n}nnString8 wakeMechanism("EPOLLWAKEUP");nif (!mUsingEpollWakeup) {n#ifndef EVIOCSSUSPENDBLOCKn // uapi headers dont include EVIOCSSUSPENDBLOCK, and future kernelsn // will use an epoll flag instead, so as long as we want to supportn // this feature, we need to be prepared to define the ioctl ourselves.n#define EVIOCSSUSPENDBLOCK _IOW(E, 0x91, int)n#endifn if (ioctl(fd, EVIOCSSUSPENDBLOCK, 1)) {n wakeMechanism = "<none>";n } else {n wakeMechanism = "EVIOCSSUSPENDBLOCK";n }n}n

這裡我們將新設備的fd註冊到mEpollFd中進行監聽,並且寫入E喚醒epoll。於是前面的問題就解決了,由於每次獲取Input事件前都會更新設備信息,因此mEpollFd能監聽到最新的設備fd。

獲取Input事件

獲取Input事件的整個流程代碼很多,我就不貼了,主要做了以下事情:

  1. 循環讀取mPendingEventItems中的eventItem
  2. 判斷eventItem是否為合法的inotify類型(eventItem.data.u32為EPOLL_ID_INOTIFY,且eventItem.events & EPOLLIN為true),如果是,說明有新的設備增刪事件,則需要更新設備列表信息
  3. 判斷eventItem是否為合法的喚醒管道讀端的事件(eventItem.data.u32為EPOLL_ID_WAKE),若合法,則喚醒管道的讀端,也就是InputReader
  4. 判斷eventItem的接收設備是否合法
  5. 如果eventItem不屬於EPOLL_ID_INOTIFY、EPOLL_ID_WAKE類型,且是epoll的輸入事件(EPOLLIN),則開始事件讀取的邏輯

事件讀取的邏輯里做了以下事情:

  1. 首先通過設備的readSize判斷設備是否被移除、設備讀取的事件總大小是否為input_event倍數(是否符合讀取格式)、readSize是否合法,通過以上檢查則獲取設備id
  2. 循環讀取readBuffer中的input_event
  3. 對input_event的發生時間進行處理,存儲到RawEvent的when中
  4. 將deviceId、type、code、value封裝到RawEvent中

於是EventHub就成功地得到了來自設備的事件,並成功將它們轉化為RawEvent,交給InputReader。

至此,本文結束。

題外話

如果你喜歡看我寫的技術文的話,可以關注我的公眾號:Cheelok的自留地喔。

weixin.qq.com/r/EyguNmH (二維碼自動識別)


推薦閱讀:

如何刷原生AOSP系統,網上搜到的多是指CM,能否直接刷AOSP,需要自己編譯嗎,還是哪裡可以下載?
極端原生粉、MD 粉是否妨礙了 Android 開放性的發展?
mac編譯aosp的配置需求?

TAG:Android | AOSP | Android系统源码 |