【android開發】Android binder學習一:主要概念

2021-03-02 程式設計師互動聯盟

要看得懂android代碼,首先要了解binder機制。binder機制也是android裡面比較難以理解的一塊,這裡記錄一下binder的重要概念以及實現,作為備忘。部分內容來源於網上,如有侵權,請及時告知。

1.binder通信機制概述

binder通信是一種client-server的通信結構,
1.從表面上來看,是client通過獲得一個server的代理接口,對server進行直接調用;


2.實際上,代理接口中定義的方法與server中定義的方法是一一對應的;


3.client調用某個代理接口中的方法時,代理接口的方法會將client傳遞的參數打包成為Parcel對象;


4.代理接口將該Parcel發送給內核中的binder driver.


5.server會讀取binder driver中的請求數據,如果是發送給自己的,解包Parcel對象,處理並將結果返回;


6.整個的調用過程是一個同步過程,在server處理的時候,client會block住。


2.為什麼使用binder通信

linux中有管道,system V IPC,socket等進程間通信機制,那麼為什麼在android中使用了一個全新的binder通信機制呢?

一、可靠性。在行動裝置上,通常採用基於Client-Server的通信方式來實現網際網路與設備間的內部通信。目前linux支持IPC包括傳統的管道,System V IPC,即消息隊列/共享內存/信號量,以及socket中只有socket支持Client-Server的通信方式。Android系統為開發者提供了豐富進程間通信的功能接口,媒體播放,傳感器,無線傳輸。這些功能都由不同的server來管理。開發都只關心將自己應用程式的client與server的通信建立起來便可以使用這個服務。毫無疑問,如若在底層架設一套協議來實現Client-Server通信,增加了系統的複雜性。在資源有限的手機 上來實現這種複雜的環境,可靠性難以保證。

二、傳輸性能。socket主要用於跨網絡的進程間通信和本機上進程間的通信,但傳輸效率低,開銷大。消息隊列和管道採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開闢的一塊緩存區中,然後從內核緩存區拷貝到接收方緩存區,其過程至少有兩次拷貝。雖然共享內存無需拷貝,但控制複雜。比較各種IPC方式的數據拷貝次數。共享內存:0次。Binder:1次。Socket/管道/消息隊列:2次。

IPC數據拷貝次數共享內存0Binder1Socket/管道/消息隊列2

三、安全性。Android是一個開放式的平臺,所以確保應用程式安全是很重要的。Android對每一個安裝應用都分配了UID/PID,其中進程的UID是可用來鑑別進程身份。傳統的只能由用戶在數據包裡填寫UID/PID,這樣不可靠,容易被惡意程序利用。而我們要求由內核來添加可靠的UID。

基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通信方式,傳輸性能和安全性的要求,這就是Binder。Binder基於Client-Server通信模式,傳輸過程只需一次拷貝,為發送發添加UID/PID身份,既支持實名Binder也支持匿名Binder,安全性高。

3.service manager

顧名思義,service manager就是android下面管理service的一個進程,它本身也是一個service,這裡的service和init.rc裡面的service有一些差別,init.rc中的service都是一個進程,而這裡的service可能不是一個單獨的進程。每一個service在使用之前都必須向SM註冊,每一個client要使用service前都應該先向SM查詢是否存在這個service,如果存在,則給client返回這個service的handle。下面是sm中main函數的關鍵代碼:

int main(int argc, char **argv){ struct binder_state *bs; bs = binder_open(128*1024); if (!bs) { ALOGE("failed to open binder driver\n"); return -1; } if (binder_become_context_manager(bs)) { ALOGE("cannot become context manager (%s)\n", strerror(errno)); return -1; } . //svcmgr_handle的值為0 svcmgr_handle = BINDER_SERVICE_MANAGER; binder_loop(bs, svcmgr_handler); return 0;}

在SM中主要做了如下工作:

打開binder設備,映射128k的內存到應用空間。

指定 svcmgr_handle的值為0,當client與SM通信時,需要先創建一個handle為0的代理binder。

binder_become_context_manager通知binder driver使SM為context manager。

binder_loop是一個死循環,裡面不停的讀binder是否有數據,如果有數據,則解析,對於BR_TRANSACTION,會調用svcmgr_handler來處理。

SM維護了一個svclist來存儲service的信息。一個新的service需要向SM註冊add到這個列表,而client請求時會在svclist裡面查找請求的service。一個service包括兩個重要的信息,handle和name。add和get都會根據name來進行匹配。

下面一個圖片可以簡單說明SM與binder driver之間的關係:


由上可知,service在使用前會先作為client向SM註冊;應用若要使用某一個服務,需要先向SM獲取該服務的handle,然後通過handle來調用該服務提供的方法。

4.ProcessState

ProcessState是以單例模式設計的。每個進程在使用binder機制通信時,均需要維護一個ProcessState實例來描述當前進程在binder通信時的binder狀態。


ProcessState有如下2個主要功能:


1.創建一個thread,該線程負責與內核中的binder模塊進行通信,稱該線程為Pool thread;


2.為指定的handle創建一個BpBinder對象,並管理該進程中所有的BpBinder對象。

4.1 Pool thread

在Binder IPC中,所有進程均會啟動一個thread來負責與BD(binder driver)來直接通信,也就是不停的讀寫BD,這個線程的實現主體是一個IPCThreadState對象,下面會介紹這個類型。

下面是 Pool thread的啟動方式:
ProcessState::self()->startThreadPool();

4.2 BpBinder獲取

BpBinder主要功能是負責client向BD發送調用請求的數據。它是client端binder通信的核心對象,通過調用transact函數向BD發送調用請求的數據,它的構造函數如下:

BpBinder(int32_t handle);

通過BpBinder的構造函數發現,BpBinder會將當前通信中server的handle記錄下來,當有數據發送時,會通知BD數據的發送目標ProcessState通過如下方式來獲取BpBinder對象:

ProcessState::self()->getContextObject(handle);

在這個過程中,ProcessState會維護一個BpBinder的vector mHandleToObject,每當ProcessState創建一個BpBinder的實例時,回去查詢mHandleToObject,如果對應的handle已經有binder指針,那麼不再創建,否則創建binder並插入到mHandleToObject中。


ProcessState創建的BpBinder實例,一般情況下會作為參數構建一個client端的代理接口,這個代理接口的形式為BpINTERFACE,例如在與SM通信時,client會創建一個代理接口BpServiceManager。

5.IPCThreadState

IPCThreadState也是以單例模式設計的。由於每個進程只維護了一個ProcessState實例,同時ProcessState只啟動一個Pool thread,也就是說每一個進程只會啟動一個Pool thread,因此每個進程則只需要一個IPCThreadState即可。

Pool thread的實際內容則為:

IPCThreadState::self()->joinThreadPool();

ProcessState中有2個Parcel成員,mIn和mOut,Pool thread會不停的查詢BD中是否有數據可讀,如果有將其讀出並保存到mIn,同時不停的檢查mOut是否有數據需要向BD發送,如果有,則將其內容寫入到BD中,總而言之,從BD中讀出的數據保存到mIn,待寫入到BD中的數據保存在了mOut中。

ProcessState中生成的BpBinder實例通過調用IPCThreadState的transact函數來向mOut中寫入數據,這樣的話這個binder IPC過程的client端的調用請求的發送過程就明了了。

IPCThreadState有兩個重要的函數,talkWithDriver函數負責從BD讀寫數據,executeCommand函數負責解析並執行mIn中的數據。


6.主要基類6.1基類IInterface

為server端提供接口,它的子類聲明了service能夠實現的所有的方法;

6.2基類IBinder

BBinder與BpBinder均為IBinder的子類,因此可以看出IBinder定義了binder IPC的通信協議,BBinder與BpBinder在這個協議框架內進行的收和發操作,構建了基本的binder IPC機制。

6.3基類BpRefBase

client端在查詢SM獲得所需的的BpBinder後,BpRefBase負責管理當前獲得的BpBinder實例。

7.兩個接口類7.1 BpINTERFACE

如果client想要使用binder IPC來通信,那麼首先會從SM出查詢並獲得server端service的BpBinder,在client端,這個對象被認為是server端的遠程代理。為了能夠使client能夠想本地調用一樣調用一個遠程server,server端需要向client提供一個接口,client在在這個接口的基礎上創建一個BpINTERFACE,使用這個對象,client的應用能夠想本地調用一樣直接調用server端的方法。而不用去關心具體的binder IPC實現。

下面看一下BpINTERFACE的原型:

class BpINTERFACE : public BpInterface<IINTERFACE>

順著繼承關係再往上看

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase

BpINTERFACE分別繼承自INTERFACE,和BpRefBase;

● BpINTERFACE既實現了service中各方法的本地操作,將每個方法的參數以Parcel的形式發送給BD。例如BpServiceManager的

virtual status_t addService(const String16& name, const sp<IBinder>& service){ Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err;}

● 同時又將BpBinder作為了自己的成員來管理,將BpBinder存儲在mRemote中,BpServiceManager通過調用BpRefBase的remote()來獲得BpBinder指針。

7.2 BnINTERFACE

在定義android native端的service時,每個service均繼承自BnINTERFACE(INTERFACE為service name)。BnINTERFACE類型定義了一個onTransact函數,這個函數負責解包收到的Parcel並執行client端的請求的方法。

順著BnINTERFACE的繼承關係再往上看,
class BnINTERFACE: public BnInterface<IINTERFACE>

IINTERFACE為client端的代理接口BpINTERFACE和server端的BnINTERFACE的共同接口類,這個共同接口類的目的就是保證service方法在C-S兩端的一致性。

再往上看
class BnInterface : public INTERFACE, public BBinder

同時我們發現了BBinder類型,這個類型又是幹什麼用的呢?既然每個service均可視為一個binder,那麼真正的server端的binder的操作及狀態的維護就是通過繼承自BBinder來實現的。可見BBinder是service作為binder的本質所在。

那麼BBinder與BpBinder的區別又是什麼呢?

其實它們的區別很簡單,BpBinder是client端創建的用於消息發送的代理,而BBinder是server端用於接收消息的通道。查看各自的代碼就會發現,雖然兩個類型均有transact的方法,但是兩者的作用不同,BpBinder的transact方法是向IPCThreadState實例發送消息,通知其有消息要發送給BD;而BBinder則是當IPCThreadState實例收到BD消息時,通過BBinder的transact的方法將其傳遞給它的子類BnSERVICE的onTransact函數執行server端的操作。

8.Parcel

Parcel是binder IPC中的最基本的通信單元,它存儲C-S間函數調用的參數.但是Parcel只能存儲基本的數據類型,如果是複雜的數據類型的話,在存儲時,需要將其拆分為基本的數據類型來存儲。

簡單的Parcel讀寫不再介紹,下面著重介紹一下2個函數。

8.1 writeStrongBinder

當一個service 調用add_service把自己加入到SM中時,就會遇到這種情況,如下(IServiceManager.cpp):

virtual status_t addService(const String16& name, const sp<IBinder>& service, bool allowIsolated) { Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); data.writeInt32(allowIsolated ? 1 : 0); status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err; }

其中writeStrongBinder(Parcel.cpp)如下:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val){ return flatten_binder(ProcessState::self(), val, this);}

接著看flatten_binder:

status_t flatten_binder(const sp<ProcessState>& /*proc*/, const sp<IBinder>& binder, Parcel* out){ flat_binder_object obj; obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; if (binder != NULL) { IBinder *local = binder->localBinder(); if (!local) { BpBinder *proxy = binder->remoteBinder(); if (proxy == NULL) { ALOGE("null proxy"); } const int32_t handle = proxy ? proxy->handle() : 0; obj.type = BINDER_TYPE_HANDLE; obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */ obj.handle = handle; obj.cookie = 0; } else { obj.type = BINDER_TYPE_BINDER; obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs()); obj.cookie = reinterpret_cast<uintptr_t>(local); } } else { obj.type = BINDER_TYPE_BINDER; obj.binder = 0; obj.cookie = 0; } return finish_flatten_binder(binder, obj, out);}

addService的參數為一個BnINTERFACE類型指針,BnINTERFACE又繼承自BBinder:

BBinder* BBinder::localBinder() { return this; }

所以寫入到Parcel的binder類型為BINDER_TYPE_BINDER,在SM中,當service的binder類型不為BINDER_TYPE_HANDLE時,SM將不會將此service添加到svclist,但是很顯然每個service的添加都是成功的,addService在開始傳遞的binder類型為BINDER_TYPE_BINDER,SM收到的binder類型為BINDER_TYPE_HANDLE,那麼這個過程當中究竟發生了什麼?這個問題是這樣的,在binder driver中(Binder.c)由以下代碼:

static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply){.. if (fp->type == BINDER_TYPE_BINDER) fp->type = BINDER_TYPE_HANDLE; else fp->type = BINDER_TYPE_WEAK_HANDLE; fp->handle = ref->desc;..}

由之前我們已經知道,SM只是保存了server binder的handle和name,那麼當client需要和某個service通訊的時候,如何獲得service的binder呢?接著看readStrongBinder

8.2 readStrongBinder

當server端收到client的調用請求之後,如果需要返回一個binder時,可以向BD發送這個binder,當IPCThreadState實例收到這個返回的Parcel時,client可以通過這個函數將這個被server返回的binder讀出。

sp<IBinder> Parcel::readStrongBinder() const{ sp<IBinder> val; unflatten_binder(ProcessState::self(), *this, &val); return val;}

再看unflatten_binder:

status_t unflatten_binder(const sp<ProcessState>& proc, const Parcel& in, sp<IBinder>* out){ const flat_binder_object* flat = in.readObject(false); if (flat) { switch (flat->type) { case BINDER_TYPE_BINDER: *out = reinterpret_cast<IBinder*>(flat->cookie); return finish_unflatten_binder(NULL, *flat, in); case BINDER_TYPE_HANDLE: *out = proc->getStrongProxyForHandle(flat->handle); return finish_unflatten_binder( static_cast<BpBinder*>(out->get()), *flat, in); } } return BAD_TYPE;}

發現如果server返回的binder類型為BINDER_TYPE_BINDER的話,也就是返回一個binder引用的話,直接獲取這個binder;如果server返回的binder類型為BINDER_TYPE_HANDLE時,也就是server返回的僅僅是binder的handle,那麼需要重新創建一個BpBinder返回給client。

有上面的代碼可以看出,SM保存的service的binder僅僅是一個handle,而client則是通過向SM獲得這個handle,從而重新構建代理binder與server通信。

這裡順帶提一下一種特殊的情況,binder通信的雙方即可作為client,也可以作為server.也就是說此時的binder通信是一個半雙工的通信。那麼在這種情況下,操作的過程會比單工的情況複雜,但是基本的原理是一樣的,有興趣可以分析一下MediaPlayer和MediaPlayerService的例子。

以上就是涉及到binder通訊的一些比較重要的點。關於binder的具體實現就需要查看binder driver的代碼了。

本文屬原創,轉載請註明出處,違者必糾。

關注微信公眾號:程式設計師互動聯盟(coder_online)

更有(java/C/C++/Linux/Android)高手幫你解決難題,和你互動,討論編程未來。

長按二維碼識別關注程式設計師互動聯盟

相關焦點

  • 助攻面試:圖解Android Binder機制
    為什麼要使用binder機制?binder機制又是怎樣運行的呢?這些問題只是了解binder機制是不夠的,需要從Android的整體系統出發來分析,在我找了很多資料後,真正的弄懂了binder機制,相信看完這篇文章大家也可以弄懂binder機制。
  • Android Systrace 基礎知識(10) - Binder 和鎖競爭解讀
    Systrace 這個工具,從另外一個角度來看待 Android 系統整體的運行,同時也從另外一個角度來對 Framework 進行學習。通信的時候,鎖競爭導致 binder 通信事件變長,影響了調用端。
  • 歷時幾個月,終於錄完Android binder,如釋重負
    今日更新:第8課第6節_Binder系統_JAVA實現_內部機制_Server端第8課第7節_回看SystemServer_硬體訪問服務及課後作業答案然後就是輸入、輸出系統;有了輸入輸出我們就可以從0移整個android了然後就是android文件系統
  • Android之Binder底層原理詳解必讀
    但我們在平時的開發過程中,可能很少用的。而Binder的整個體系結構又尤為複雜,一般很難通過網上的一兩篇博客,就能把Binder吃透,我們需要通過源碼及Binder的一些架構原理,來進行研究。後面的章節我們將主要通過3個部分來由淺至深來了解Binder。首先我們先看在實際的開發中怎麼來實現Binder通訊,接著分析Binder框架的原理,最後結合源碼進行分析。
  • 新課程上線 | 學習使用 Kotlin 進行 Android 開發的最佳時機!
    對於希望獲得新機遇的人而言,即使過去沒有編程經驗,也可以立即開始學習 Android。我們於 2016 年發布了 Android 基礎知識課程,該課程專為零編程經驗的學員打造,並且好評如潮。數萬名學員一邊構建自己的應用,一邊學習著 Android 開發和編程概念。
  • 圖文詳解 Android Binder跨進程通信的原理
    而 Binder的作用則是:連接 兩個進程,實現了mmap()系統調用,主要負責 創建數據接收的緩存空間 & 管理數據接收緩存b. 註:傳統的跨進程通信需拷貝數據2次,但 Binder機制只需1次,主要是使用到了內存映射,具體下面會詳細說明2.5 內存映射3.
  • Android-HIDL實例解析
    設計 HIDL 這個機制的目的,主要是想把**框架(framework)**與 HAL 進行隔離,使得框架部分可以直接被覆蓋、更新,而不需要重新對 HAL 進行編譯。HAL 的部分將會放在設備的 /vendor 分區中,並且是由設備供應商(vendors)或 SOC 製造商來構建。
  • Android學習(四) — 組件(一)
    通俗講就是我們在Android的App中看到的輸入框,文字(文本框),按鈕,菜單等,這些都是組件,本期我們來了解一部分組件,學習怎麼使用吧~ 」首先我們在Android Studio中新建一個項目,然後就可以開始今天的學習了(●ˇ∀ˇ●)
  • 一看就懂的Android APP開發入門教程
    工作中有做過手機App項目,前端和android或ios程式設計師配合完成整個項目的開發,開發過程中與ios程序配合基本沒什麼問題,而 android
  • 開發總結:Android反編譯方法的總結
    【IT168技術】對於軟體開發人員來說,保護代碼安全也是比較重要的因素之一,不過目前來說Google Android平臺選擇了Java Dalvik VM的方式使其程序很容易破解和被修改,首先APK文件其實就是一個MIME為ZIP的壓縮包,我們修改ZIP後綴名方式可以看到內部的文件結構,類似Sun JavaMe的Jar
  • Android新手入門-Android中文SDK
    understand the pieces that make up an Android app)入門指導:構建一個完整的Android程序 (Tutorial: Building a Full Android Application)入門指導文檔將帶領你通過構建一個真實的Android應用程式,一個記事本的創建、編輯、刪除內容.Android開發網提示涵蓋一些基本的概念通過實例
  • Android開發必備的「80」個開源庫
    地址 |  https://.jianshu.com/p/d6c384864329文末驚喜-贈送五本《第一行代碼》在github上大神整理得一份學習Android非常好得資源,分享給大家。Android 學習筆記https://github.com/CharonChui/AndroidNoteAndroid 開發中的日常積累https://github.com/lizhangqu/CoreLink/blob/master/README.mdAndroid-Tipshttps://github.com/
  • 【學習經驗】android開發的學習路線
    9.Java I/O輸入輸出流:File和FileRandomAccess類,字節流InputStream和OutputStream,字符流Reader和Writer,以及相應實現類,IO性能分析,字節和字符的轉化流,包裝流的概念,以及常用包裝類,計算機編碼。10.Java高級特性:反射、代理和泛型。
  • 從零開始學Android架構(一)——什麼是設計模式?
    前言不少人會覺得架構師是一個高大上的崗位,只有技術頂尖的人才能勝任,但其實它並沒有這麼高大上,大部分的架構師,都只是開發經驗非常豐富,並且熱愛學習
  • Android架構學習資料
    Android架構學習資料整理,總有一個適合你連結可以點擊閱讀原文獲取個人最近在嘗試
  • 【Android基礎學習一】Android 常用 adb 命令總結
    Android Debug Bridgeadb 其實就是 Android Debug Bridge, Android 調試橋的縮寫,adb 是一個 C/S 架構的命令行工具,主要由 3 部分組成:運行在 PC 端的 Client : 可以通過它對 Android 應用進行安裝、卸載及調試Eclipse 中的 ADT、
  • 經驗丨Android開發最佳實踐
    +'compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.你可以重複使用Fragments用戶接口來 組合成你的應用。我們強烈推薦使用Fragments而不是activity來呈現UI界面,理由如下:●提供多窗格布局解決方案 Fragments 的引入主要將手機應用延伸到平板電腦,所以在平板電腦上你可能有A、B兩個窗格,但是在手機應用上A、B可能分別充滿 整個屏幕。
  • [乾貨] 【譯】Android 開發規範與應用
    如果你對lambdas不熟悉,只需參照以下開始學習吧:當心dex方法數限制,同時避免使用過多的類庫 Android apps,當打包成一個dex文件時,有一個65535個應用方法強硬限制[1] [2] [3]。當你突破65k限制之後你會看到一個致命錯誤。
  • Android Support Library主要庫詳細介紹
    網上對Android Support Library中各個依賴包介紹的中文資料太少了,結合官方文檔和有限的參考資料做了一次總結,有描述得不對的地方還請指正。不可能去更新行動裝置中的android.jar吧,因為硬體設備集成的sdk版本是固定的,android.jar也是固定的,所以最好的方式是將新增的API以依賴包的形式集成到需要使用高版本API的應用程式中。  谷歌早已經考慮到了向後兼容的問題,所以推出了一系列脫離於android.jar的依賴包,比如常見的android-support-v4、appcompat-v7等。
  • Android 學習資料收集
    本文是我一位粉絲整理收集的Android學習資料,並已授權發布,簡直不能更全,所以特殊的日子給你們一份大禮。老規矩,回復「1024」獲取封面妹子圖。收集整理這份資料主要幫助初學者學習 Android 開發, 希望能快速幫助到他們快速入門, 找到適合自己學習資料, 節省再去收集學習資料時間.