前言
稍微看過Android FrameWork層的人應該都知道Binder,因為app與系統服務之間的通信基本上都是建立在Binder的基礎上。之前對Binder也是云里霧里,似懂非懂,于是花了不少時間,看了很多資料和源碼,才大致了解了Binder通信的原理,總結出來,如有錯誤,還望指正。
簡介
Binder是什么?Binder是為跨進程通信而生的產物。眾所周知,我們的app都是由Zygote進程fork出來的,每個app都運行在單獨的進程中,一個app想與另外一個app進行通信,只能采用跨進程的方式,傳統的Linux跨進程方式有如下幾種:管道、信號量、共享內存及Socket等,Android系統建立在Linux的基礎上,但其采用的是Binder來進行IPC的。
接下來讓我們用一個小Demo展示一下如何用Binder進行跨進程通信。
實例
既然是跨進程通信,那么至少得有兩個進程,最簡單的方式就是指定activity的process屬性,但是本文為了更清楚的講解binder,采用的方式是建立兩個app,一個作為服務端,一個作為客戶端。服務端提供一個簡單的生詞本功能,客戶端可以向服務端插入和查詢生詞。
使用Binder通信主要有以下幾個步驟:
Step1:編寫AIDL文件
想要使用Binder,必須要先了解AIDL(Android Interface Definition Language),也就是接口定義語言,提供接口給遠程調用者。
為了給客戶端提供生詞本的調用接口,我們在/src/main目錄下先新建一個文件夾aidl,并新建一個aidl文件IDictionaryManager.aidl。
// IDictionaryManager.aidl
package com.wanginbeijing.dictionaryserver;
interface IDictionaryManager {
void add(String chinese,String english);
String query(String chinese);
}
接口中提供了兩個方法:add()和query(),分別作為插入和查詢操作。Build一下工程,android studio會自動為我們生成一個java類:IDictionaryManager.java。
我們來看看這個java類里面都寫了什么
public interface IDictionaryManager extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
private static final java.lang.String DESCRIPTOR = "com.wanginbeijing.dictionaryserver.IDictionaryManager";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static com.wanginbeijing.dictionaryserver.IDictionaryManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.wanginbeijing.dictionaryserver.IDictionaryManager))) {
return ((com.wanginbeijing.dictionaryserver.IDictionaryManager) iin);
}
return new com.wanginbeijing.dictionaryserver.IDictionaryManager.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_add: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _arg1;
_arg1 = data.readString();
this.add(_arg0, _arg1);
reply.writeNoException();
return true;
}
case TRANSACTION_query: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _result = this.query(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
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 add(java.lang.String chinese, java.lang.String english) 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(chinese);
_data.writeString(english);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.lang.String query(java.lang.String chinese) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(chinese);
mRemote.transact(Stub.TRANSACTION_query, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_query = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void add(java.lang.String chinese, java.lang.String english) throws android.os.RemoteException;
public java.lang.String query(java.lang.String chinese) throws android.os.RemoteException;
}
這個接口類比較長,繼承了android.os.IInterface這個接口。這個類簡化的結構大致如下:
public interface IDictionaryManager extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static com.wanginbeijing.dictionaryserver.IDictionaryManager asInterface(android.os.IBinder obj)
@Override
public android.os.IBinder asBinder()
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
private static class Proxy implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
@Override
public android.os.IBinder asBinder()
public java.lang.String getInterfaceDescriptor()
@Override
public void add(java.lang.String chinese, java.lang.String english)
@Override
public java.lang.String query(java.lang.String chinese)
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_query = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void add(java.lang.String chinese, java.lang.String english)
public java.lang.String query(java.lang.String chinese)
}
該類首先包含了一個抽象內部類:Stub, 該類繼承自Binder并實現了IDictionary接口。在Stub的內部,又包含了一個靜態內部類:Proxy,Proxy類同樣實現了IDictionary接口。
Step2:定義一個Service,用于客戶端連接
public class DictionaryManagerService extends Service {
private Map<String, String> mMap = new HashMap<>();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new IDictionaryManager.Stub() {
@Override
public void add(String chinese, String english) throws RemoteException {
mMap.put(chinese, english);
Log.e("DictionaryManager", "add new word");
}
@Override
public String query(String chinese) throws RemoteException {
return mMap.get(chinese);
}
};
}
}
該類中定義了一個HashMap用來保存生詞,并重寫了Service中的onBind方法,在onBinder方法中,返回了一個繼承自IDictionaryManager.Stub的匿名內部類,并重寫了IDictionaryManager接口中add和query方法,實現真正的生詞本業務邏輯。
Step3:客戶端連接服務端Service
客戶端要調用服務端接口,先要將服務端定義好的aidl文件拷貝到客戶端相同目錄下,并build生成java文件。然后開始連接服務器端:
public class MainActivity extends Activity {
private IDictionaryManager mDictionaryManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("android.intent.action.DictionaryManagerService");
intent.setPackage("com.wanginbeijing.dictionaryserver");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
//添加一個新單詞
findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
mDictionaryManager.add("你好", "Hello");
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
//查詢單詞
findViewById(R.id.btn_query).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
String english = mDictionaryManager.query("你好");
Toast.makeText(MainActivity.this, english, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
IDictionaryManager dictionaryManager = IDictionaryManager.Stub.asInterface(iBinder);
try {
mDictionaryManager = dictionaryManager;
Toast.makeText(MainActivity.this, "connect success", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(MainActivity.this, "connect failed", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
客戶端的MainActivty的onCreate()方法中,首先自動連接遠程service,拿到遠程service傳回的Binder對象后,強轉為IDictionaryService類型的變量mDictionaryManager。然后在add按鈕和query按鈕的監聽事件中分別添加和查詢單詞(你好,Hello)。下面展示一下操作Demo.
可以看見,在連接遠端service成功后,首先點擊add按鈕插入單詞,接著調用query接口,可以成功查詢到剛剛插入的單詞。
以上就是使用Binder進行IPC的主要過程,但是僅僅掌握了使用方法怎么能滿足我們的好奇心呢,我們還必須要挖挖它的原理。
原理
java層
說到原理,還得先從代碼看起,回到上述所說的Android Studio自動生成的那個java類:IDictionaryManager。剛才已經說過,該類中有一個繼承自Binder的Stub類,它實現了我們所需要的IDictionaryManager接口。我們先來看看它的幾個方法:
public static IDictionaryManager asInterface(IBinder obj)
這方法將一個IBinder對象轉換成實現了IDictionaryManager。如果請求調用的客戶端在同一進程,就直接返回Stub對象本身,如果不在同一進程,就返回Stub類中的Proxy對象。Proxy類同樣實現了IDictionaryManager接口。
asBinder
這個方法很簡單,就直接返回了Stub對象自己。
onTransact
這個方法主要是進行用來調用其他函數的,它會根據傳入的參數code,選擇調用add方法還是query方法。
接下來看看Stub.Proxy類中的方法
asBinder
這個方法返回了Proxy對象中的mRemote變量,mRemote變量的賦值發生在Proxy類的構造函數中,而在Stub類的asInterface()方法中調用了該構造函數Proxy(),并傳入了一個Binder對象,將mRemote指向了這個Binder對象。
add
該add方法繼承自IDictionary接口。里面主要是將客戶端傳參寫入_data中,然后調用transact()方法,在transact方法中,會回調Stub類中的onTransact()。
query
同上述add方法一樣,只不過多了一個返回值。
介紹完這些方法之后,我們再來將這些方法串起來,講一下完整的IPC過程??蛻舳诉B接遠程Service時,Service會返回一個Binder對象,即我們在DictionaryManagerService.onBind()中返回的繼承自Stub的匿名對象,該類實現了生詞本邏輯。
客戶端調用有兩種情況:
1:客戶端和服務端在同一個進程
服務器端就直接將該Stub對象返回,即我們在onBind()中返回的Stub對象,客戶端調用返回對象的asInterface()方法,將Stub對象強轉為IDictionManager對象,直接調用IDictionManager對象中的方法,就像調用本地對象的方法一樣。
2:客戶端和服務端不在同一個進程
因為是跨進程,兩端的內存資源是不能共享的,服務器端不可能將真正的Stub對象返回,它只能將Stub中的代理類Prxoy對象返回。客戶端同樣調用返回對象的asInterface()方法,將Proxy對象強轉為IDictionManager對象,所以這次調用IDictionManager中add方法的時候,調用的實際上是Prxoy.add()。這次調用發生在客戶端進程,然后在Proxy.add()方法中,將參數和目標函數code寫入Binder驅動中,并通過回調服務端Stub.onTransact()方法,實現調用真正的Stub.add()方法。
C++層
我們開發時所見到的Binder是Android系統提供給我們的java接口,java層的Binder對象只是Android對底層Binder的一個封裝,提供給上層開發人員使用,真正的Binder其實隱藏在系統底層,默默的替我們進行著跨進程通信。
Java層的服務端Binder對象在C++層對應的對象為BBinder,而客戶端拿到的BinderProxy對象對應的則為BpBinder,BBindder和BpBinder都繼承自IBinder,上層Binder和BinderProxy之間的通信其實是BBinder和BpBinder之間的通信。
Binder的核心是Binder Driver,即Binder驅動,雖然各個進程不能共享自己的內存空間,但是系統的內核空間是共享的,每個進程都可以訪問,而Binder Driver即存在于內核空間,BBinder和BpBinder都是通過它來通信的,所以可以把Binder驅動當作是BBinder和BpBinder之間的一座橋梁。當然BBinder和BpBinder也不是直接和Binder驅動打交道,它們中間還隔著一個IPCThreadState。每一個進程都有一個ProcessState對象,它負責打開Binder驅動,這樣該進程中的所有線程都可以通過Binder驅動通信,而每個線程都會有一個IPCThreadState對象,它才是真正讀寫Binder驅動的主角,它通過talkWithDriver()和驅動打交道,將IPC數據寫入驅動中。
Java-->C++對象轉換
回到我們上面的Demo,客戶端連接成功后拿到了BinderProxy對象,那么這個服務端的Binder對象是如何轉為BinderPrxoy的呢?這點我們要看bindService的源碼,bindService流程很長,感興趣的讀者可以自己去看后者直接看老羅的分析,我這里直接告訴讀者大致過程:
1.客戶端請求bindService,先會請求ActivityManagerService;
2.ActvityManagerService再去找到對應的Service,讓Service所在進程創建并啟動Service;
3.Service調用AMS.publishService()將Binder對象傳遞給AMS;
4.AMS拿到的Binder對象同樣為BinderProxy對象,然后調用 c.conn.connected(r.name, service)方法,將BinderProxy對象傳遞給客戶端。
這里要知道的是,AMS也是處在一個單獨的進程中,所以Binder對象不是直接返回給AMS,AMS也不是直接返回給客戶端的,而是經過了Binder驅動。服務端Service將Binder對象傳遞給AMS時,會調用AMS在服務端的代理對象ActivityManagerProxy.publishService()方法,準確的說,Service端在此時成了AMS的客戶端。
public void publishService(IBinder token,
Intent intent, IBinder service) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
intent.writeToParcel(data, 0);
data.writeStrongBinder(service);
mRemote.transact(PUBLISH_SERVICE_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
該方法中通過Parcel.writeStrongBinder(),將服務端的Binder對象轉化成C++中的flat_binder_object對象,并將Binder對象的地址賦值給flat_binder_object對象中的cookie。
struct flat_binder_object {
unsigned long type;
unsigned long flags;
union {
void *binder; /* local object */
signed long handle; /* remote object */
};
void *cookie;
};
flat_binder_object對象最終被包裝在了data中,然后通過mRemote.transact(),將data數據傳送到IPCThreadState, IPCThreadState再次將data包裝成binder_transaction_data,并調用talkWithDriver(),將包裝好的數據傳遞給Binder驅動。
Binder驅動收到數據后,并不會急著將數據傳給AMS,因為傳送的數據中有flat_binder_object,所以它會查詢flat_binder_object對應的Binder對象在驅動中是否有Binder節點,如果沒有,則會利用flat_binder_object中的cookie值(指向BBinder)創建一個Binder節點,并為該節點生成一個索引值,將索引值賦給flat_binder_object的handle。
然后AMS就可以取數據了,AMS同樣有一個IPCThreadState對象,Binder驅動將數據傳遞給該對象,接收端拿到了數據后,會調用Parcel.readStrongBinder(),這在java層是一個jni方法,該方法會先調用Native層的readStrongBinder(),利用flat_binder_object中的handle值創建BpBinder對象,然后該Native方法返回BpBinder的指針,jni方法再根據BpBinder指針創建java層的BinderProxy對象并返回給java層。至此BpBinder和BinderProxy對象都已經創建完畢。
BpBinder與BBinder的通信
上面我們已經拿到了BpBinder,那BpBinder又將如何與BBinder通信呢?這里不得不提一個重要的變量:handle。還記得在創建BpBiner的時候,需要傳入flat_binder_object的handle嗎,而這個handle是Binder節點在驅動中的索引,即位置。這樣當BpBinder通過transact()調用BBinder時,Binder驅動就可以根據BpBinder提供的handle值找到Binder在驅動中的節點,Binder節點保存了BBinder的地址,從而找到了BBinder,實現對BBinder的調用。
系統服務的注冊
上面的例子展示的匿名Binder的通信,為什么說是匿名,因為Binder對象并沒有在ContextManager中實名注冊。
ContextManager又是什么?它是系統服務的管理者,它在java層和C++層的代理對象都為ServiceManager,系統服務可以通過ServiceManager.addService()將自己實名注冊到ContextManager中。為什么要實名注冊?如果不采用實名的方法,系統服務那么多,你難道一個個的去記得它的handle?
ServiceManager.addService()會將服務的Binder對象傳到Binder Driver,Binder Driver為其生成Binder節點后,ContextManager就會將服務的實名和其Binder節點的索引handle保存到自身。當客戶端需要找一個系統服務時,只需將服務名ServiceManager.getService(),ServiceManager就可以找到服務的索引handle,并創建對應的BpBinder對象,從而建立通信。比如java層的ActivityManagerService就是一個Binder類,我們就可以通過ServiceManager.getService("activity"),拿到它的代理對象,從而進行Activity生命周期的調度。
上面說到ServiceManager是ContextManager的代理,因為ContextManager本身也就是一個服務,服務端和客戶端想調用它的addService或getService時,也必須通過ServiceManager來跨進程??墒俏以撛趺茨玫竭@個代理呢?我總不能調用它的getService來獲取它自己吧。Binder這一點設計的很奇妙,ContextManager在BinderDriver中的節點索引為0,誰讓它是老大呢。這樣大家都知道了,我想調用ContextManger,直接根據handle=0就可以生成它的代理對象BpBinder了,從而創建代理對象ServiceManager。
總結
系統中每個app和系統服務都活在自己的進程中,無法訪問彼此的內存空間,正是因為相互通信的強烈需求,從而誕生了Binder。Binder驅動就像所有進程共同的地盤,系統服務可以這里留下自己的地址,而客戶端可以可以根據這些地址找到對應的服務,相互通信,從而千里姻緣一線牽,百年恩愛雙心結。
(轉載請注明ID:半棧工程師,歡迎訪問個人博客:https://halfstackdeveloper.github.io/)
歡迎關注我的知乎專欄:https://zhuanlan.zhihu.com/halfstack