為什麼Android的Handler採用管道而不使用Binder?


謝邀@周磊

  1. 關於Binder,可以先看看我之前的回答,為什麼 Android 要採用 Binder 作為 IPC 機制? - Gityuan 的回答
  2. 關於Handler為何不採用Binder, 先總結說一句,Handler完全可以通過BInder,但是殺雞焉用牛刀。 後面再補充...

--------------------------------------------------分割線-------------------------------------------------------------------

更新時間 2016.4.28 18:18

如果僅僅從上層來看待這個問題,那是盲人摸象。這個問題需要從上往下追溯才能看清本原,重頭戲也在底層,這裡的底層不僅僅是Native層,還需要對Linux所有了解,才能真正理解透徹。看到前面大家的回答,我覺得有必要儘早回復一下,以免有些回答者誤導更多新人。

(1)有人說管道是Linux IPC機制,IPC英文全稱Inter-Process Communication,意思是進程間通信,而Handler是用於同一進程內的線程間通信,怎麼會用到管道呢?這樣的回答應該是看了一些教科書,但並沒有真正理解透徹,這是不對的。Handler底層的確是採用管道機制。

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

(2)再說說Handler是如何使用管道的

有人說Handler所涉及的MessageQueue/Message/Looper/Handler這4個類都是採用Java實現,哪來的底層採用管道的機會?這是不對的

在Looper.loop方法,會不斷循環處理Message,其中消息的獲取是通過

Message msg = queue.next(); //用於獲取消息隊列中的下一條消息

該方法中會調用nativePollOnce()方法,這便是一個native方法,再通過JNI調用進入Native層,便採用了管道,比如epoll_create/epoll_wait/epoll_ctl,這裡通過一副圖來讓大家看清楚Java層與native的聯繫。

流程就不細說了,直接去看代碼或者我的博客 Android消息機制2-Handler(Native層)。

(3)到這裡有人可能好奇,既然是同一個進程間的線程通信,為何需要管道呢?

線程之間內存共享,通過Handler通信,消息池的內容並不需要從一個線程拷貝到另一個線程,

因為兩線程可使用的內存時同一個區域,都有權直接訪問,當然也存在線程私有區域ThreadLocal(這裡不涉及)。即然不需要拷貝內存,那管道是何作用呢?

Handler機制中管道作用就是當一個線程A準備好Message,並放入消息池,這時需要通知另一個線程B去處理這個消息。線程A向管道的寫端寫入數據1(對於老的Android版本是寫入字元`W`),管道有數據便會喚醒線程B去處理消息。管道主要工作是用於通知另一個線程的,這便是最核心的作用。

(4)有了這些關於Handler的準備,再加上為什麼 Android 要採用 Binder 作為 IPC 機制? - Gityuan 的回答,再來說說handler為何採用管道而非Binder?

handler不採用Binder,並非binder完成不了這個功能,而是太浪費CPU和內存資源了。

Binder採用C/S架構,往往用於不同進程間的通信

  • 從內存角度:通信過程中還涉及一次內存拷貝,handler機制中的Message根本不需要拷貝,本身就是在同一個內存。Handler需要的僅僅是告訴另一個線程數據有了。
  • 從CPU角度,為了Binder通信底層驅動還需要為何一個binder線程池,每次通信涉及binder線程的創建和內存分配等比較浪費CPU資源。

Handler不宜採用Binder,殺雞焉用牛刀。

(5)Binder Vs Handler

Binder用於進程間通信,而Handler消息機制用於同進程的線程間通信。

  • 有人可能會疑惑,為何Binder/Socket用於進程間通信,能否用於線程間通信呢?答案是肯定,對於兩個具有獨立地址空間的進程通信都可以,當然也能用於共享內存空間的兩個線程間通信,這就好比殺雞用牛刀。
  • 接著可能還有人會疑惑,那handler消息機制能否用於進程間通信?答案是不能,Handler只能用於共享內存地址空間的兩個線程間通信,即同進程的兩個線程間通信。


線程間通信,本來就是在一塊內存區


首先Handler不見得使用管道 (pipe), 事實上, 在Android M中實際上使用了 eventfd. 不過這不關鍵.

要想比較好的理解為什麼Handler使用管道而不是Binder, 我們可以從開發者的角度來思考一下: 在Looper中, 他要解決一個什麼問題呢? 我自己本身不是很熟悉Looper這些概念, 粗粗看了下代碼: 大約可以看成是消息循環(Message Loop)的樣子吧. 其它線程發消息, Looper所在的線程接受並處理消息. 好吧, 開發者要解決的是一個生產者消費者問題. 一般說來對於生產者消費者問題我們需要考慮幾個因素: buffer (用於存放"產品"的內存), 上鎖 (保護對buffer的訪問), 等待/喚醒:

1. 生產者和消費者在同一個進程內, 所以無需考慮共享內存問題

2. 上鎖: posix mutex, 簡單好用

3. 等待/喚醒: 在沒有消息時, 消費者不能忙等, 而應該阻塞 (block) 直到新消息到來 -

這裡的關鍵是3), 在Android/Linux中有很多方法可以用來等待/喚醒, 比如條件變數, Posix信號量 (Android不支持System V信號量), 消息隊列, Unix socket , pipe, 甚至binder等等. 怎麼選呢? Looper開發者的標準很可能是: 選較"輕"的. 於是他先選了pipe, 以後又發現eventfd更好 (很可能開發者不知道eventfd的存在, 畢竟eventfd還是比較新的系統調用).


Handler 用在非進程間通訊 就 沒有必要使用Binder.

需要跨進程使用Handler的時候, 瞅瞅android.os.Messenger.

綜上, 不是說Handler不用Binder, 而是需要誇進程才考慮binder.


下面的理解是錯的:

-----------------------------------------------------------------------------------------------------------------------------------

Handler被設計用來執行消息,線程有關的操作,並不會用於跨進程調用,因此它既沒有使用Binder, 也不需要使用管道。

話說樓主是在哪裡看到的?

如果我理解錯了,請速指出。

------------------------------------------------------------------------------------------------------------------------------------

如何使用管道可以看這兩篇文章:

Android多線程分析之四:MessageQueue的實現

Native looper 分析


Looper構造的時候 new MessageQueue,MessageQueue構造方法會走nativeInit

裡面貌似會用到管道,坐等大神回答一下。


推薦閱讀:

手機續航能力由哪些因素決定?
安卓中有沒有可懸浮可透明的輸入法?
作為大陸用戶,我們錯過了Android 的什麼?
為什麼 Android 比 Windows Phone 和 iOS 卡頓情況嚴重?
現在android開發都會用到那些快速開發框架或者第三庫?

TAG:Linux | Android開發 | Android | AndroidStudio |