Android Handler 機制(三):Handler 機制與管道 Pipe 機制


在深入系統的學習Handler的時候,我們接觸到了Looper之所以死循環不會導致CPU使用率過高,是因為使用了Linux下的pipe和epoll機制。

Android的應用層通過Message.java實現隊列,利用管道和epoll機制實現線程狀態的管理,配合起來實現了Android主線程的消息隊列模型。

對Handler,我們在之前整理了如下內容,也上對Handler的機制有了相當程度的了解:

Android Handler 機制(一):Handler 運行機制完整梳理 

Android Handler 機制(二):Hander 機制深入探究問題梳理 

同時為了更進一步的了解Handler機制,我們整理了Epoll機制的相關基本知識:Linux下Epoll機制概述

本文我們講述一下Android的Handler機制為何使用管道。

一、管道概述

管道,其本質是也是文件,但又和普通的文件會有所不同:管道緩沖區大小一般為1頁,即4K字節。管道分為讀端和寫端,讀端負責從管道拿數據,當數據為空時則阻塞;寫端向管道寫數據,當管道緩存區滿時則阻塞。

在Handler機制中,Looper.loop方法會不斷循環處理Message,其中消息的獲取是通過 Message msg = queue.next(); 方法獲取下一條消息。該方法中會調用nativePollOnce()方法,這便是一個native方法,再通過JNI調用進入Native層,在Native層的代碼中便采用了管道機制。

二、Handler為何使用管道?

我們可能會好奇,既然是同一個進程間的線程通信,為何需要管道呢?

我們知道線程之間內存共享,通過Handler通信,消息池的內容並不需要從一個線程拷貝到另一個線程,因為兩線程可使用的內存時同一個區域,都有權直接訪問,當然也存在線程私有區域ThreadLocal(這里不涉及)。即然不需要拷貝內存,那管道是何作用呢?

Handler機制中管道作用就是當一個線程A准備好Message,並放入消息池,這時需要通知另一個線程B去處理這個消息。線程A向管道的寫端寫入數據1(對於老的Android版本是寫入字符`W`),管道有數據便會喚醒線程B去處理消息。管道主要工作是用於通知另一個線程的,這便是最核心的作用。
這里我們通過兩張圖來展示Handler在Java層和在Native層的邏輯:
Java層:

Native層:

三、Handler為何采用管道而非Binder?

handler不采用Binder,並非binder完成不了這個功能,而是太浪費CPU和內存資源了。因為Binder采用C/S架構,一般用於不同進程間的通信。
  • 從內存角度:通信過程中Binder還涉及一次內存拷貝,handler機制中的Message根本不需要拷貝,本身就是在同一個內存。Handler需要的僅僅是告訴另一個線程數據有了。
  • 從CPU角度,為了Binder通信底層驅動還需要為何一個binder線程池,每次通信涉及binder線程的創建和內存分配等比較浪費CPU資源。

從上面的角度分析可得,Binder用於進程間通信,而Handler消息機制用於同進程的線程間通信,Handler不宜采用Binder。

四、Android 6.0及以后的機制

在Android 6.0及以前的版本使用管道與epoll來完成Looper的休眠與喚醒的。

在Android 6.0及以后的新版本中使用的是eventfd與epoll來完成Looper的休眠與喚醒的。

感興趣的可以進一步的學習和了解管道的知識及eventfd的知識,並比較一下兩種機制的優劣,進而明白Android官方為何對此機制進行調整。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM