IPC機制之Binder

寫在前面:

本文摘自《Android開發藝術探索》。

什麼是Binder?

Binder是Android中的一個類,實現了IBinder接口。

從IPC(進程間通信)角度講,Binder是Android中的一種跨進程通信方式,Binder可以理解爲一種虛擬的物理設備,它的驅動是/dev/binder,該通信方式在Linux中沒有;

從Android Framework角度講,Binder是ServiceManager連接各種Manger(ActivityManager、WindowManager,等等)和相應ManagerService的橋樑;

從Android 應用層將,Binder是客戶端和服務端進行通信的媒介,當bindService時,服務器會返回一個包含服務業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。


什麼時候使用Binder?

Binder主要使用在Service中,包括AIDL和Messenger(Messenger的底層其實是AIDL)。


什麼是AIDL?

AIDL:Android Interface Definition Language,安卓接口定義語言。

Android系統中的進程之間不能共享內存,因此,需要提供一些機制在不同進程之間進行數據通信。Learn more>>

通俗地講,AIDL爲我們提供一種快速實現Binder的工具。(後續會說到)

AIDL文件代碼如下:

// Book.aidl
package com.cooffee.studypro.aidl;

parcelable Book;
// IBookManager.aidl
package com.cooffee.studypro.aidl;

import com.cooffee.studypro.aidl.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

系統自動幫我們生成IBookManager接口的java代碼文件,代碼結構如下:

interface IBookManager extends IInterface {
    
        abstract class Stub implements IBookManager {
            asInterface() {...};
            asBinder() {...};
            onTransact() {...};
            
            abstract class Proxy implements IBookManager {
                asBinder() {...};
                getInterfaceDescriptor() {...};
                getBookList() {...};
                addBook() {...};
            }
        }
    
    getBookList();  // 抽象方法
    addBook();      // 抽象方法
    
    // 兩個方法標記ID的聲明...
}


IBookManager接口的核心是它的內部類Stub和Stub的內部代理類Proxy。

下面詳細介紹針對這兩個類的每個方法的定義:

DESCRIPTOR:

Binder的唯一標識,一般用當前Binder的類名標識,比如“com.example.aidl.IBookManager”。

asInterface(android.os.IBinder obj):

將服務器端的Binder對象轉換成客戶端所需的AIDL接口類型的對象。

該轉換過程是區分進程的:

如果客戶端和服務端位於統一進程,此方法返回服務端的Stub對象本身;

否則返回的是系統封裝的Stub.proxy對象。

asBinder():

用於返回當前Binder對象。

onTransact():

此方法運行在服務器端的Binder線程池,當客戶端發起跨進程請求時,遠程請求會通過系統低層封裝後交由次方法來處理。

原型:

public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)。

code:服務器通過code,確定請求的目標方法是什麼。

data:從data中取出目標方法所需的參數(如果目標方法有參數),然後執行目標方法。(感覺像:方法執行的入口)

reply:向reply寫入返回值(如果請求的方法有返回值的話)。(感覺像:方法執行的出口)

注意:如果此方法返回false,那麼客戶端的請求會失敗,因此我們利用這個特性來做權限驗證,畢竟我們也不希望隨便一個進程都能遠程調用我們的服務。

Proxy#getBookList:

此方法運行在客戶端,當客戶端遠程調用此方法時,其內部實現如下:

創建該方法的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象List;
把該方法的參數信息寫入_data(如果有參數);
調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起;
服務器端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並從_reply中取出RPC過程的返回結果;
返回reply中的數據。

Proxy#addBook:

執行過程與getBookList過程相同,但是該方法沒有返回值。


說明一下:

1. 當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是耗時的,不能在UI線程發起此遠程請求

2. 由於服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該採用同步的方式實現,因爲它已經運行在一個線程中了。



Binder的工作機制

從上圖我們可以看出,實現Binder的工作機制中並沒有出現我們當初建立的AIDL文件,之所以提供AIDL文件,是爲了方便系統爲我們生成代碼(IBookManager接口及其內部類)。


如果我們客戶端遠程請求工程中,中途斷掉了怎麼辦?

Binder運行在服務端進程中,如果服務端進程由於某種原因異常終止,這個時候我們到服務端的Binder連接斷裂(稱之爲Binder死亡),會導致我們的遠程調用失敗。而且我們不知道Binder連接是什麼時候斷裂的,那麼客戶端的功能會受到影響。

Binder提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以爲Binder設置一個死亡代理,當Binder死亡時,我們會收到通知,這個時候我們就可以重新發起連接請求從而恢復連接。


首先,聲明一個DeathRecipient對象。DeathRecipient是一個接口,其內部只有一個方法binderDied,我們需要實現這個方法,當Binder死亡的時候,系統就會回調binderDied方法,然後我們就可以移出之前綁定的binder代理並重新綁定遠程服務:

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (mBookManager == null)
                return;
            mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mBookManager - null;
            // TODO: 重新綁定遠程service
        }
    };

其次,在客戶端綁定遠程服務成功後,給binder設置死亡代理:

mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);

其中linkToDeath的第二個參數是個標記位,我們直接設爲0即可。

另外,Binder的方法isBinderAlive也可以判斷Binder是否死亡。


附錄:

AIDL文件生成IBookManager接口代碼

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Volumes/J_Eric/J_workRoom/AndroidStudio/StudyPro/app/src/main/aidl/com/cooffee/studypro/aidl/IBookManager.aidl
 */
package com.cooffee.studypro.aidl;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.cooffee.studypro.aidl.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.cooffee.studypro.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.cooffee.studypro.aidl.IBookManager interface,
 * generating a proxy if needed.
 */
public static com.cooffee.studypro.aidl.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.cooffee.studypro.aidl.IBookManager))) {
return ((com.cooffee.studypro.aidl.IBookManager)iin);
}
return new com.cooffee.studypro.aidl.IBookManager.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
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.cooffee.studypro.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.cooffee.studypro.aidl.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.cooffee.studypro.aidl.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.cooffee.studypro.aidl.IBookManager
{
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 java.util.List<com.cooffee.studypro.aidl.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.cooffee.studypro.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.cooffee.studypro.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.cooffee.studypro.aidl.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.cooffee.studypro.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.cooffee.studypro.aidl.Book book) throws android.os.RemoteException;
}


發佈了22 篇原創文章 · 獲贊 16 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章