Android 進程間通信——AIDL

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

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

1 使用AIDL的必要條件

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

2 AIDL的使用

使用Java編程語言語法在.aidl文件中定義您的AIDL接口,然后將其保存在承載服務(wù)的應(yīng)用程序和任何其他綁定到該服務(wù)的應(yīng)用程序的源代碼(在src /目錄中)。
當(dāng)應(yīng)用程序構(gòu)建包含.aidl文件時,Android SDK工具將生成一個基于.aidl文件的IBinder接口,并將其保存在項目的gen /目錄中。 該服務(wù)必須適當(dāng)?shù)貙崿F(xiàn)IBinder接口。 然后,客戶端應(yīng)用程序可以綁定到服務(wù)并從IBinder調(diào)用方法來執(zhí)行IPC。

使用AIDL 創(chuàng)建綁定的服務(wù),具體步驟:

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

2.1 創(chuàng)建.aidl文件

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

默認(rèn)情況下,AIDL支持以下數(shù)據(jù)類型:

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

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

在定義服務(wù)接口時,注意:

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

如下是一個.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結(jié)果IRemoteService.java)。

2.2 實現(xiàn)接口

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

要實現(xiàn)從.aidl生成的接口,請繼承生成的Binder接口(例如IRemoteService.Stub),并實現(xiàn)從.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;
    }
};

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

<font color =red>在實現(xiàn)AIDL接口時,您應(yīng)該注意一些規(guī)則</font>:

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

2.3 將接口公開給客戶端

為了暴露你的服務(wù)的接口,擴展Service并實現(xiàn)onBind()返回實現(xiàn)生成的Stub的類的實例。 這里是一個示例服務(wù),將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;
        }
    };
}

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

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

    // 當(dāng)與服務(wù)端連接成功時,回調(diào)該方法。
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //轉(zhuǎn)換
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

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

3 調(diào)用IPC方法

以下是調(diào)用類必須用來調(diào)用AIDL定義的遠(yuǎn)程接口的步驟:

  1. 將.aidl文件包含在項目src /目錄中。
  2. 聲明一個IBinder接口的實例(基于AIDL生成)。
  3. 實現(xiàn)<font color=red>ServiceConnection.
  4. 調(diào)用<font color=red>Context.bindService()</font>,傳入你的ServiceConnection實現(xiàn)。
  5. 在onServiceConnected()實現(xiàn)中,將收到一個IBinder實例。 調(diào)用YourInterfaceName.Stub.asInterface((IBinder)service)將返回的參數(shù)強制轉(zhuǎn)換為YourInterfaceName類型。
  6. 調(diào)用你在接口上定義的方法。 您應(yīng)該始終捕獲連接斷開時引發(fā)的DeadObjectException異常; 這將是遠(yuǎn)程方法拋出的唯一異常。
  7. 要斷開連接,調(diào)用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;
    //與服務(wù)端的連接狀態(tài)
    private int mBindState = STATE_DISCONNECTED;

    private ServiceConnection mConnection = new ServiceConnection() {

        // 當(dāng)與服務(wù)端連接成功時,回調(diào)該方法。
        @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();
            }
        }

        // 當(dāng)與服務(wù)端連接異常斷開時,回調(diào)該方法。
        @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 以上顯示綁定服務(wù)
        intent.setComponent(new ComponentName("com.zpengyong.aidl", "com.zpengyong.aidl.AIDLService"));
        // 綁定服務(wù)
        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 服務(wù)端回調(diào)客戶端

如上的列子中只有客戶端調(diào)用服務(wù)端的方法,并不能服務(wù)端調(diào)用客戶端。

在之前的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中添加服務(wù)端調(diào)用客戶端的接口。
該文件服務(wù)度和客戶端都需要包含該文件。

package com.zpengyong.aidl;

interface IRemoteServiceCallback {
    void stateChange(int value);
}

1 客戶端實現(xiàn)回調(diào)接口
要實現(xiàn)從IRemoteServiceCallback.aidl生成的接口,請繼承生成的Binder接口(IRemoteServiceCallback.Stub),并實現(xiàn)從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 注冊回調(diào)
客戶端bindservice成功后會回調(diào)onServiceConnected,客戶端可以獲取到mIRemoteService,可以調(diào)用遠(yuǎn)端的放,這時可以通過調(diào)用遠(yuǎn)端方法注冊回調(diào)接口實例。

    private ServiceConnection mConnection = new ServiceConnection() {

        // 當(dāng)與服務(wù)端連接成功時,回調(diào)該方法。
        @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 {
                //注冊回調(diào)。
                mIRemoteService.registerCallback(mIRemoteServiceCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
}

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

//aidl支持多個客戶端綁定,并且處理并發(fā)進程間通信,所以這里要存列表中。
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 服務(wù)器調(diào)用客戶端方法
遍歷回調(diào)list,分別調(diào)用其stateChange方法,實現(xiàn)服務(wù)端調(diào)用客戶端,實現(xiàn)雙方通信。

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

當(dāng)服務(wù)端調(diào)用回調(diào)接口的方法后,客戶端的接口實現(xiàn)中就會收到響應(yīng)。

4 取消注冊
客戶端unbindService前 調(diào)用取消注冊的方法。

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;
}

客戶端接收服務(wù)端的回調(diào),效果顯示如下:


這里寫圖片描述
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容