#### 1.INotify介紹與使用
INotify是一個Linux內核所提供的一種文件系統變化通知機制。它可以為應用程序監控文件系統的變化,如文件的新建、刪除、讀寫等。INotify機制有兩個基本對象,分別為inotify對象與watch對象,都使用文件描述符表示。
inotify對象對應了一個隊列,應用程序可以向inotify對象添加多個監聽。當被監聽的事件發生時,可以通過read()函數從inotify對象中將事件信息讀取出來。Inotify對象可以通過以下方式創建:
```
int inotifyFd = inotify_init();
```
而watch對象則用來描述文件系統的變化事件的監聽。它是一個二元組,包括監聽目標和事件掩碼兩個元素。監聽目標是文件系統的一個路徑,可以是文件也可以是文件夾。而事件掩碼則表示了需要需要監聽的事件類型,掩碼中的每一位代表一種事件。可以監聽的事件種類很多,其中就包括文件的創建(IN\_CREATE)與刪除(IN\_DELETE)。讀者可以參閱相關資料以了解其他可監聽的事件種類。以下代碼即可將一個用于監聽輸入設備節點的創建與刪除的watch對象添加到inotify對象中:
```
int wd = inotify_add_watch (inotifyFd, “/dev/input”,IN_CREATE | IN_DELETE);
```
完成上述watch對象的添加后,當/dev/input/下的設備節點發生創建與刪除操作時,都會將相應的事件信息寫入到inotifyFd所描述的inotify對象中,此時可以通過read()函數從inotifyFd描述符中將事件信息讀取出來。
事件信息使用結構體inotify\_event進行描述:
```
struct inotify_event {
__s32 wd; /* 事件對應的Watch對象的描述符 */
__u32 mask; /* 事件類型,例如文件被刪除,此處值為IN_DELETE */
__u32 cookie;
__u32 len; /* name字段的長度 */
char name[0]; /* 可變長的字段,用于存儲產生此事件的文件路徑*/
};
```
當沒有監聽事件發生時,可以通過如下方式將一個或多個未讀取的事件信息讀取出來:
```
size_t len = read (inotifyFd, events_buf,BUF_LEN);
```
其中events\_buf是inotify\_event的數組指針,能夠讀取的事件數量由取決于數組的長度。成功讀取事件信息后,便可根據inotify\_event結構體的字段判斷事件類型以及產生事件的文件路徑了。
總結一下INotify機制的使用過程:
- 通過inotify\_init()創建一個inotify對象。
- 通過inotify\_add\_watch將一個或多個監聽添加到inotify對象中。
- 通過read()函數從inotify對象中讀取監聽事件。當沒有新事件發生時,inotify對象中無任何可讀數據。
通過INotify機制避免了輪詢文件系統的麻煩,但是還有一個問題,INotify機制并不是通過回調的方式通知事件,而需要使用者主動從inotify對象中進行事件讀取。那么何時才是讀取的最佳時機呢?這就需要借助Linux的另一個優秀的機制Epoll了。
#### 2.Epoll介紹與使用
無論是從設備節點中獲取原始輸入事件還是從inotify對象中讀取文件系統事件,都面臨一個問題,就是這些事件都是偶發的。也就是說,大部分情況下設備節點、inotify對象這些文件描述符中都是無數據可讀的,同時又希望有事件到來時可以盡快地對事件作出反應。為解決這個問題,我們不希望不斷地輪詢這些描述符,也不希望為每個描述符創建一個單獨的線程進行阻塞時的讀取,因為這都將會導致資源的極大浪費。
此時最佳的辦法是使用Epoll機制。Epoll可以使用一次等待監聽多個描述符的可讀/可寫狀態。等待返回時攜帶了可讀的描述符或自定義的數據,使用者可以據此讀取所需的數據后可以再次進入等待。因此不需要為每個描述符創建獨立的線程進行阻塞讀取,避免了資源浪費的同時又可以獲得較快的響應速度。
Epoll機制的接口只有三個函數,十分簡單。
- epoll\_create(int max\_fds):創建一個epoll對象的描述符,之后對epoll的操作均使用這個描述符完成。max\_fds參數表示了此epoll對象可以監聽的描述符的最大數量。
- epoll\_ctl (int epfd, int op,int fd, struct epoll\_event \*event):用于管理注冊事件的函數。這個函數可以增加/刪除/修改事件的注冊。
- int epoll\_wait(int epfd, structepoll\_event \* events, int maxevents, int timeout):用于等待事件的到來。當此函數返回時,events數組參數中將會包含產生事件的文件描述符。
接下來以監控若干描述符可讀事件為例介紹一下epoll的用法。
(1) 創建epoll對象
首先通過epoll\_create()函數創建一個epoll對象:
```
Int epfd = epoll_create(MAX_FDS)
```
(2) 填充epoll\_event結構體
接著為每一個需監控的描述符填充epoll\_event結構體,以描述監控事件,并通過epoll\_ctl()函數將此描述符與epoll\_event結構體注冊進epoll對象。epoll\_event結構體的定義如下:
```
struct epoll_event {
__uint32_tevents; /* 事件掩碼,指明了需要監聽的事件種類*/
epoll_data_t data; /* 使用者自定義的數據,當此事件發生時該數據將原封不動地返回給使用者 */
};
```
epoll\_data\_t聯合體的定義如下,當然,同一時間使用者只能使用一個字段:
```
typedef union epoll_data {
void*ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
```
epoll\_event結構中的events字段是一個事件掩碼,用以指明需要監聽的事件種類,同INotify一樣,掩碼的每一位代表了一種事件。常用的事件有EPOLLIN(可讀),EPOLLOUT(可寫),EPOLLERR(描述符發生錯誤),EPOLLHUP(描述符被掛起)等。更多支持的事件讀者可參考相關資料。
data字段是一個聯合體,它讓使用者可以將一些自定義數據加入到事件通知中,當此事件發生時,用戶設置的data字段將會返回給使用者。在實際使用中常設置epoll\_event.data.fd為需要監聽的文件描述符,事件發生時便可以根據epoll\_event.data.fd得知引發事件的描述符。當然也可以設置epoll\_event.data.fd為其他便于識別的數據。
填充epoll\_event的方法如下:
```
structepoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN | EPOLLERR | EPOLLHUP; // 監聽描述符可讀以及出錯的事件
eventItem.data.fd= listeningFd; // 填寫自定義數據為需要監聽的描述符
```
接下來就可以使用epoll\_ctl()將事件注冊進epoll對象了。epoll\_ctl()的參數有四個:
- epfd是由epoll\_create()函數所創建的epoll對象的描述符。
- op表示了何種操作,包括EPOLL\_CTL\_ADD/DEL/MOD三種,分別表示增加/刪除/修改注冊事件。
- fd表示了需要監聽的描述符。
- event參數是描述了監聽事件的詳細信息的epoll\_event結構體。
注冊方法如下:
```
// 將事件監聽添加到epoll對象中去
result =epoll_ctl(epfd, EPOLL_CTL_ADD, listeningFd, &eventItem);
```
重復這個步驟可以將多個文件描述符的多種事件監聽注冊到epoll對象中。完成了監聽的注冊之后,便可以通過epoll\_wait()函數等待事件的到來了。
(3) 使用epoll\_wait()函數等待事件
epoll\_wait()函數將會使調用者陷入等待狀態,直到其注冊的事件之一發生之后才會返回,并且攜帶了剛剛發生的事件的詳細信息。其簽名如下:
```
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
```
- epfd是由epoll\_create()函數所創建的epoll對象描述符。
- events是一個epoll\_event的數組,此函數返回時,事件的信息將被填充至此。
- maxevents表示此次調用最多可以獲取多少個事件,當然,events參數必須能夠足夠容納這么多事件。
- timeout表示等待超時的事件。
epoll\_wait()函數返回值表示獲取了多少個事件。
4\. 處理事件
epoll\_wait返回后,便可以根據events數組中所保存的所有epoll\_event結構體的events字段與data字段識別事件的類型與來源。
Epoll的使用步驟總結如下:
- 通過epoll\_create()創建一個epoll對象。
- 為需要監聽的描述符填充epoll\_events結構體,并使用epoll\_ctl()注冊到epoll對象中。
- 使用epoll\_wait()等待事件的發生。
- 根據epoll\_wait()返回的epoll\_events結構體數組判斷事件的類型與來源并進行處理。
- 繼續使用epoll\_wait()等待新事件的發生。
#### 3.INotify與Epoll的小結
INotify與Epoll這兩套由Linux提供的事件監聽機制以最小的開銷解決了文件系統變化以及文件描述符可讀可寫狀態變化的監聽問題。它們是Reader子系統運行的基石,了解了這兩個機制的使用方法之后便為對Reader子系統的分析學習鋪平了道路。
- 前言
- 推薦序
- 第1章 開發環境部署
- 1.1獲取Android源代碼
- 1.2Android的編譯
- 1.3在IDE中導入Android源代碼
- 1.3.1將Android源代碼導入Eclipse
- 1.3.2將Android源代碼導入SourceInsight
- 1.4調試Android源代碼
- 1.4.1使用Eclipse調試Android Java源代碼
- 1.4.2使用gdb調試Android C/C 源代碼
- 1.5本章小結
- 第2章 深入理解Java Binder和MessageQueue
- 2.1概述
- 2.2Java層中的Binder分析
- 2.2.1Binder架構總覽
- 2.2.2初始化Java層Binder框架
- 2.2.3窺一斑,可見全豹乎
- 2.2.4理解AIDL
- 2.2.5Java層Binder架構總結
- 2.3心系兩界的MessageQueue
- 2.3.1MessageQueue的創建
- 2.3.2提取消息
- 2.3.3nativePollOnce函數分析
- 2.3.4MessageQueue總結
- 2.4本章小結
- 第3章 深入理解AudioService
- 3.1概述
- 3.2音量管理
- 3.2.1音量鍵的處理流程
- 3.2.2通用的音量設置函數setStreamVolume()
- 3.2.3靜音控制
- 3.2.4音量控制小結
- 3.3音頻外設的管理
- 3.3.1 WiredAccessoryObserver 設備狀態的監控
- 3.3.2AudioService的外設狀態管理
- 3.3.3音頻外設管理小結
- 3.4AudioFocus機制的實現
- 3.4.1AudioFocus簡單的例子
- 3.4.2AudioFocus實現原理簡介
- 3.4.3申請AudioFocus
- 3.4.4釋放AudioFocus
- 3.4.5AudioFocus小結
- 3.5AudioService的其他功能
- 3.6本章小結
- 第4章 深入理解WindowManager-Service
- 4.1初識WindowManagerService
- 4.1.1一個從命令行啟動的動畫窗口
- 4.1.2WMS的構成
- 4.1.3初識WMS的小結
- 4.2WMS的窗口管理結構
- 4.2.1理解WindowToken
- 4.2.2理解WindowState
- 4.2.3理解DisplayContent
- 4.3理解窗口的顯示次序
- 4.3.1主序、子序和窗口類型
- 4.3.2通過主序與子序確定窗口的次序
- 4.3.3更新顯示次序到Surface
- 4.3.4關于顯示次序的小結
- 4.4窗口的布局
- 4.4.1從relayoutWindow()開始
- 4.4.2布局操作的外圍代碼分析
- 4.4.3初探performLayoutAndPlaceSurfacesLockedInner()
- 4.4.4布局的前期處理
- 4.4.5布局DisplayContent
- 4.4.6布局的階段
- 4.5WMS的動畫系統
- 4.5.1Android動畫原理簡介
- 4.5.2WMS的動畫系統框架
- 4.5.3WindowAnimator分析
- 4.5.4深入理解窗口動畫
- 4.5.5交替運行的布局系統與動畫系統
- 4.5.6動畫系統總結
- 4.6本章小結
- 第5章 深入理解Android輸入系統
- 5.1初識Android輸入系統
- 5.1.1getevent與sendevent工具
- 5.1.2Android輸入系統簡介
- 5.1.3IMS的構成
- 5.2原始事件的讀取與加工
- 5.2.1基礎知識:INotify與Epoll
- 5.2.2 InputReader的總體流程
- 5.2.3 深入理解EventHub
- 5.2.4 深入理解InputReader
- 5.2.5原始事件的讀取與加工總結
- 5.3輸入事件的派發
- 5.3.1通用事件派發流程
- 5.3.2按鍵事件的派發
- 5.3.3DispatcherPolicy與InputFilter
- 5.3.4輸入事件的派發總結
- 5.4輸入事件的發送、接收與反饋
- 5.4.1深入理解InputChannel
- 5.4.2連接InputDispatcher和窗口
- 5.4.3事件的發送
- 5.4.4事件的接收
- 5.4.5事件的反饋與發送循環
- 5.4.6輸入事件的發送、接收與反饋總結
- 5.5關于輸入系統的其他重要話題
- 5.5.1輸入事件ANR的產生
- 5.5.2 焦點窗口的確定
- 5.5.3以軟件方式模擬用戶操作
- 5.6本章小結
- 第6章 深入理解控件系統
- 6.1 初識Android的控件系統
- 6.1.1 另一種創建窗口的方法
- 6.1.2 控件系統的組成
- 6.2 深入理解WindowManager
- 6.2.1 WindowManager的創建與體系結構
- 6.2.2 通過WindowManagerGlobal添加窗口
- 6.2.3 更新窗口的布局
- 6.2.4 刪除窗口
- 6.2.5 WindowManager的總結
- 6.3 深入理解ViewRootImpl
- 6.3.1 ViewRootImpl的創建及其重要的成員
- 6.3.2 控件系統的心跳:performTraversals()
- 6.3.3 ViewRootImpl總結
- 6.4 深入理解控件樹的繪制
- 6.4.1 理解Canvas
- 6.4.2 View.invalidate()與臟區域
- 6.4.3 開始繪制
- 6.4.4 軟件繪制的原理
- 6.4.5 硬件加速繪制的原理
- 6.4.6 使用繪圖緩存
- 6.4.7 控件動畫
- 6.4.8 繪制控件樹的總結
- 6.5 深入理解輸入事件的派發
- 6.5.1 觸摸模式
- 6.5.2 控件焦點
- 6.5.3 輸入事件派發的綜述
- 6.5.4 按鍵事件的派發
- 6.5.5 觸摸事件的派發
- 6.5.6 輸入事件派發的總結
- 6.6 Activity與控件系統
- 6.6.1 理解PhoneWindow
- 6.6.2 Activity窗口的創建與顯示
- 6.7 本章小結
- 第7章 深入理解SystemUI
- 7.1 初識SystemUI
- 7.1.1 SystemUIService的啟動
- 7.1.2 狀態欄與導航欄的創建
- 7.1.3 理解IStatusBarService
- 7.1.4 SystemUI的體系結構
- 7.2 深入理解狀態欄
- 7.2.1 狀態欄窗口的創建與控件樹結構
- 7.2.2 通知信息的管理與顯示
- 7.2.3 系統狀態圖標區的管理與顯示
- 7.2.4 狀態欄總結
- 7.3 深入理解導航欄
- 7.3.1 導航欄的創建
- 7.3.2 虛擬按鍵的工作原理
- 7.3.3 SearchPanel
- 7.3.4 關于導航欄的其他話題
- 7.3.5 導航欄總結
- 7.4 禁用狀態欄與導航欄的功能
- 7.4.1 如何禁用狀態欄與導航欄的功能
- 7.4.2 StatusBarManagerService對禁用標記的維護
- 7.4.3 狀態欄與導航欄對禁用標記的響應
- 7.5 理解SystemUIVisibility
- 7.5.1 SystemUIVisibility在系統中的漫游過程
- 7.5.2 SystemUIVisibility發揮作用
- 7.5.3 SystemUIVisibility總結
- 7.6 本章小結
- 第8章 深入理解Android壁紙
- 8.1 初識Android壁紙
- 8.2深入理解動態壁紙
- 8.2.1啟動動態壁紙的方法
- 8.2.2壁紙服務的啟動原理
- 8.2.3 理解UpdateSurface()方法
- 8.2.4 壁紙的銷毀
- 8.2.5 理解Engine的回調
- 8.3 深入理解靜態壁紙-ImageWallpaper
- 8.3.1 獲取用作靜態壁紙的位圖
- 8.3.2 靜態壁紙位圖的設置
- 8.3.3 連接靜態壁紙的設置與獲取-WallpaperObserver
- 8.4 WMS對壁紙窗口的特殊處理
- 8.4.1 壁紙窗口Z序的確定
- 8.4.2 壁紙窗口的可見性
- 8.4.3 壁紙窗口的動畫
- 8.4.4 壁紙窗口總結
- 8.5 本章小結