一、引言
關於Android
中的進程間通信,我們知道大概可以通過以下方式進行:
-
Bundle
:四大組件間通信 -
File
:文件共享 -
ContentProvider
:應用間數據共享 -
AIDL
:Binder
機制 -
Messager
:基於AIDL
、Handler
實現 -
Socket
:建立C/S
通信模型
本文主要探索的是AIDL
和Socket
兩種實現方式,並在日常使用的基礎上根據AIDL
所生成的代碼分析 Binder
跨進程通信機制,感興趣的童鞋可以看看。
本文完整代碼:AndroidIPCDemo
二、使用 AIDL
和 Socket
進行通信
先來說說我們一會兒要實現的通信模型,大致如下圖:
然後看看目錄結構:
再看看IMyAidlInterface.aidl
,這裏在定義方法名稱的時候需要注意的是方法不能同名,包需要手動導入,可能是因爲AIDL
文件在解析時不會區分參數類型,導致我在設定同名方法時一直編譯錯誤,搞得我一直找其他問題,所以這點需要注意一下:
// IMyAidlInterface.aidl
package com.project.horselai.bindprogressguarddemo;
// 需要手動導入的包
import com.project.horselai.bindprogressguarddemo.IMyAidlInterfaceCallback;
import com.project.horselai.bindprogressguarddemo.MyMessage;
interface IMyAidlInterface {
void sendMessage(String msg);
void sendMessageObj(in MyMessage msg);
int getProcessId();
void registerCallback(IMyAidlInterfaceCallback callback);
void unregisterCallback(IMyAidlInterfaceCallback callback);
}
然後是IMyAidlInterfaceCallback.aidl
:
// IMyAidlInterfaceCallback.aidl
package com.project.horselai.bindprogressguarddemo;
interface IMyAidlInterfaceCallback {
void onValueCallback(int value);
}
最後是 MyMessage.aidl
:
// MyMessage.aidl
package com.project.horselai.bindprogressguarddemo;
parcelable MyMessage;
其他代碼太長就不貼出來了,具體請查看 AndroidIPCDemo。
演示圖如下,具體還是跑起來看看吧。
這裏有個現象就是,unbindService
調用之後,ServiceConnection
並沒有中斷,因此,如果此時再次發送消息也是能夠發送和接收到的。
三、從AIDL
生成類源碼角度理解Binder
機制
1. 先來點關於 IBinder 的理論知識
官方原文:IBinder
IBinder
作爲遠程對象的基礎接口,是爲高性能執行跨進程和進程內調用設計的輕量級遠程調用機制的核心部分,它描述了與遠程對象交互時的抽象協議,使用時應該繼承 Binder
,而應直接實現 IBinder
接口。
IBinder
的關鍵 API
是 transact()
,它與 Binder.onTranscat()
配對使用,當調用transcat()
方法時會發送請求到IBinder
對象,而接收到請求時是在Binder.onTranscat()
中接收,transcat()
是同步執行的,執行transcat()
後transcat()
會等待對方Binder.onTranscat()
方法返回後才返回,這種行爲在同一進程中執行時是必然的,而在不同進程間執行時,底層IPC
機制也會確保具備與之相同的行爲。
transact()
方法發送的是Parcel
類型的數據,Parcel
是一種通用數據緩衝,它包含一些描述它所承載內容的元數據(meta-data
),這些元數據用於管理緩衝數據中的IBinder
對象引用,因此這些引用可以被保存爲緩衝數據而傳遞到其他進程。這種機制保證了IBinder
能夠被寫入Parcel
中併發送到其他進程,如果其他進程發送相同的IBinder
引用回來給源進程,則說明源進程收到一個相同的IBinder
對象,這種特性使IBinder/Binder
對象能夠作爲進程間的唯一標識(作爲服務器token
或者其他目的)。
系統會爲每個運行的進程維護了一個事務線程池,線程池中的線程用於分發所有來自其他進程的IPC
事務,例如,當進程A
與進程B
進行IPC
時(此時A
爲發送進程),由於A
調用transact()
發送事務到進程B
的緣故,A
中被調用的線程會被阻塞在transact()
,此時如果B
進程中的可用線程池線程接收到了來自A
的事務,就會調用目標對象(A
進程)的Binder.onTranscat()
,並回復一個Parcel
作爲應答。接收到來自B
進程的應答後,在A
進程中執行transact()
的線程就會結束阻塞,從而繼續執行其他邏輯。
Binder
系統同樣支持跨進程遞歸,例如,如果進程A
執行一個事務到進程B
,然後進程B
處理接收到的事務時又執行了由進程A
實現的IBinder.transact()
,那麼進程A
中正在等待原事務執行結束的線程將用於執行由進程B
調用的對象的Binder.onTranscat()
應答。這種行爲保證了遞歸機制在遠程調用Binder
對象和本地調用時行爲一致。
通過以下三種方式可以確定遠程對象是否可用:
- 當調用一個不存在進程的
IBinder.transact()
時會拋出RemoteException
異常; - 調用
pingBinder()
返回false
時表示遠程進程已經不存在; - 使用
linkToDeath()
方法給IBinder
註冊一個IBinder.DeathRecipient
,那麼當其承載進程被殺死時會通過這個監聽器通知;
2. AIDL 生成類源碼分析
先來看看生成類的結構:
其中IMyAidlInterface
接口是我們定義AIDL
接口的直接代碼生成,而IMyAidlInterface.Stub
則是實現IMyAidlInterface
接口的抽象類,實現了onTranscat()
方法,不過它並沒有具體實現IMyAidlInterface
的方法,而是將這部分的實現交給了IMyAidlInterface.Stub.Proxy
。
OK,我們來具體分析一下。首先定位到Stub#asInterface
,可見它主要負責區分當前進行的是本地通信還是跨進程通信。
public static com.project.horselai.bindprogressguarddemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
// 1. 查找本地是否存在這個 IBinder 對象
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.project.horselai.bindprogressguarddemo.IMyAidlInterface))) {
// 如果是本地通信,則稍後進行本地通信
return ((com.project.horselai.bindprogressguarddemo.IMyAidlInterface) iin);
}
// 2. 否則,稍後使用這個對象進行遠程通信
return new com.project.horselai.bindprogressguarddemo.IMyAidlInterface.Stub.Proxy(obj);
}
接着來看看Stub#onTranscat
方法, 各參數作用如下
-
code: 標識需要執行的動作,是一個從
FIRST_CALL_TRANSACTION
到LAST_CALL_TRANSACTION
之間的數字。 -
data:
transcat()
調用者發送過來的數據。 -
reply: 用於給
transcat()
調用者寫入應答數據。 -
flags: 如果是
0
,代表是一個普通RPC
,如果是FLAG_ONEWAY
則代表是一個one-way
類型的RPC
。 -
return: 返回
true
代表請求成功了,返回false
則表示你沒有明白事務代碼(code
)。
基於前面的理論知識,我們已經知道進程A
中的onTransact()
會被進程B
調用,用於遠程回調應答數據,下面通過兩個標誌性的方法解釋在onTransact()
中都做了什麼:
@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 TRANSACTION_sendMessage: {
data.enforceInterface(descriptor);
java.lang.String _arg0;
// 1. 從進程A的遠程請求包中讀取請求數據
_arg0 = data.readString();
// 2. 執行進程B中的sendMessage方法寫入來自進程A的數據
this.sendMessage(_arg0);
reply.writeNoException();
return true;
}
// 對於遠程讀請求
case TRANSACTION_getProcessId: {
data.enforceInterface(descriptor);
// 1. 執行進程B中的getProcessId() 讀取需要作爲響應數據的數據
int _result = this.getProcessId();
// 2. 將讀取到的響應數據寫入到進程A的應答中
reply.writeNoException();
reply.writeInt(_result);
return true;
}
// ...
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
可能你會對上面的this.sendMessage(_arg0)
和this.getProcessId()
有所疑問,比如,爲什麼在TRANSACTION_sendMessage
中還要執行this.sendMessage(_arg0)
,這不就死循環了嗎? 不會的,爲啥呢,因爲TRANSACTION_sendMessage
判斷的是來自進程A
的方法類型碼,而在解析了來自進程A
的請求參數data
後會調用進程B
自身的sendMessage(_arg0)
方法將數據保存到自己的存儲內存中,而它的sendMessage(_arg0)
是有我們自己實現的,如下是我們在進程B
中的實現:
IMyAidlInterface.Stub myAidlInterface = new IMyAidlInterface.Stub() {
@Override
public void sendMessage(String msg) throws RemoteException {
Log.i(TAG, "sendMessage: " + msg);
}
@Override
public int getProcessId() throws RemoteException {
return Process.myPid();
}
};
到這是不是就很好理解了。
下面通過Proxy#sendMessage
和Proxy#getProcessId
兩個與上面對應的方法來解釋作爲客戶端的進程A
是如何給遠程作爲服務端的B
進程發送請求的:
@Override
public void sendMessage(java.lang.String msg) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); // 請求參數
android.os.Parcel _reply = android.os.Parcel.obtain(); // 響應數據
try {
// 1. 封裝遠程請求參數
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(msg);
// 2. 通過Binder執行遠程請求,最終響應數據會封裝在_reply
mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0);
// 3. 沒有需要返回數據則僅讀取異常
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public int getProcessId() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); // 請求參數
android.os.Parcel _reply = android.os.Parcel.obtain(); // 響應數據
int _result;
try {
// 1. 沒有參數,則僅寫入標識
_data.writeInterfaceToken(DESCRIPTOR);
// 2. 通過Binder執行遠程請求,最終響應數據會封裝在_reply
mRemote.transact(Stub.TRANSACTION_getProcessId, _data, _reply, 0);
// 3. transact阻塞結束後讀取響應數據
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
可見,實際上_reply
一直使用的都是同一個,由進程A
創建,發送給B
進程,進程B
會將處理好的響應數據寫入到_reply
中,並最終通過onTranscat
方法回調給進程A
,這樣就完成了一個RPC
。
總的來說,整個過程的執行流程如下:
四、Messager
使用與源碼分析
1. 使用
在Service
進程中如下使用:
public class MessengerRemoteService extends Service {
private static final String TAG = "MessengerRemoteService";
private Messenger mMessenger;
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
// 3. 使用 Messenger 進行進程間通信
mMessenger = new Messenger(new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg);
Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));
return true;
}
}));
}
}
然後在Activity
中如下建立服務連接:
// for Messenger
private Messenger mMessenger;
ServiceConnection mServiceConnection3 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected3: ");
mMessenger = new Messenger(service);
btnBindRemote.setEnabled(false);
mIsBond = true;
Toast.makeText(MainActivity.this, "service bond 3!", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected 3: ");
mIsBond = false;
btnBindRemote.setEnabled(true);
}
};
綁定後如下發送信息:
if (mMessenger != null){
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putString("msg","message clicked from Main ..");
message.what = 122;
message.setData(bundle);
try {
mMessenger.send( message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
日誌輸出如下:
基於上面的使用,整個流程下來你會發現Messenger
的通信是單向的,如果想要雙向的話,那麼需要在作爲客戶端的進程A
上也創建一個Messenger
和Handler
,然後在B
進程中發送響應消息。
爲了能夠進行雙向通信,我們可以對上面代碼進行如下修改,其中MessengerRemoteService
中的Messenger
可以這麼修改:
mMessenger = new Messenger(new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg);
Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));
Message message = Message.obtain();
message.replyTo = mMessenger;
Bundle bundle = new Bundle();
bundle.putString("msg", "MSG from MessengerRemoteService..");
message.setData(bundle);
message.what = 124;
try {
// 注意這裏
msg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
return true;
}
}));
注意到上面的msg.replyTo.send(message)
,其中msg.replyTo
是一個代表發送這個消息的Messenger
。在Activity
中可以這麼改:
// onCreate中
mClientMessenger = new Messenger(mHandler);
// ...
Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 2) {
Toast.makeText(MainActivity.this, "" + msg.obj, Toast.LENGTH_SHORT).show();
return true;
}else if (msg.what == 124){
Toast.makeText(MainActivity.this, msg.getData().getString("msg"), Toast.LENGTH_SHORT).show();
Log.i(TAG, "handleMessage: " + msg.getData().getString("msg"));
Log.i(TAG, "handleMessage: ");
return true;
}
textView.setText(String.valueOf(msg.obj));
return true;
}
});
最後Activity
收到消息時會彈出收到的消息,如下圖:
整個雙向通信的流程如下:
2. Messenger 實現原理
Messenger底層僅僅是簡單地包裹了一下Binder
,具體來說就是也使用的AIDL
,因此它不會影響到進程的生命週期,不過當進程銷燬時,連接也會中斷。
下面來簡要看一下它的部分源碼:
public final class Messenger implements Parcelable {
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
// ...
}
其中IMessenger
是個AIDL
接口,如下:
package android.os;
import android.os.Message;
/** @hide */
oneway interface IMessenger {
void send(in Message msg);
}
有了前面的知識基礎,這玩意兒就很好理解了。
五、總結
本文主要描述了Android
進程間通信中的AIDL
和Socket
兩種方式,文中沒有對Socket
方式做過多描述和分析,是因爲使用Socket
通信是比較基礎的事情,並且它的實現過程相對容易理解,因此就一筆帶過了,具體實現源碼請查看 AndroidIPCDemo。文中着重從AIDL
生成源碼角度分析了Binder
的運行機制,並簡單介紹了Messenger
的使用及其實現。
OK,水平有限,歡迎理性指正。