Android系統中,涉及到多進程間的通訊底層都是依賴於Binder IPC機制。例如當進 程A中的Activity要向進程B中的Service通訊,這便須要依賴於Binder IPC。不只於 此,整個Android系統架構中,大量採用了Binder機制做爲IPC(進程間通訊, Interprocess Communication
)方案。 android
固然也存在部分其餘的IPC方式,如管道、SystemV、Socket等。那麼Android爲什 麼不使用這些原有的技術,而是要使開發一種新的叫Binder的進程間通訊機制呢?git
(順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)github
爲何要使用Binder?
性能方面 在移動設備上(性能受限制的設備,好比要省電),普遍地使用跨進程通訊對通訊 機制的性能有嚴格的要求,Binder相對於傳統的Socket方式,更加高效。Binder數 據拷貝只須要一次,而管道、消息隊列、Socket都須要2次,共享內存方式一次內 存拷貝都不須要,但實現方式又比較複雜。面試
安全方面
傳統的進程通訊方式對於通訊雙方的身份並無作出嚴格的驗證,好比Socket通訊 的IP地址是客戶端手動填入,很容易進行僞造。然而,Binder機制從協議自己就支 持對通訊雙方作身份校檢,從而大大提高了安全性。安全
IPC原理
從進程角度來看IPC(Interprocess Communication
)機制
每一個Android的進程,只能運行在本身進程所擁有的虛擬地址空間。例如,對應一 個4GB的虛擬地址空間,其中3GB是用戶空間,1GB是內核空間。固然內核空間的 大小是能夠經過參數配置調整的。對於用戶空間,不一樣進程之間是不能共享的,而 內核空間倒是可共享的。Client進程向Server進程通訊,偏偏是利用進程間可共享 的內核內存空間來完成底層通訊工做的。Client端與Server端進程每每採用ioctl等方 法與內核空間的驅動進行交互。架構
Binder原理
Binder通訊採用C/S架構,從組件視角來講,包含Client、Server、ServiceManager 以及Binder驅動,其中ServiceManager用於管理系統中的各類服務。架構圖以下所 示:
Binder通訊的四個角色 ide
Client進程: 使用服務的進程。
Server進程: 提供服務的進程。
ServiceManager進程: ServiceManager的做用是將字符形式的Binder名字轉化成 Client中對該Binder的引用,使得Client可以經過Binder名字得到對Server中Binder 實體的引用。
Binder驅動: 驅動負責進程之間Binder通訊的創建,Binder在進程之間的傳遞, Binder引用計數管理,數據包在進程之間的傳遞和交互等一系列底層支持。函數
Binder運行機制
圖中Client/Server/ServiceManage
之間的相互通訊都是基於Binder機制。既然基於 Binder機制通訊,那麼一樣也是C/S架構,則圖中的3大步驟都有相應的Client端與 Server端。佈局
註冊服務(addService
): Server進程要先註冊Service到ServiceManager
。該過 程:Server是客戶端,ServiceManager
是服務端。
獲取服務(getService
): Client進程使用某個Service前,須先向ServiceManager
中 獲取相應的Service。該過程:Client是客戶端,ServiceManager
是服務端。
使用服務: Client根據獲得的Service信息創建與Service所在的Server進程通訊的通 路,而後就能夠直接與Service交互。該過程:Client是客戶端,Server是服務端。性能
圖中的Client
,Server
,Service Manager
之間交互都是虛線表示,是因爲它們彼此 之間不是直接交互的,而是都經過與Binder驅動進行交互的,從而實現IPC通訊 (Interprocess Communication
)方式。其中Binder驅動位於內核空間,Client
, Server
,Service Manager
位於用戶空間。Binder驅動和Service Manager
能夠看作 是Android平臺的基礎架構,而Client和Server是Android的應用層,開發人員只需自 定義實現Client、Server端,藉助Android的基本平臺架構即可以直接進行IPC通 信。
Binder運行的實例解釋
首先咱們看看咱們的程序跨進程調用系統服務的簡單示例,實現浮動窗口部分代 碼:
//獲取WindowManager服務引用 WindowManager wm = (WindowManager) getSystemService(getApplicati on().WINDOW_SERVICE); //佈局參數layoutParams相關設置略... View view = LayoutInflater.from(getApplication()).inflate(R.layo ut.float_layout, null); //添加view wm.addView(view, layoutParams);
註冊服務(addService
): 在Android開機啓動過程當中,Android會初始化系統的各類 Service,並將這些Service向ServiceManager
註冊(即讓ServiceManager
管理)。 這一步是系統自動完成的。
獲取服務(getService
): 客戶端想要獲得具體的Service直接向ServiceManager
要 便可。客戶端首先向ServiceManager
查詢獲得具體的Service引用,一般是Service 引用的代理對象,對數據進行一些處理操做。即第2行代碼中,獲得的wm是 WindowManager
對象的引用。
使用服務: 經過這個引用向具體的服務端發送請求,服務端執行完成後就返回。即 第6行調用WindowManager
的addView
函數,將觸發遠程調用,調用的是運行在 systemServer
進程中的WindowManager
的addView
函數。
使用服務的具體執行過程
- Client經過得到一個Server的代理接口,對Server進行調用。
- 代理接口中定義的方法與Server中定義的方法是一一對應的。
- Client調用某個代理接口中的方法時,代理接口的方法會將Client傳遞的參數打 包成Parcel對象。
- 代理接口將Parcel發送給內核中的Binder Driver。
- Server會讀取Binder Driver中的請求數據,若是是發送給本身的,解包Parcel 對象,處理並將結果返回。
- 整個的調用過程是一個同步過程,在Server處理的時候,Client會Block住。因 此Client調用過程不該在主線程。
AIDL (Android Interface Definition Language) 是一種接口定義語言,用於生成能夠 在Android設備上兩個進程之間進行進程間通訊(Interprocess
Communication
, IPC
) 的代碼。若是在一個進程中(例如Activity)要調用另外一個進程中(例如Service) 對象的操做,就可使用AIDL生成可序列化的參數,來完成進程間通訊。
簡言之,AIDL可以實現進程間通訊,其內部是經過Binder機制來實現的,後面會 具體介紹,如今先介紹AIDL的使用。
AIDL的實現一共分爲三部分,一部分是客戶端,調用遠程服務。一部分是服務端, 提供服務。最後一部分,也是最關鍵的是AIDL接口,用來傳遞的參數,提供進程間 通訊。
先在服務端建立AIDL部分代碼。
AIDL文件 經過以下方式新建一個AIDL文件
默認生成格式
interface IBookManager { /** * Demonstrates some basic types that you can use as paramet ers * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, flo at aFloat, double aDouble, String aString); }
默認以下格式,因爲本例要操做Book類,實現兩個方法,添加書本和返回書本列 表。
定義一個Book類,實現Parcelable
接口。
public class Book implements Parcelable { public int bookId; public String bookName; public Book() { } public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } public int getBookId() { return bookId; } public void setBookId(int bookId) { this.bookId = bookId; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(this.bookId); dest.writeString(this.bookName); } protected Book(Parcel in) { this.bookId = in.readInt(); this.bookName = in.readString(); } public static final Parcelable.Creator<Book> CREATOR = new P arcelable.Creator<Book>() { @Override public Book createFromParcel(Parcel source) { return new Book(source); } @Override public Book[] newArray(int size) { return new Book[size]; } }; }
因爲AIDL只支持數據類型:基本類型(int,long,char,boolean等),String, CharSequence,List,Map,其餘類型必須使用import導入,即便它們可能在同一 個包裏,好比上面的Book。 最終IBookManager.aidl
的實現
// Declare any non-default types here with import statements import com.lvr.aidldemo.Book; interface IBookManager { /** * Demonstrates some basic types that you can use as paramet ers * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, flo at aFloat, double aDouble, String aString); void addBook(in Book book); List<Book> getBookList(); }
注意: 若是自定義的Parcelable
對象,必須建立一個和它同名的AIDL文件,並在其 中聲明它爲parcelable
類型。
Book.aidl
// Book.aidl package com.lvr.aidldemo; parcelable Book;
以上就是AIDL部分的實現,一共三個文件。 而後Make Project ,SDK爲自動爲咱們生成對應的Binder類。 在以下路徑下:
其中該接口中有個重要的內部類Stub ,繼承了Binder 類,同時實現了 IBookManager
接口。 這個內部類是接下來的關鍵內容。
public static abstract class Stub extends android.os.Binder impl ements com.lvr.aidldemo.IBookManager{}
服務端 服務端首先要建立一個Service用來監聽客戶端的鏈接請求。而後在Service 中實現Stub 類,並定義接口中方法的具體實現。
//實現了AIDL的抽象函數 private IBookManager.Stub mbinder = new IBookManager.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoole an, float aFloat, double aDouble, String aString) throws RemoteE xception { //什麼也不作 } @Override public void addBook(Book book) throws RemoteException { //添加書本 if (!mBookList.contains(book)) { mBookList.add(book); } } @Override public List<Book> getBookList() throws RemoteException { return mBookList; } };
當客戶端鏈接服務端,服務端就會調用以下方法:
public IBinder onBind(Intent intent) { return mbinder; }
就會把Stub實現對象返回給客戶端,該對象是個Binder對象,能夠實現進程間通 信。 本例就不真實模擬兩個應用之間的通訊,而是讓Service另外開啓一個進程來 模擬進程間通訊。
<service android:name=".MyService" android:process=":remote"> <intent-filter> <category android:name="android.intent.category.DEFAULT" /> <action android:name="com.lvr.aidldemo.MyService" /> </intent-filter> </service>
android:process=":remote"
設置爲另外一個進程。 <action android:name="com.lvr.aidldemo.MyService"/>
是爲了能讓其餘apk隱式 bindService。經過隱式調用的方式來鏈接service,須要把category設爲default, 這是由於,隱式調用的時候,intent中的category默認會被設置爲default。
客戶端
首先將服務端工程中的aidl文件夾下的內容整個拷貝到客戶端工程的對應位置下, 因爲本例的使用在一個應用中,就不須要拷貝了,其餘狀況必定不要忘記這一步。
客戶端須要作的事情比較簡單,首先須要綁定服務端的Service。
Intent intentService = new Intent(); intentService.setAction("com.lvr.aidldemo.MyService"); intentService.setPackage(getPackageName()); intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); MyClient.this.bindService(intentService, mServiceConnection, BIN D_AUTO_CREATE); Toast.makeText(getApplicationContext(), "綁定了服務", Toast.LENGTH _SHORT).show();
將服務端返回的Binder對象轉換成AIDL接口所屬的類型,接着就能夠調用AIDL中的 方法了。
if (mIBookManager != null) { try { mIBookManager.addBook(new Book(18, "新添加的書")); Toast.makeText(getApplicationContext(), mIBookManager.ge tBookList().size() + "", Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } }
Binder機制的運行主要包括三個部分:註冊服務、獲取服務和使用服務。 其中註冊 服務和獲取服務的流程涉及C的內容,因爲我的能力有限,就不予介紹了。
本篇文章主要介紹使用服務時,AIDL的工做原理。
①.Binder對象的獲取
Binder是實現跨進程通訊的基礎,那麼Binder對象在服務端和客戶端是共享的,是 同一個Binder對象。在客戶端經過Binder對象獲取實現了IInterface接口的對象來調 用遠程服務,而後經過Binder來實現參數傳遞。
那麼如何維護實現了IInterface
接口的對象和獲取Binder對象呢?
服務端獲取Binder對象並保存IInterface
接口對象 Binder中兩個關鍵方法:
public class Binder implement IBinder { void attachInterface(IInterface plus, String descriptor) IInterface queryLocalInterface(Stringdescriptor) //從IBinder 中繼承而來 ...... }
Binder具備被跨進程傳輸的能力是由於它實現了IBinder接口。系統會爲每一個實現了 該接口的對象提供跨進程傳輸,這是系統給咱們的一個很大的福利。
Binder具備的完成特定任務的能力是經過它的IInterface
的對象得到的,咱們能夠 簡單理解attachInterface
方法會將(descriptor,plus)做爲(key,value)對存入 Binder對象中的一個Map對象中,Binder對象可經過attachInterface
方法持有一個 IInterface
對象(即plus)的引用,並依靠它得到完成特定任務的能力。 queryLocalInterface
方法能夠認爲是根據key值(即參數 descriptor)查找相應的 IInterface
對象。 在服務端進程,經過實現 private IBookManager.Stub mbinder = new IBookManager.Stub() {}
抽象類,得到Binder對象。 並保存了IInterface
對象。
public Stub() { this.attachInterface(this, DESCRIPTOR); }
客戶端獲取Binder對象並獲取IInterface
接口對象 經過bindService
得到Binder對象
MyClient.this.bindService(intentService, mServiceConnection, BIN D_AUTO_CREATE);
而後經過Binder對象得到IInterface
對象。
private ServiceConnection mServiceConnection = new ServiceConnec tion() { @Override public void onServiceConnected(ComponentName name, IBinder b inder) { //經過服務端onBind方法返回的binder對象獲得IBookManager的實例, 獲得實例就能夠調用它的方法了 mIBookManager = IBookManager.Stub.asInterface(binder); } @Override public void onServiceDisconnected(ComponentName name) { mIBookManager = null; } };
其中 asInterface(binder)
方法以下:
public static com.lvr.aidldemo.IBookManager asInterface(android. os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPT OR); if (((iin != null) && (iin instanceof com.lvr.aidldemo.IBook Manager))) { return ((com.lvr.aidldemo.IBookManager) iin); } return new com.lvr.aidldemo.IBookManager.Stub.Proxy(obj); }
先經過 queryLocalInterface(DESCRIPTOR);
查找到對應的IInterface
對象,而後 判斷對象的類型,若是是同一個進程調用則返回IBookManager
對象,因爲是跨進 程調用則返回Proxy對象,即Binder類的代理對象。
②.調用服務端方法
得到了Binder類的代理對象,而且經過代理對象得到了IInterface
對象,那麼就能夠 調用接口的具體實現方法了,來實現調用服務端方法的目的。 以addBook
方法爲例,調用該方法後,客戶端線程掛起,等待喚醒:
@Override public void addBook(com.lvr.aidldemo.Book book) th rows android.os.RemoteException { .......... //第一個參數:識別調用哪個方法的ID //第二個參數:Book的序列化傳入數據 //第三個參數:調用方法後返回的數據 //最後一個不用管 mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply , 0); _reply.readException(); } .......... }
省略部分主要完成對添加的Book對象進行序列化工做,而後調用 transact 方 法。
Proxy對象中的transact調用發生後,會引發系統的注意,系統意識到Proxy對象想 找它的真身Binder對象(系統其實一直存着Binder和Proxy的對應關係)。因而系統 將這個請求中的數據轉發給Binder對象,Binder對象將會在onTransact
中收到Proxy 對象傳來的數據,因而它從data中取出客戶端進程傳來的數據,又根據第一個參數 肯定想讓它執行添加書本操做,因而它就執行了響應操做,並把結果寫回reply。代 碼概略以下:
case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.lvr.aidldemo.Book _arg0; if ((0 != data.readInt())) { _arg0 = com.lvr.aidldemo.Book.CREATOR.createFromParcel(d ata); } else { _arg0 = null; } //這裏調用服務端實現的addBook方法 this.addBook(_arg0); reply.writeNoException(); return true; }
而後在 transact 方法得到 _reply 並返回結果,本例中的addList方法沒有返回 值。
客戶端線程被喚醒。所以調用服務端方法時,應開啓子線程,防止UI線程堵塞,導 致ANR。
(順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)