androidpipe
『壹』 淺談Android之Linux pipe/epoll
管道
管道的概念:
管道是一種最基本的IPC機制,作用於有血緣關系的進程之間臘仔掘,完成數據傳遞。調用pipe系統函數即可創建一個管道。有如下特質:
1. 其本質是一個偽文件(實為內核緩沖區)
2. 由兩個文件描述符引用,一個表示讀端,一個表示寫端。
3. 規定數據從管道的寫端流入管道,從讀端流出。
管道的原理: 管道實為內核使用環形隊列機制,藉助內核緩沖區(4k)實現。
管道的局限性:
① 數據自己讀不能自己寫。
② 數據一旦被讀走,便不在管道中存在,不可反復讀取。
③ 由於管道採用半雙工通信方式。因此,數據只能在一個方向上流動。
④ 只能在有公共祖先的進程間使用管道。
常見的通信方式有,單工通信、半雙工通信、全雙工通信。
簡單來說這個管道是一個文件,但又和普通輪核文件不通:管道緩沖區大小一般為1頁,即4K位元組,管道分讀端和寫端,讀端負責從管道拿數據,當數據為空時則阻塞;寫端向管道寫數據,當管道緩存區滿時則阻塞。
pipe函數
創建管道
int pipe(int pipefd[2]); 成功:0;失敗:-1,設置errno
函數調用成功返回r/w兩個文件描述符。無需open,但需手動close。規定:fd[0] → r; fd[1] → w,就像0對應標准輸入,1對應標准輸出一樣。向管道文件讀寫數據其實是在讀寫內核緩沖區。
管道創建成功以後,創建該管道的進程(父進程)同時掌握著管道的讀端和寫端。如何實現父子進程間通信呢?通常可以採用如下步驟:
1. 父進程調用pipe函數創建管道,得到兩個文件描述符fd[0]、fd[1]指向管道的讀端和寫端。
2. 父進程調用fork創建子進程,那麼子進程也有兩個文件描述符指向同一管道。
3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以向管道中寫入數據,子進程將管道中的數據讀出。由於管道戚芹是利用環形隊列實現的,數據從寫端流入管道,從讀端流出,這樣就實現了進程間通信。
管道的讀寫行為
使用管道需要注意以下4種特殊情況(假設都是阻塞I/O操作,沒有設置O_NONBLOCK標志):
1. 如果所有指向管道寫端的文件描述符都關閉了(管道寫端引用計數為0),而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾一樣。
2. 如果有指向管道寫端的文件描述符沒關閉(管道寫端引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。
3. 如果所有指向管道讀端的文件描述符都關閉了(管道讀端引用計數為0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,通常會導致進程異常終止。當然也可以對SIGPIPE信號實施捕捉,不終止進程。具體方法信號章節詳細介紹。
4. 如果有指向管道讀端的文件描述符沒關閉(管道讀端引用計數大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。
總結:
① 讀管道: 1. 管道中有數據,read返回實際讀到的位元組數。
2. 管道中無數據:
(1) 管道寫端被全部關閉,read返回0 (好像讀到文件結尾)
(2) 寫端沒有全部被關閉,read阻塞等待(不久的將來可能有數據遞達,此時會讓出cpu)
② 寫管道: 1. 管道讀端全部被關閉, 進程異常終止(也可使用捕捉SIGPIPE信號,使進程不終止)
2. 管道讀端沒有全部關閉:
(1) 管道已滿,write阻塞。
(2) 管道未滿,write將數據寫入,並返回實際寫入的位元組數。
Epoll的概念
Epoll可以使用一次等待監聽多個描述符的可讀/可寫狀態.等待返回時攜帶了可讀的描述符或者自定義的數據.不需要為每個描述符創建獨立的線程進行阻塞讀取,
Linux系統中的epoll機制為處理大批量句柄而作了改進的poll,是Linux下多路復用IO介面select/poll的增強版本,它能顯著減少程序在大量並發連接中只有少量活躍的情況下的系統CPU利用率
(01) pipe(wakeFds),該函數創建了兩個管道句柄。
(02) mWakeReadPipeFd=wakeFds[0],是讀管道的句柄。
(03) mWakeWritePipeFd=wakeFds 1 ,是寫管道的句柄。
(04) epoll_create(EPOLL_SIZE_HINT)是創建epoll句柄。
(05) epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem),它的作用是告訴mEpollFd,它要監控mWakeReadPipeFd文件描述符的EPOLLIN事件,即當管道中有內容可讀時,就喚醒當前正在等待管道中的內容的線程。
回到Android中的epoll大致流程如下:
Looper.loop -> MessageQueue.nativePollOnce
epoll_create() epoll_ctl() 注冊事件的回調
looper.pollInner() -> epoll_wait() 等待接受事件喚醒的回調
MessageQueue.enqueueMessage(Message msg, long when) -> MessageQueue.nativeWake(long ptr)
參考鏈接如下
鏈接:https://www.jianshu.com/p/8656bebc27cb
鏈接:https://blog.csdn.net/oguro/article/details/53841949
『貳』 Android 進程間通信的幾種實現方式
Android 進程間通信的幾種實現方式
主要有4種方式:
這4種方式正好對應於android系統中4種應用程序組件:Activity、Content Provider、Broadcast和Service。
主要實現原理:
由於應用程序之間不能共享內存。為了在不同應用程序之間交互數據(跨進程通訊),AndroidSDK中提供了4種用於跨進程通訊的方式進行交互數據,實現進程間通信主要是使用sdk中提供的4組組件根據實際開發情況進行實現數據交互。
詳細實現方式:
Acitivity實現方式
Activity的跨進程訪問與進程內訪問略有不同。雖然它們都需要Intent對象,但跨進程訪問並不需要指定Context對象和Activity的 Class對象,而需要指定的是要訪問的Activity所對應的Action(一個字元串)。有些Activity還需要指定一個Uri(通過 Intent構造方法的第2個參數指定)。 在android系統中有很多應用程序提供了可以跨進程訪問的Activity,例如,下面的代碼可以直接調用撥打電話的Activity。
java">IntentcallIntent=newIntent(Intent.ACTION_CALL,Uri.parse("tel:12345678");
startActivity(callIntent);
Content Provider實現方式
Android應用程序可以使用文件或SqlLite資料庫來存儲數據。Content Provider提供了一種在多個應用程序之間數據共享的方式(跨進程共享數據)
應用程序可以利用Content Provider完成下面的工作
1. 查詢數據
2. 修改數據
3. 添加數據
4. 刪除數據
Broadcast 廣播實現方式
廣播是一種被動跨進程通訊的方式。當某個程序向系統發送廣播時,其他的應用程序只能被動地接收廣播數據。這就象電台進行廣播一樣,聽眾只能被動地收聽,而不能主動與電台進行溝通。在應用程序中發送廣播比較簡單。只需要調用sendBroadcast方法即可。該方法需要一個Intent對象。通過Intent對象可以發送需要廣播的數據。
Service實現方式
常用的使用方式之一:利用AIDL Service實現跨進程通信
這是我個人比較推崇的方式,因為它相比Broadcast而言,雖然實現上稍微麻煩了一點,但是它的優勢就是不會像廣播那樣在手機中的廣播較多時會有明顯的時延,甚至有廣播發送不成功的情況出現。
注意普通的Service並不能實現跨進程操作,實際上普通的Service和它所在的應用處於同一個進程中,而且它也不會專門開一條新的線程,因此如果在普通的Service中實現在耗時的任務,需要新開線程。
要實現跨進程通信,需要藉助AIDL(Android Interface Definition Language)。Android中的跨進程服務其實是採用C/S的架構,因而AIDL的目的就是實現通信介面。
總結
跨進程通訊這個方面service方式的通訊遠遠復雜於其他幾種通訊方式,實際開發中Activity、Content Provider、Broadcast和Service。4種經常用到,學習過程中要對沒種實現方式有一定的了解。
『叄』 Android跨進程通信-共享內存
還是先看共享內存的使用方法,我主要介紹兩個函數:
通過 shmget() 函數申請共享內存,它的入參如下
通過 shmat() 函數將我們申請到的共享內存映射到自己的用戶空間,映射成功會返回地址,有了這個地址,我們就可以隨意的讀寫數據了,我們繼續看一下這個函數的入參
共享內存的原理是在內存中單獨開辟的一段內存空間,這段內存空間其實就是一個tempfs(臨時虛擬文件),tempfs是VFS的一種文件系統,掛載在/dev/shm上,前面提到的管道pipefs也是VFS的一種文件系統。
由於共享的內存空間對使用和接收進程來講,完全無感知,就像是在自己的內存上讀寫數據一樣,所以也是 效率最高 的一種IPC方式。
上面提到的IPC的方式都是 在內核空間中開辟內存來存儲數據 ,寫數據時,需要將數據從用戶空間拷貝到內核空間,讀數據時,需要從內核空間拷貝到自己的用戶空間,
共享內存就只需要一次拷貝 ,而且共享內存不是在內核開辟空間,所以可以 傳輸的數據量大 。
但是 共享內存最大的缺點就是沒有並發的控制,我們一般通過信號量配合共享內存使用,進行同步和並發的控制 。
共享內存在Android系統中主要的使用場景是 用來傳輸大數據 ,並且 Android並沒有直接使用Linux原生的共享內存方式,而是設計了Ashmem匿名共享內存 。
之前說到有名管道和匿名管道的區別在於有名管道可以在vfs目錄樹中查看到這個管道的文件,但是匿名管道不行, 所以匿名共享內存同樣也是無法在vfs目錄中查看到 的, Android之所以要設計匿名共享內存 ,我覺得主要是為了安全性的考慮吧。
我們來看看共享內存的一個使用場景,在Android中,如果我們想要將當前的界面顯示出來,需要將當前界面的圖元數據傳遞Surfaceflinger去做圖層混合,圖層混合之後的數據會直接送入幀緩存,送入幀緩存後,顯卡就會直接取出幀緩存里的圖元數據顯示了。
那麼我們如何將應用的Activity的圖元數據傳遞給SurfaceFlinger呢?想要將圖像數據這樣比較大的數據跨進程傳輸,靠binder是不行的,所以這兒便用到匿名共享內存。
從谷歌官方提供的架構圖可以看到,圖元數據是通過BufferQueue傳遞到SurfaceFlinger去的,當我們想要繪制圖像的時候, 需要從BufferQueue中申請一個Buffer,Buffer會調用Gralloc模塊來分配共享內存 當作圖元緩沖區存放我們的圖元數據。
可以看到Android的匿名共享內存是通過 ashmem_create_region() 函數來申請共享內存的,它會在/dev/ashmem下創建一個虛擬文件,Linux原生共享內存是通過shmget()函數,並會在/dev/shm下創建虛擬文件。
匿名共享內存是通過 mmap() 函數將申請到的內存映射到自己的進程空間,而Linux是通過*shmat()函數。
雖然函數不一樣,但是Android的匿名共享內存和Linux的共享內存在本質上是大同小異的。
要使用一塊共享內存
『肆』 Android ParcelFileDescriptor實現進程間通信
一個通信通道,實現跨進程的的Socket網路通信。
具體的通信通道的圖如下。
android進程間通信是使用Binder來傳數據,而Binder傳輸的數據,有一個最為基本的要求,就是要實現Parcelable介面。
ParcelFileDescriptor是android提供的一個數據結構。
ParcelFileDescriptor是可以用於進程間Binder通信的FileDescriptor。支持stream 寫入和stream 讀出
我們可以使用
來將PacecelFileDescriptor 與File對應起來,以實現進程間的文件共享。
我們也可以使用
來建立一個pipe通信通道,ParcelFileDescriptor數組第一個元素是read端,第二個元素是write端,通過write端的AutoCloseOutputStream和read端的AutoCloseInputStream,我們就可以實現進程見的數據流傳輸了。
發送端:
1. 業務層調用getOutputStream向通信層發起請求
2. 通信層通過creatPipe 建立一個ParcelFileDescriptor數組,並將write端的pipe[1]返回給業務層
3. 業務層得到pipe[1](ParcelFileDescriptor)後,可以通過AutoCloseOutputStream寫入數據
4. 從通信層的pipe[0]的AutoCloseInputStream中讀出數據通過socket發送出去
接收端:
1. 業務層調用getInputStream向通信層發起請求
2. 通信層通過creatPipe 建立一個ParcelFileDescriptor數組,並將read端的pipe[0]返回給業務層
3. 業務層得到pipe 0 後,可以通過AutoCloseInputStream讀取數據。(如沒有數據,則阻塞,一直等到有數據為止)
4. socket中讀取數據,寫入到通信層的pipe[1]的AutoCloseOutputStream。(pipe[1]一旦寫入,第三步中pipe[2]就可以讀取出數據)
『伍』 android系統源代碼情景分析 需要具備什麼基礎知識
Android系統的源代碼非常龐大和復雜,我們不能貿然進入,否則很容易在裡面迷入方向,進而失去研究它的信心。我們應該在分析它的源代碼之前學習好一些理論知識,下面就介紹一些與Android系統相關的資料。
我們知道,Android系統是基於Linux內核來開發的,在分析它在運行時庫層的源代碼時,我們會經常碰到諸如管道(pipe)、套接字(socket)和虛擬文件系統(VFS)等知識。此外,Android系統還在Linux內核中增加了一些專用的驅動程序,例如用於日誌系統的Logger驅動程序、用於進程間通信的Binder驅動程序和用於輔助內存管理的匿名共享內存Ashmem驅動程序。在分析這些Android專用驅動程序的時候,也會碰到Linux內核中與進程、內存管理相關的數據結構。因此,我們有必要掌握一些Linux內核的基礎知識,下面就介紹四本典經的Linux內核書籍。
1.Linux Kernel Development.
這本書的作者是Robert Love,目前最新的版本是第3版。這本書對Linux內核的設計和實現提供了一個總覽視圖,從概念上對Linux內核的各個子系統的設計目標和實現思路進行了清晰的描述,非常適合初學者閱讀。如果從軟體工程的角度來看,這本書就相當於是Linux內核的概要設計文檔。
2.Understanding the Linux Kernel.
這本書的作者是Daniel P. Bovet和Marco Cesati,目前最新的版本是第3版。這本書對Linux內核的實現提供了更多的細節,詳細地描述了內核開發中用到的重要數據結構、演算法以及編程技巧,非常適合中高級讀者閱讀。如果從軟體工程的角度來看,這本書就相當於是Linux內核的詳細設計文檔。
3.Linux Device Drivers.
這本書的作者是Jonathan Corbet, Alessandro Rubini和Greg Kroah-Hartman,目前最新的版本是第3版。這本書更加註重實際操作,它詳細地講解了Linux內核驅動程序的實現原理和實現方法,讀者可以跟著它來實際地編寫出自己的Linux驅動程序。閱讀了這本書之後,對我們後續去分析Android的專用驅動程序是有非常大的幫助的。
4.Linux內核源代碼情景分析
這本書的作者是毛德操和胡希明,是中國人自己編寫的一本經典的Linux內核書籍。這本書最大的特點是從使用情景出發,對Linux內核的源代碼作了詳細的分析,幫助讀者把枯燥無味的源代碼給理順了。
掌握了Linux內核的基礎知識之後,還不宜馬上就去分析Android系統的源代碼,因為這樣做是漫無目的的,我們應該帶著問題或者目標去分析Android系統的源代碼。要把問題或者目標挖掘出來,最好的方法就莫過於是在Android平台上編寫自己的應用程序了。通過編寫應用程序,我們可以知道Android平台都提供了哪些功能,進而我們就會想去了解這些功能是怎麼實現的,這樣就可以達到帶著問題或者目標去分析Android系統的源代碼了。這里介紹兩個Android應用程序開發教程的書籍:
1.Professional Android 2 Application Development.
2.Google Android SDK開發範例大全.
這兩本書都使用了大量的例子來說明如何使用Android SDK來開發Android應用程序。讀者可以根據實際情況來練習一下,主要掌握Android應用程序四大組件(Activity、Service、Broadcast Receiver和Content Provider)的用法,因為Android系統的整個架構和實現就是為了向開發者提供這四大組件來實現各種各樣的應用程序的。在學習的過程中,如果遇到其它問題,還可以參考官方文檔
『陸』 Android View 事件分發機制
Android 事件機制包含系統啟動流程、輸入管理(InputManager)、系統服務和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分發等一系列的環節。
Android 系統中將輸入事件定義為 InputEvent,根據輸入事件的類型又分為了 KeyEvent(鍵盤事件) 和 MotionEvent(屏幕觸摸事件)。這些事件統一由系統輸入管理器 InputManager 進行分發。
在系統啟動的時候,SystemServer 會啟動 WindowManagerService,WMS 在啟動的時候通過 InputManager 來負責監控鍵盤消息。
InputManager 負責從硬體接收輸入事件,並將事件通過 ViewRootImpl 分發給當前激活的窗口處理,進而分發給 View。
Window 和 InputManagerService 之間通過 InputChannel 來通信,底層通過 socket 進行通信。
Android Touch 事件的基礎知識:
KeyEvent 對應了鍵盤的輸入事件;MotionEvent 就是手勢事件,滑鼠、筆、手指、軌跡球等相關輸入設備的事件都屬於 MotionEvent。
InputEvent 統一由 InputManager 進行分發,負責與硬體通信並接收輸入事件。
system_server 進程啟動時會創建 InputManagerService 服務。
system_server 進程啟動時同時會啟動 WMS,WMS 在啟動的時候就會通過 IMS 啟動 InputManager 來監控鍵盤消息。
App 端與服務端建立了雙向通信之後,InputManager 就能夠將產生的輸入事件從底層硬體分發過來,Android 提供了 InputEventReceiver 類,以接收分發這些消息:
Window 和 IMS 之間通過 InputChannel 通信。InputChannel 是一個 pipe,底層通過 socket 進行通信。在 ViewRootImpl.setView() 過程中注冊 InputChannel。
Android 事件傳遞機制是 先分發再處理 ,先由外部的 View 接收,然後依次傳遞給其內層的 View,再從最內層 View 反向依次向外層傳遞。
三個方法的關系如下:
分發事件:
應用了樹的 深度優先搜索演算法 (Depth-First-Search,簡稱 DFS 演算法),每個 ViewGroup 都持有一個 mFirstTouchTarget, 當接收到 ACTION_DOWN 時,通過遞歸遍歷找到 View 樹中真正對事件進行消費的 Child,並保存在 mFirstTouchTarget 屬性中,依此類推組成一個完整的分發鏈。在這之後,當接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 時,則會跳過遞歸流程,將事件直接分發給下一級的 Child。
ViewGroup 分發事件的主要的任務是找一個 Target,並且用這個 Target 處理事件,主要邏輯如下 :
為什麼倒序查找 TouchTarget?
如果按添加順序遍歷,當 View 重疊時(FrameLayout),先添加的 View 總是能消費事件,而後添加的 View 不可能獲取到事件。
攔截事件:
[1] Android 事件分發機制的設計與實現
[2] Android 事件攔截機制的設計與實現