Android 進程間通信——AIDL

AIDL(Android Interface Definition Language)——進程間通信的一種機制。它允許您定義客戶端和服務端通過使用進程間通信(IPC)進行通信的編程接口。在Android上,一個進程無法正常訪問另一個進程的內存。所以說,他們需要將他們的對象分解成操作系統能夠理解的原語,并且把這些對象放在你的邊界上。編寫這些代碼非常繁瑣,所以Android使用AIDL來處理它。

Demo下載地址http://blog.csdn.net/vnanyesheshou/article/details/79047650

1 使用AIDL的必要條件

  • 只有當你需要來自不同應用的客戶端通過IPC(進程間通信)通信來訪問你的服務時,并且想在服務里處理多線程的業務,這時就需要使用AIDL。
  • 如果你不需要同時對幾個應用進程IPC操作,你最好通過實現Binder接口來創建你的接口。
  • 如果你仍需要執行IPC操作,但不需要處理多線程,使用Messenger來實現接口即可。

2 AIDL的使用

使用Java編程語言語法在.aidl文件中定義您的AIDL接口,然后將其保存在承載服務的應用程序和任何其他綁定到該服務的應用程序的源代碼(在src /目錄中)。
當應用程序構建包含.aidl文件時,Android SDK工具將生成一個基于.aidl文件的IBinder接口,并將其保存在項目的gen /目錄中。 該服務必須適當地實現IBinder接口。 然后,客戶端應用程序可以綁定到服務并從IBinder調用方法來執行IPC。

使用AIDL 創建綁定的服務,具體步驟:

  1. 創建.aidl文件
    這個文件用方法簽名來定義編程接口。
  2. 實現接口
    Android SDK工具根據你的.aidl文件以Java編程語言生成一個接口 。這個接口有一個名為Stub的內部抽象類,它繼承了Binder并實現了AIDL接口中的方法。你必須繼承這個 Stub類并實現這些方法。
  3. 將接口公開給客戶端
    實現一個服務并重寫onBind() 來返回你的Stub類的實現。

2.1 創建.aidl文件

AIDL使用簡單的語法,可以用一個或多個方法(可以接收參數和返回值)來聲明接口。參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。
必須使用Java編程語言構建.aidl文件。 每個.aidl文件都必須定義一個接口,并且只需要接口聲明和方法簽名。

默認情況下,AIDL支持以下數據類型:

  • Java編程語言中的所有基本類型(如int,long,char,boolean等)
  • String
  • CharSequence
  • List
    List中的所有元素都必須是支持的數據類型之一,或者是您聲明的其他AIDL生成的接口或可接受的元素之一。 列表可以選擇性地用作“通用”類(例如List <String>)。 對方收到的實際具體類始終是一個ArrayList,盡管生成的方法是使用List接口。
  • Map
    Map中的所有元素都必須是此列表中受支持的數據類型之一,或者是您聲明的其他AIDL生成的接口或可接受元素之一。 通用映射(如Map <String,Integer>形式的映射)不被支持。對方接收的實際具體類總是一個HashMap,盡管該方法是使用Map接口生成的。

對于上面沒有列出的每種附加類型,即使它們在與接口相同的包中定義,也必須包含一條import語句。

在定義服務接口時,注意:

  • 方法可以采用零個或多個參數,并返回一個值或void。
  • 所有非原始參數都需要一個指向數據的方向標簽。in,out或者inout(見下面的例子)。基本數據默認是in的,不能以其他方式。
    警告:您應該將方向限制在真正需要的地方,因為編組參數非常昂貴。
  • 包含在.aidl文件中的所有代碼注釋都包含在生成的IBinder接口中(導入和包裝語句之前的注釋除外)。
  • 只支持方法; 您不能在AIDL中公開靜態字段。

如下是一個.aidl 例子。IRemoteService.aidl

package com.zpengyong.aidl;

interface IRemoteService {
    void sendMessage(in String str);
    
    boolean play();
    
    boolean pause();
    
    boolean stop();
}

只需將.aidl文件保存在項目src/目錄中,SDK工具會在項目gen/目錄中生成IBinder接口文件。生成的文件名與.aidl文件名相匹配,但帶有.java擴展名(例如IRemoteService.aidl結果IRemoteService.java)。

2.2 實現接口

IRemoteService.java接口文件包含一個名為Stub的類 ,它繼承了Binder ,實現了IRemoteService接口,并聲明.aidl文件中的所有方法。
Stub還定義了一些輔助方法,最值得注意的是asInterface(),它接受一個IBinder(通常是傳遞給客戶端的onServiceConnected()回調方法中的參數),并返回stub接口的一個實例。

要實現從.aidl生成的接口,請繼承生成的Binder接口(例如IRemoteService.Stub),并實現從.aidl文件繼承的方法。
下面是示例:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){  
    public void sendMessage(String str){
        Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
        Message msg = new Message();
        msg.what = MSG_RECEIVE_MESSAGE;
        msg.obj = str;
        mHandler.sendMessage(msg);
    }
    
    public boolean play(){
        mService.play();
        return true;
    }
    
    public boolean pause(){
        mService.pause();
        return true;
    }
    
    public boolean stop(){
        mService.stop();
        return true;
    }
};

現在mBinder是Stub類的一個實例(一個Binder),它定義了服務的RPC接口。 在下一步中,這個實例被暴露給客戶,以便他們可以與服務交互。

<font color =red>在實現AIDL接口時,您應該注意一些規則</font>:

  • 傳入的調用并不保證在主線程中執行,所以需要從頭開始考慮多線程,并將服務正確地構建為線程安全的。
  • 默認情況下,RPC調用是同步的。如果您知道該服務需要超過幾毫秒才能完成請求,則不應該從活動的主線程調用該服務,因為它可能會掛起應用程序(Android可能會顯示“應用程序不響應”對話框,應該通常從客戶端的一個單獨的線程調用它們。
  • 拋出的任何異常都將被發回給調用者。

2.3 將接口公開給客戶端

為了暴露你的服務的接口,擴展Service并實現onBind()返回實現生成的Stub的類的實例。 這里是一個示例服務,將IRemoteService示例接口公開給客戶端。

public class AIDLService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
        public void sendMessage(String str){
            Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
            Message msg = new Message();
            msg.what = MSG_RECEIVE_MESSAGE;
            msg.obj = str;
            mHandler.sendMessage(msg);
        }
        
        public boolean play(){
            mService.play();
            return true;
        }
        
        public boolean pause(){
            mService.pause();
            return true;
        }
        
        public boolean stop(){
            mService.stop();
            return true;
        }
    };
}

現在,當一個客戶端(比如一個activity)調用bindService()連接到這個服務時,客戶端的onServiceConnected()回調會收到mBinder(服務onBind() 方法返回的 實例)。
客戶端還必須能夠訪問接口類,所以如果客戶端和服務在不同的應用程序中,那么客戶端的應用程序必須在其src/目錄中擁有該.aidl文件的副本(這會生成android.os.Binder 接口 - 為客戶端提供對AIDL方法的訪問)。
當客戶端收到onServiceConnected()回調,得到IBinder,它必須調用 IRemoteService.Stub.asInterface(service)轉換成IRemoteService類型。例如:

private IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {

    // 當與服務端連接成功時,回調該方法。
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //轉換
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // 當與服務端連接異常斷開時,回調該方法。
    @Override
    public void onServiceDisconnected(ComponentName name) {
        mIRemoteService = null;
    }
};

3 調用IPC方法

以下是調用類必須用來調用AIDL定義的遠程接口的步驟:

  1. 將.aidl文件包含在項目src /目錄中。
  2. 聲明一個IBinder接口的實例(基于AIDL生成)。
  3. 實現<font color=red>ServiceConnection.
  4. 調用<font color=red>Context.bindService()</font>,傳入你的ServiceConnection實現。
  5. 在onServiceConnected()實現中,將收到一個IBinder實例。 調用YourInterfaceName.Stub.asInterface((IBinder)service)將返回的參數強制轉換為YourInterfaceName類型。
  6. 調用你在接口上定義的方法。 您應該始終捕獲連接斷開時引發的DeadObjectException異常; 這將是遠程方法拋出的唯一異常。
  7. 要斷開連接,調用Context.unbindService()。

如下:

package com.zpengyong.aidlclient;

import com.zpengyong.aidl.IRemoteService;
import com.zpengyong.aidl.IRemoteServiceCallback;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.Editable;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {
    private final static String TAG = "MainActivity";

    private TextView mStateText, mMusicState;
    private Button mBtnHello, mBtnBind, mBtnStart, mBtnPause, mBtnStop;
    private EditText mTextMessage;
    
    private IRemoteService mIRemoteService;
    
    private final int STATE_DISCONNECTED = 1;
    private final int STATE_CONNECTING = 2;
    private final int STATE_CONNECTED = 3;
    private final int STATE_DISCONNECTING = 4;
    //與服務端的連接狀態
    private int mBindState = STATE_DISCONNECTED;

    private ServiceConnection mConnection = new ServiceConnection() {

        // 當與服務端連接成功時,回調該方法。
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected");
            mIRemoteService = IRemoteService.Stub.asInterface(service);
            mStateText.setText("connected");
            mBindState = STATE_CONNECTED;
            mBtnBind.setText("解綁");
            try {
                mIRemoteService.registerCallback(mIRemoteServiceCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        // 當與服務端連接異常斷開時,回調該方法。
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected");
            mIRemoteService = null;
            mStateText.setText("disconnected");
            mBindState = STATE_DISCONNECTED;
            mBtnBind.setText("綁定");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mStateText = (TextView) findViewById(R.id.connectState);
        mBtnHello = (Button) findViewById(R.id.sendMessage);
        mBtnBind = (Button)findViewById(R.id.bind);
        mBtnStart = (Button)findViewById(R.id.start_play);
        mBtnPause = (Button)findViewById(R.id.pause);
        mBtnStop = (Button)findViewById(R.id.stop_play);
        mBtnHello.setOnClickListener(this);
        mBtnStart.setOnClickListener(this);
        mBtnPause.setOnClickListener(this);
        mBtnStop.setOnClickListener(this);
        mBtnBind.setOnClickListener(this);
        mTextMessage = (EditText) findViewById(R.id.message);
        mMusicState = (TextView)findViewById(R.id.musicState);
    }

    private void bind() {
        mBindState = STATE_CONNECTING;
        Intent intent = new Intent();
        // Android 5.0 以上顯示綁定服務
        intent.setComponent(new ComponentName("com.zpengyong.aidl", "com.zpengyong.aidl.AIDLService"));
        // 綁定服務
        this.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        mStateText.setText("connecting");
    }

    private void unbind() {
        mBindState = STATE_DISCONNECTING;
        try {
            mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mStateText.setText("disconnecting");
        //解除與Service的連接
        unbindService(mConnection);
        mBindState = STATE_DISCONNECTED;
        mStateText.setText("disconnected");
        mBtnBind.setText("綁定");
        mIRemoteService = null;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mBindState != STATE_DISCONNECTED){
            unbind();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.sendMessage:
            String str = mTextMessage.getText().toString();
            if(str == null ||str.length() == 0)
                return;
            if(mIRemoteService == null)
                return;
            try {
                mIRemoteService.sendMessage(str);
            } catch (RemoteException e1) {
                e1.printStackTrace();
            }
            break;
        case R.id.bind:
            if(mBindState == STATE_DISCONNECTED){
                bind();
            }else if(mBindState == STATE_CONNECTED){
                unbind();
            }
            break;
        case R.id.start_play:
            if(mIRemoteService == null)
                return;
            try {
                boolean ret = mIRemoteService.play();
                Log.i(TAG, "play ret="+ret);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            break;
        case R.id.pause:
            if(mIRemoteService == null)
                return;
            try {
                boolean ret = mIRemoteService.pause();
                Log.i(TAG, "pause ret="+ret);
            } catch (RemoteException e) {
                e.printStackTrace();
            };
            break;
        case R.id.stop_play:
            if(mIRemoteService == null)
                return;
            try {
                boolean ret = mIRemoteService.stop();
                Log.i(TAG, "stop ret="+ret);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            break;
        default:
            break;
        }
    }
}

效果圖如下:


這里寫圖片描述

4 服務端回調客戶端

如上的列子中只有客戶端調用服務端的方法,并不能服務端調用客戶端。

在之前的IRemoteService.aidl文件中添加接口

package com.zpengyong.aidl;
import com.zpengyong.aidl.IRemoteServiceCallback;

interface IRemoteService {
    
    void registerCallback(in IRemoteServiceCallback cb);
    
    void unregisterCallback(in IRemoteServiceCallback cb);
    
    void sendMessage(in String str);
    
    boolean play();
    
    boolean pause();
    
    boolean stop();
}

IRemoteServiceCallback.aidl中添加服務端調用客戶端的接口。
該文件服務度和客戶端都需要包含該文件。

package com.zpengyong.aidl;

interface IRemoteServiceCallback {
    void stateChange(int value);
}

1 客戶端實現回調接口
要實現從IRemoteServiceCallback.aidl生成的接口,請繼承生成的Binder接口(IRemoteServiceCallback.Stub),并實現從IRemoteServiceCallback.aidl文件繼承的方法。

    private IRemoteServiceCallback mIRemoteServiceCallback = new IRemoteServiceCallback.Stub() {
        
        @Override
        public void stateChange(int value) throws RemoteException {
            Log.i(TAG, "stateChange value="+value);
            if(value == 1){
                mMusicState.setText("開始播放");
            }else if(value == 2){
                mMusicState.setText("暫停播放");
            }else if(value == 3){
                mMusicState.setText("停止播放");
            }else if(value == 4){
                mMusicState.setText("播放出錯");
            }else {
                mMusicState.setText("unknown");
            }
        }
    };

2 注冊回調
客戶端bindservice成功后會回調onServiceConnected,客戶端可以獲取到mIRemoteService,可以調用遠端的放,這時可以通過調用遠端方法注冊回調接口實例。

    private ServiceConnection mConnection = new ServiceConnection() {

        // 當與服務端連接成功時,回調該方法。
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected");
            mIRemoteService = IRemoteService.Stub.asInterface(service);
            mStateText.setText("connected");
            mBindState = STATE_CONNECTED;
            mBtnBind.setText("解綁");
            try {
                //注冊回調。
                mIRemoteService.registerCallback(mIRemoteServiceCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
}

3 服務端保存回調接口
由于AIDl支持多個客戶端綁定,并處理并發請求。所以這里要將回調接口存到列表中,避免后注冊的將前面注冊的回調接口覆蓋。

//aidl支持多個客戶端綁定,并且處理并發進程間通信,所以這里要存列表中。
final RemoteCallbackList<IRemoteServiceCallback> mCallbackList
        = new RemoteCallbackList<IRemoteServiceCallback>();

private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
    
    public void registerCallback(IRemoteServiceCallback cb){
        if(cb != null)mCallbackList.register(cb);
    }
    
    public void unregisterCallback(IRemoteServiceCallback cb){
        if(cb != null)mCallbackList.unregister(cb);
    }
    。。。
}       

4 服務器調用客戶端方法
遍歷回調list,分別調用其stateChange方法,實現服務端調用客戶端,實現雙方通信。

 private void callstateChange(int value){
     //遍歷保存的IRemoteServiceCallback,發送狀態改變的消息。
     int num = mCallbackList.beginBroadcast();
     for(int i=0; i<num; i++){
         try {
            mCallbackList.getBroadcastItem(i).stateChange(value);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
     }
     mCallbackList.finishBroadcast();
 }

當服務端調用回調接口的方法后,客戶端的接口實現中就會收到響應。

4 取消注冊
客戶端unbindService前 調用取消注冊的方法。

private void unbind() {
    mBindState = STATE_DISCONNECTING;
    try {
        mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    mStateText.setText("disconnecting");
    //解除與Service的連接
    unbindService(mConnection);
    mBindState = STATE_DISCONNECTED;
    mStateText.setText("disconnected");
    mBtnBind.setText("綁定");
    mIRemoteService = null;
}

客戶端接收服務端的回調,效果顯示如下:


這里寫圖片描述
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容