【實習週記】Android getevent.c源碼分析

【實習週記】Android getevent.c源碼分析

一.概述

getevent和sendevent是Android系統下的兩個工具
在cmd 命令行下輸入adb shell進入Android設備的shell
輸入getevent ,獲取當前設備的事件
輸入sendevent ,模擬設備產生事件
這兩個命令的源碼在Android系統system/core/toolbox/目錄下,
名字分別爲getevent.c和sendevent.c,屬於Android底層代碼

二.Android輸入系統

1.輸入設備和輸入事件

提到輸入設備,我們能想到的有觸摸屏和鍵盤,實際上Android所支持的輸入設備不僅這兩種,包括鼠標,手柄和一些傳感器都屬於輸入設備。
當輸入設備可用時,Linux內核會在/dev/input/目錄下爲設備創建名爲event0~n或其他名稱的設備節點。當輸入設備不可用時,則會將對應的節點刪除。在用戶空間可以通過ioctl()函數從這些設備節點中獲取其對應的輸入設備的類型、廠商、描述等信息。

例如:進入adb shell 輸入getevent
將打印如下相關設備的信息
add device 1: /dev/input/event4
name: “proximity_sensor” //距離傳感器
add device 2: /dev/input/event3
name: “accelerometer_sensor” //加速度傳感器
add device 3: /dev/input/event1
name: “meta_event” //
add device 4: /dev/input/event0
name: “qpnp_pon” //power按鍵
could not get driver version for /dev/input/mice, Not a typewriter
add device 5: /dev/input/event5
name: “gpio-keys” //鍵盤
add device 6: /dev/input/event2
name: “sec_touchkey” //虛擬按鍵
could not get driver version for /dev/input/mouse0, Not a typewriter
add device 7: /dev/input/event6
name: “sec_touchscreen” //觸摸屏

Android輸入事件的產生:當用戶操作輸入設備時,Linux內核接收到相應的硬件中斷,並將中斷加工成原始的輸入事件數據,寫入對應的設備節點中,在用戶空間可以通過read()函數將事件讀出。
Android輸入系統的工作原理:監控/dev/input/下的所有設備節點,當某個節點有數據可讀時,將數據讀出並進行相應的翻譯加工,在所有的窗口中尋找合適的事件接收者,並派發給它。

2.Android輸入系統的工作流程

在這裏插入圖片描述

(1).組成&功能:

Linux內核:接收輸入設備中斷,將原始事件數據寫入設備節點。

設備節點:連接內核和IMS

InputManagerService(IMS):Android系統服務,分爲Java層和Native層兩部分。Java層負責與WMS的通信。Native層是InputReader和InputDispatcher兩個輸入系統關鍵組件的運行容器。

EventHub:直接訪問所有的設備節點,通過名爲getEvents()的函數將所有輸入系統相關的待處理的底層事件返回給使用者。這些事件包括原始輸入事件、設備節點的增刪等。

InputReader:IMS中的關鍵組件之一。它運行於一個獨立的線程中,負責管理輸入設備的列表與配置,同時對輸入事件進行加工處理。它通過其線程循環不斷地通getEvents()函數從EventHub中將事件取出並進行處理。對於設備節點的增刪事件,它會更新輸入設備列表與配置。對於原始輸入事件,InputReader對其進行翻譯、組裝、封裝爲包含了更多信息、更具可讀性的輸入事件,然後交給InputDispatcher進行派發。

InputReaderPolicy:爲InputReader的事件加工處理提供一些策略配置。

InputDispatcher:IMS中的另一個關鍵組件。它也運行於一個獨立的線程中。
InputDispatcher中保管了來自WMS的所有窗口的信息,其收到來自InputReader的輸入事件後,會在其保管的窗口中尋找合適的窗口,並將事件派發給此窗口。

InputDispatcherPolicy:爲InputDispatcher的派發過程提供策略控制。例如截取某些特定的輸入事件用作特殊用途,或者阻止將某些事件派發給目標窗口。eg:home鍵被InputDispatcherPolicy截取到PhoneWindowManager中進行處理,並阻止窗口收到HOME鍵按下的事件。

WindowManagerSevice(WMS):當新建窗口時,WMS爲新窗口和IMS創建了事件傳遞所用的通道。WMS將所有窗口的信息,包括窗口的可點擊區域,焦點窗口等,實時地更新到IMS的InputDispatcher中,使InputDispatcher可以正確地將事件派發到指定的窗口。

ViewRootImpl:應用層事件分發的入口,將窗口所接收到的輸入事件沿着控件樹派發給相應的控件。

(2).工作流程:

內核將原始的事件寫入設備節點中,InputReader通過EventHub將原始事件取出來並翻譯加工成Android輸入事件,然後交給InputDispatcherInputDispatcher根據WMS提供的窗口信息將事件分發給合適的窗口。窗口對應的ViewRootImpl對象沿着控件樹將事件分發給相應控件。最終,控件對接收的事件進行響應。

三.getevent.c源碼分析

1.基礎知識

(1).INotify

INotify是一個Linux內核所提供的一種文件系統變化通知機制。它可以爲應用程序監控文件系統的變化,如文件的新建、刪除、讀寫等。INotify機制有兩個基本對象,分別爲
inotify對象watch對象,都使用文件描述符表示。

inotify對象對應了一個隊列,應用程序可以向inotify對象添加多個監聽。當被監聽的事
件發生時,可以通過read()函數從inotify對象中將事件信息讀取出來。

inotify對象的創建int inotifyFd = notify_init();

watch對象用來描述文件系統的變化事件的監聽。它是一個二元組,包括監聽目標和事件
掩碼兩個元素。監聽目標是文件系統的一個路徑,可以是文件也可以是文件夾。而事件掩
碼則表示了需要需要監聽的事件類型,掩碼中的每一位代表一種事件。可以監聽的事件種
類很多,其中就包括文件的創建(IN_CREATE)與刪除(IN_DELETE)。

watch對象的創建
int wd = inotify_add_watch(inotifyFd,”/dev/input”,IN_CREATE | IN_DELETE);
創建完上面的watch後,當/dev/input/下的設備節點發生創建和刪除操作時,都會將相應
的事件寫入notifyFd所描述的inotify對象中。

事件信息的讀取size_t len = read(inotifyFd,events_buf,BUF_LEN);
events_buf是inotify_event的數組指針,讀取事件的數量取決於數組的長度,BUF_LEN
爲events_buf的長度。
struct inotify_event{
__s32 wd; //事件對應watch對象描述符
__u32 mask; //事件類型 eg:IN_CREATE
__u32 cookie;
__u32 len; //name字段的長度
char name[0]; //存儲產生此事件的文件路徑
}
通過INotify機制避免了輪詢文件系統的麻煩,但是還有一個問題,INotify機制並不是通
過回調的方式通知事件,而需要使用者主動從inotify對象中進行事件讀取。

(2).poll

poll是監控文件是否可讀的一種機制,poll機制會判斷fds中的文件是否可讀,如果可讀
則會立即返回,返回的值就是可讀fd的數量,如果不可讀,那麼就進程就會休眠timeout
這麼長的時間,然後再來判斷是否有文件可讀,如果有,返回fd的數量,如果沒有,則
返回0。
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
參數說明:
typedef struct pollfd {
int fd; /* 需要被檢測或選擇的文件描述符*/
short events; /* 對文件描述符fd上感興趣的事件 /
short revents; /
文件描述符fd上當前實際發生的事件*/
} ;
事件類型:
POLLIN 有數據可讀
POLLRDNORM 有普通數據可讀
POLLRDBAND 有優先數據可讀
POLLPRI 有緊迫數據可讀
POLLOUT 寫數據不會導致阻塞
POLLWRNORM 寫普通數據不會導致阻塞
POLLWRBAND 寫優先數據不會導致阻塞
POLLMSG SIGPOLL消息可用
POLLER 指定的文件描述符發生錯誤
POLLHUP 指定的文件描述符掛起事件
POLLNVAL 指定的文件描述符非法

typedef unsigned long nfds_t;/* fds中的結構體元素的總數量*/

timeout:poll函數調用阻塞的事件,單位毫秒,小於0時一直阻塞。

返回值說明:

0:fds中準備好讀,寫或出錯的描述符的數量
=0:fds中沒有準備好讀,寫或出錯的描述符的數量,此時poll超時,超時事件爲timeout
=-1:poll函數調用失敗,自動設置全局變量errno。

(2).Ioctl

ioctl是設備驅動程序中對設備的I/O通道進行管理的函數。
int ioctl(int fd, int cmd, …);
參數說明:
fd:用戶程序打開設備時使用open函數返回的文件標示符,
cmd:是用戶程序對設備的控制命令,
            Linux核心定義的命令碼(cmd):
            設備類型:8bit 序列號:8bit方向:2bit 數據尺寸:8~14bit
…:補充參數,一般最多一個,這個參數的有無和cmd的意義相關。

2.getevent指令

在adb shell下輸入getevent,會爲我們返回未加工過的事件信息。
格式:事件產生設備(device)    事件類型(type)    事件代碼(code)    事件值(value)
             type,code,value都爲十六進制。
例如:/dev/input/event3: 0002 0001 ffffff5b

3.getevent.c源碼分析

(1).程序執行入口:getevent_main(int argc, char *argv[]);
(2).通過getopt(argc, argv, "tns:Sv::dpilqc:rh")對參數進行解析。
(3).創建inotify對象,返回的操作符存儲在ufds中設置ufds中監聽的事件爲有數據可讀。
     ufds[0].fd = inotify_init();
     ufds[0].events = POLLIN;
(4). 爲inotify對象設置watch對象,監聽/dev/input目錄下文件的創建和刪除
     res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE);
(5).在while循環中調用poll函數阻塞等待事件的產生
     poll(ufds, nfds, -1);
(6).當實際發生的事件爲有數據可讀時,調用read_notify()函數
     if(ufds[0].revents & POLLIN) {
        read_notify(device_path, ufds[0].fd, print_flags);
     }
read_notify()函數內部調用 open_device()函數對輸入設備device進行增加或者調用close_device()函數對輸入設備device進行刪除,open_device()函數和close_device()函數內部通過ioctl()函數來對輸入設備進行控制。
(7).若實際發生的事件爲有數據可讀,讀取,存入input_event的對象event中。
     res = read(ufds[i].fd, &event, sizeof(event));
(8).調用print_event()函數打印event
     print_event(event.type, event.code, event.value, print_flags);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章