Android10?Binder原理概述深入解析
IPC工具介紹
Binder作為Android 眾多的IPC通訊手段之一,在Framework的數(shù)據(jù)傳輸中起到極為關(guān)鍵的作用。為什么Google需要重新創(chuàng)造Binder這么一個(gè)IPC工具,使用linux默認(rèn)提供的Pipe、Socket、共享內(nèi)存、信號、消息隊(duì)列等IPC工具不行嗎?
答案是 這些傳統(tǒng)的linux IPC工具有一部分android也在使用,只是在某些場合下它們無法滿足需求,所以才創(chuàng)造了Binder這么一個(gè)工具。 為了更好地向各位讀者解釋為什么需要Binder,我們先來簡單地認(rèn)識一下linux傳統(tǒng)的IPC工具,讓大家對它們的優(yōu)勢和劣勢有一個(gè)更為直觀的認(rèn)識。
Pipe
管道是一種最基本的IPC工具,作用于有血緣關(guān)系的進(jìn)程之間,完成數(shù)據(jù)傳遞。調(diào)用pipe系統(tǒng)函數(shù)即可創(chuàng)建一個(gè)管道。它有如下特質(zhì):
- 其本質(zhì)是一個(gè)偽文件(實(shí)為內(nèi)核緩沖區(qū))
- 由兩個(gè)文件描述符引用,一個(gè)表示讀端,一個(gè)表示寫端。
- 規(guī)定數(shù)據(jù)從管道的寫端流入管道,從讀端流出,一般只能單向通信,雙向通信需建立兩個(gè)管道。
- 只能用于父子、兄弟進(jìn)程(有共同祖先)間通信。
- 數(shù)據(jù)一旦被讀走,便不在管道中存在,不可反復(fù)讀取。 因此,管道的局限性表現(xiàn)得非常明顯,它并不適合一對多的方式建立通訊(盡管技術(shù)上能夠?qū)崿F(xiàn)),原因在于第5條,管道中的數(shù)據(jù)無法反復(fù)讀取。類似的還有FIFO(命名管道),它在管道的基礎(chǔ)上做了升級,擺脫了第4條的共同祖先的限制,但仍要面臨一對多通訊的困境。
framework中有沒有使用Pipe進(jìn)行通訊?答案是有,但是用的很少,相比之下用的更多的是FIFO??!各位讀者如果感興趣的話,可以在源碼中搜一下 TransferPipe
這個(gè)類,在其中可以找到Pipe的痕跡。
Sign
信號是由用戶、系統(tǒng)或者進(jìn)程發(fā)送給目標(biāo)進(jìn)程的信息,以通知目標(biāo)進(jìn)程某個(gè)狀態(tài)的改變或系統(tǒng)異常。linux系統(tǒng)已經(jīng)預(yù)置了一部分信號標(biāo)識,它們都有著特殊的含義,部分信號如下所示:
- SIGHUP:本信號在用戶終端結(jié)束時(shí)發(fā)出,通常是在終端的控制進(jìn)程結(jié)束時(shí),通知同一會話期內(nèi)的各個(gè)作業(yè),這時(shí)他們與控制終端不在關(guān)聯(lián)。比如,登錄Linux時(shí),系統(tǒng)會自動(dòng)分配給登錄用戶一個(gè)控制終端,在這個(gè)終端運(yùn)行的所有程序,包括前臺和后臺進(jìn)程組,一般都屬于同一個(gè)會話。當(dāng)用戶退出時(shí),所有進(jìn)程組都將收到該信號,這個(gè)信號的默認(rèn)操作是終止進(jìn)程。此外對于與終端脫離關(guān)系的守護(hù)進(jìn)程,這個(gè)信號用于通知它重新讀取配置文件。
- SIGINT:程序終止信號。當(dāng)用戶按下CRTL+C時(shí)通知前臺進(jìn)程組終止進(jìn)程。
- SIGQUIT:Ctrl+\控制,進(jìn)程收到該信號退出時(shí)會產(chǎn)生core文件,類似于程序錯(cuò)誤信號。
- SIGILL:執(zhí)行了非法指令。通常是因?yàn)榭蓤?zhí)行文件本身出現(xiàn)錯(cuò)誤,或者數(shù)據(jù)段、堆棧溢出時(shí)也有可能產(chǎn)生這個(gè)信號。
- SIGTRAP:由斷點(diǎn)指令或其他陷進(jìn)指令產(chǎn)生,由調(diào)試器使用。
- SIGABRT:調(diào)用abort函數(shù)產(chǎn)生,將會使程序非正常結(jié)束。
- SIGBUS:非法地址。包括內(nèi)存地址對齊出錯(cuò)。比如訪問一個(gè)4個(gè)字長的整數(shù),但其地址不是4的倍數(shù)。它與SIGSEGV的區(qū)別在于后者是由于對合法地址的非法訪問觸發(fā)。
- SIGFPE:發(fā)生致命的算術(shù)運(yùn)算錯(cuò)誤。
- SIGKILL:用來立即結(jié)束程序的運(yùn)行。不能被捕捉、阻塞或忽略,只能執(zhí)行默認(rèn)動(dòng)作。
信號只能起到對進(jìn)程的通知作用,它無法發(fā)送復(fù)雜的數(shù)據(jù)類型,不適合用于進(jìn)程間的數(shù)據(jù)交換。
信號在整個(gè)framework中也扮演了極為重要的角色,各位讀者可以通過搜索sigemptyset
、sigaddset
等關(guān)鍵字,在源碼中找到它們的身影。
message queue
消息隊(duì)列,Unix的通信機(jī)制之一,可以理解為是一個(gè)存放消息(數(shù)據(jù))容器。將消息寫入消息隊(duì)列,然后再從消息隊(duì)列中取消息,一般來說是先進(jìn)先出的順序。消息隊(duì)列本質(zhì)上是位于內(nèi)核空間的鏈表,鏈表的每個(gè)節(jié)點(diǎn)都是一條消息。每一條消息都有自己的消息類型,消息類型用整數(shù)來表示,而且必須大于 0。每種類型的消息都被對應(yīng)的鏈表所維護(hù)。
其中數(shù)字 1 表示類型為 1 的消息,數(shù)字2、3、4 類似。彩色塊表示消息數(shù)據(jù),它們被掛在對應(yīng)類型的鏈表上。
消息隊(duì)列的缺陷在于:容量受到系統(tǒng)限制;消息隊(duì)列的發(fā)送方與接收方?jīng)]有強(qiáng)關(guān)聯(lián)性,容易造成發(fā)送方往消息隊(duì)列中存放了消息,沒有接收方來取消息或接收方?jīng)]有及時(shí)取消息的問題,消息的及時(shí)性無法保障。
目前在Android 10的非內(nèi)核源碼范圍內(nèi),沒有發(fā)現(xiàn)使用消息隊(duì)列。
shared memory
共享內(nèi)存就是映射一段能被其他進(jìn)程所訪問的內(nèi)存,這段共享內(nèi)存由一個(gè)進(jìn)程創(chuàng)建,但多個(gè)進(jìn)程都可以訪問。共享內(nèi)存是最快的 IPC 方式,它是針對其他進(jìn)程間通信方式運(yùn)行效率低而專門設(shè)計(jì)的。它往往與其他通信機(jī)制,如信號量,配合使用,來實(shí)現(xiàn)進(jìn)程間的同步和通信。
共享內(nèi)存利用內(nèi)存緩沖區(qū)直接交換信息,無須復(fù)制,快捷、信息量大是其優(yōu)點(diǎn)。但是共享內(nèi)存的通信方式是通過將共享的內(nèi)存緩沖區(qū)直接附加到進(jìn)程的虛擬地址空間中來實(shí)現(xiàn)的,因此,這些進(jìn)程之間的讀寫操作的同步問題操作系統(tǒng)無法實(shí)現(xiàn)。必須由各進(jìn)程利用其他同步工具解決,開發(fā)上手難度較高,容易出錯(cuò),且存在數(shù)據(jù)安全隱患。
目前在Android 10的非內(nèi)核源碼范圍內(nèi),沒有發(fā)現(xiàn)使用共享內(nèi)存。
Socket
Socket這個(gè)不需要特別介紹了,不管是做C++開發(fā)還是Java開發(fā),都會涉及到套接字編程。相對其他的IPC方式,Socket是最適合做一對多這種通訊需求的。它的問題在于數(shù)據(jù)需要經(jīng)過兩次拷貝,通訊效率相對低下。這個(gè)問題在電腦等設(shè)備上都不是什么特別大的問題,但考慮到Android搭載的移動(dòng)設(shè)備,尤其是早期的移動(dòng)設(shè)備,這個(gè)問題就很致命了。
framework中當(dāng)然也存在Socket的使用痕跡,比如 system/core/init/init.cpp
這個(gè)文件中就采用epoll機(jī)制,實(shí)現(xiàn)init進(jìn)程與其子進(jìn)程的通訊。
Android更看重的是效率和一對多通訊的問題,無法采用傳統(tǒng)的IPC工具實(shí)現(xiàn),所以只能考慮自己另起爐灶。除此之外,傳統(tǒng)的IPC無法獲得對方進(jìn)程的PID\UID,從而無法鑒別對象的身份,從而會使Android系統(tǒng)的安全性無法得到保證(ps:無法獲得對方進(jìn)程的身份指的是Linux默認(rèn)沒有提供獲取通訊進(jìn)程的身份的接口,并不是說采用傳統(tǒng)IPC沒有辦法實(shí)現(xiàn)這樣的安全管控需求,只是谷歌在綜合考慮了上述所有的因素的情況下,在共享內(nèi)存的基礎(chǔ)上做了一套新的解決方案)。
這里給各位讀者留個(gè)思考題,有興趣的讀者可以自己動(dòng)手去實(shí)驗(yàn)一下:
在一對多通訊的場景下,Binder的傳輸效率一定會比Socket高嗎?(提示:Socket包括BIO、NIO、NIO2、epoll等,請不要局限在BIO的通訊方式)
AIDL
AIDL 是 Android interface definition Language 的英文縮寫, 意思Android 接口定義語言,它與Binder有著千絲萬縷的聯(lián)系。
AIDL是谷歌使用Java 編程語言的語法定義的專門服務(wù)于Binder IPC通訊的腳本語言,推出的根本原因是為了避免Binder通訊中大量模板代碼的書寫。AIDL腳本會在編譯期間,由Android SDK 工具生成基于該 .aidl 文件的 IBinder 接口,并將其保存到項(xiàng)目的 generated/ 目錄中。
我們來看一個(gè)簡單的aidl文件:
packageackage com.example.commonservice; // Declare any non-default types here with import statements interface ITtsService { void showTts(in String uid,in int textId,in boolean toPlayTts,in int type); boolean isShowing(); }
它生成的java文件如下所示:
/* * This file is auto-generated. DO NOT MODIFY. */ package com.example.commonservice; // Declare any non-default types here with import statements public interface ITtsService extends android.os.IInterface { /** Default implementation for ITtsService. */ public static class Default implements com.example.commonservice.ITtsService { @Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException { } @Override public boolean isShowing() throws android.os.RemoteException { return false; } @Override public android.os.IBinder asBinder() { return null; } } /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService { private static final java.lang.String DESCRIPTOR = "com.example.commonservice.ITtsService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.commonservice.ITtsService interface, * generating a proxy if needed. */ public static com.example.commonservice.ITtsService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.example.commonservice.ITtsService))) { return ((com.example.commonservice.ITtsService)iin); } return new com.example.commonservice.ITtsService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_showTts: { data.enforceInterface(descriptor); java.lang.String _arg0; _arg0 = data.readString(); int _arg1; _arg1 = data.readInt(); boolean _arg2; _arg2 = (0!=data.readInt()); int _arg3; _arg3 = data.readInt(); this.showTts(_arg0, _arg1, _arg2, _arg3); reply.writeNoException(); return true; } case TRANSACTION_isShowing: { data.enforceInterface(descriptor); boolean _result = this.isShowing(); reply.writeNoException(); reply.writeInt(((_result)?(1):(0))); return true; } default: { return super.onTransact(code, data, reply, flags); } } } private static class Proxy implements com.example.commonservice.ITtsService { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(uid); _data.writeInt(textId); _data.writeInt(((toPlayTts)?(1):(0))); _data.writeInt(type); boolean _status = mRemote.transact(Stub.TRANSACTION_showTts, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { getDefaultImpl().showTts(uid, textId, toPlayTts, type); return; } _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public boolean isShowing() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); boolean _result; try { _data.writeInterfaceToken(DESCRIPTOR); boolean _status = mRemote.transact(Stub.TRANSACTION_isShowing, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { return getDefaultImpl().isShowing(); } _reply.readException(); _result = (0!=_reply.readInt()); } finally { _reply.recycle(); _data.recycle(); } return _result; } public static com.example.commonservice.ITtsService sDefaultImpl; } static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); public static boolean setDefaultImpl(com.example.commonservice.ITtsService impl) { if (Stub.Proxy.sDefaultImpl == null && impl != null) { Stub.Proxy.sDefaultImpl = impl; return true; } return false; } public static com.example.commonservice.ITtsService getDefaultImpl() { return Stub.Proxy.sDefaultImpl; } } public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException; public boolean isShowing() throws android.os.RemoteException; }
雖然是簡短的一個(gè)aidl文件,但生成的模板代碼卻極為復(fù)雜,為了整理清楚這段代碼的結(jié)構(gòu),筆者先隱藏其部分內(nèi)容:
public interface ITtsService extends android.os.IInterface { /** Default implementation for ITtsService. */ public static class Default implements com.example.commonservice.ITtsService { } /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.commonservice.ITtsService { private static class Proxy implements com.example.commonservice.ITtsService { } static final int TRANSACTION_showTts = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_isShowing = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public void showTts(java.lang.String uid, int textId, boolean toPlayTts, int type) throws android.os.RemoteException; public boolean isShowing() throws android.os.RemoteException; }
可以看到,代碼的結(jié)構(gòu)如同套娃一樣,一層接一層。各位讀者不妨思考一下,如果不這樣套娃,把ITtsService
里的Default
和Stub
類移到外面來,這樣做可不可以?
答案是,可以的,不過類的命名方式可能要稍微做一下修改,如ITtsService_Defalut
、ITtsService_Stub
,以便于引用上的區(qū)分。當(dāng)然,代碼的結(jié)構(gòu)不是重點(diǎn),雖然谷歌的方式可讀性會差一點(diǎn),但開發(fā)人員不需要直接和這些源碼打交道,也不是不可以接受。
Binder不一定都是跨進(jìn)程通訊,同樣也支持同進(jìn)程通訊,比如 Activity綁定Service,通過Binder實(shí)現(xiàn)數(shù)據(jù)傳輸。在同一進(jìn)程通訊的情況下,Stub
類身兼兩職,因其implements了ITtsService
,它可以作為客戶端的調(diào)用方;同時(shí),它也是服務(wù)端的實(shí)現(xiàn)方。而在跨進(jìn)程通訊的情況下,則由Proxy
來擔(dān)任客戶端的調(diào)用方。
至于Default
這個(gè)類的作用,暫時(shí)不明,無法找到相關(guān)的資料得知為什么谷歌要生成這么一個(gè)類。
在此,AIDL的介紹先告一段落,其中更多的細(xì)節(jié)將放到后續(xù)的文章中再做補(bǔ)充。
HIDL
HIDL的生命周期及其短暫,它從Android 8引入,然后在Android 10 立馬被 Stable AIDL 所取代,雖然沒啥存在感,但還是簡單地提及一下吧。
HAL 接口定義語言(簡稱 HIDL,發(fā)音為“hide-l”)是用于指定 HAL 和其用戶之間的接口的一種接口描述語言 (IDL)。HIDL 允許指定類型和方法調(diào)用(會匯集到接口和軟件包中)。從更廣泛的意義上來說,HIDL 是指用于在可以獨(dú)立編譯的代碼庫之間進(jìn)行通信的系統(tǒng)。
HIDL 旨在用于進(jìn)程間通信 (IPC)。進(jìn)程之間的通信采用 Binder 機(jī)制。對于必須與進(jìn)程相關(guān)聯(lián)的代碼庫,還可以使用直通模式(在 Java 中不受支持)。
更多HIDL相關(guān)的資料,可以參考 source.android.google.cn/docs/core/a…
以上就是Android10 Binder原理概述深入解析的詳細(xì)內(nèi)容,更多關(guān)于Android10 Binder原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
LayoutAnimation給ListView中的item設(shè)置動(dòng)態(tài)出場效果(實(shí)例)
下面小編就為大家?guī)硪黄狶ayoutAnimation給ListView中的item設(shè)置動(dòng)態(tài)出場效果(實(shí)例)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10詳談OnTouchListener與OnGestureListener的區(qū)別
下面小編就為大家?guī)硪黄斦凮nTouchListener與OnGestureListener的區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04Android開發(fā)實(shí)現(xiàn)自動(dòng)切換文字TextSwitcher功能示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)自動(dòng)切換文字TextSwitcher功能,結(jié)合實(shí)例形式詳細(xì)分析了Android使用TextSwitcher實(shí)現(xiàn)文字自動(dòng)切換的原理、實(shí)現(xiàn)方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-03-03Android Viewpager實(shí)現(xiàn)無限循環(huán)輪播圖
這篇文章主要為大家詳細(xì)介紹了Android Viewpager實(shí)現(xiàn)無限循環(huán)輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11Android組件Activity的啟動(dòng)過程深入分析
這篇文章主要介紹了Android組件Activity的啟動(dòng)過程,Activity作為Android四大組件之一,他的啟動(dòng)沒有那么簡單。這里涉及到了系統(tǒng)服務(wù)進(jìn)程,啟動(dòng)過程細(xì)節(jié)很多,這里我只展示主體流程。activity的啟動(dòng)流程隨著版本的更替,代碼細(xì)節(jié)一直在進(jìn)行更改,每次都會有很大的修改2023-04-04