Android 進程間通信

一、引言

關於Android中的進程間通信,我們知道大概可以通過以下方式進行:

  • Bundle:四大組件間通信
  • File:文件共享
  • ContentProvider:應用間數據共享
  • AIDLBinder機制
  • Messager:基於AIDLHandler實現
  • Socket:建立C/S通信模型

本文主要探索的是AIDLSocket兩種實現方式,並在日常使用的基礎上根據AIDL所生成的代碼分析 Binder跨進程通信機制,感興趣的童鞋可以看看。

本文完整代碼:AndroidIPCDemo

二、使用 AIDLSocket 進行通信

先來說說我們一會兒要實現的通信模型,大致如下圖:

然後看看目錄結構:


再看看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的關鍵 APItransact(),它與 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_TRANSACTIONLAST_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#sendMessageProxy#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上也創建一個MessengerHandler,然後在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進程間通信中的AIDLSocket兩種方式,文中沒有對Socket方式做過多描述和分析,是因爲使用Socket通信是比較基礎的事情,並且它的實現過程相對容易理解,因此就一筆帶過了,具體實現源碼請查看 AndroidIPCDemo。文中着重從AIDL生成源碼角度分析了Binder的運行機制,並簡單介紹了Messenger的使用及其實現。

OK,水平有限,歡迎理性指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章