Android 進程間通信——AIDL


代碼地址如下:
http://www.demodashi.com/demo/12321.html

原文地址:http://blog.csdn.net/vnanyesheshou/article/details/79047650

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

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 )。 對方收到的實際具體類始終是一個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接口。 在下一步中,這個實例被暴露給客戶,以便他們可以與服務交互。

在實現AIDL接口時,您應該注意一些規則

  • 傳入的調用並不保證在主線程中執行,所以需要從頭開始考慮多線程,並將服務正確地構建為線程安全的。
  • 默認情況下,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. 實現ServiceConnection.
  4. 調用Context.bindService(),傳入你的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;
}

5 項目結構截圖

客戶端項目結構

服務端項目結構

6 運行效果圖

客戶端接收服務端的回調,效果顯示如下:
這里寫圖片描述Android 進程間通信——AIDL

代碼地址如下:
http://www.demodashi.com/demo/12321.html

注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM