使用AIDL實現跨進程Service的綁定

基於在綁定Service時,與Service實現綁定的組件可以歸屬於不同的應用程序,因此可以實現跨進程的綁定,進而實現通信功能。在跨進程的綁定中,需要使用AIDL來定義接口,本博客將詳細的介紹這種跨進程綁定、通信的方式,由於使用AIDL定義接口時,默認可用的數據類型非常有限。本博客還會介紹如何在AIDL中使用自定義的數據類型,及Parcelable接口的使用方式。

AIDL

AIDL表示"安卓接口定義語言",使用AIDL定義的接口會被開發工具生成爲可實現遠程訪問的接口。進程間通信是組件間通信應用的“高級方式”,與組件間通信的區別在於:接口需要使用AIDL定義。由於存在兩個應用程序進行通信,一般提供服務的應用程序被稱爲“服務端”,而調用它接口方法的應用程序稱爲“客戶端”。

進程間的通信方式


AIDL的創建

AIDL接口的創建方式與普通java接口的創建方式一致,區別在於:
  • AIDL文件的擴展名爲.aidl,而普通java接口爲.java.(我們只需要像創建普通接口那樣先創建,然後把擴展名改爲.aidl即可)
  • aidl不允許用任何訪問權限修飾符去修飾該接口,它默認被修飾爲public的。
用法示例
interface IPlayerAIDL {
	void play();
	void pause();
	void previous();
	void next();
}

在創建了AIDL接口後,開發工具會自動在gen下創建匹配的java文件。

使用AIDL遠程綁定Service

在開發工具生成了匹配AIDL接口的java文件中定義了一個內部類Stub。繼承了android.os.Binder並實現了該AIDL接口,所以我們在創建service中IBinder的實現類時繼承該類即可。在該類中存在asInterface(IBinder obj)方法,用於將IBinder對象轉換爲AIDL接口實現類對象。因此在綁定Service的組件中可以該方法得到AIDL接口實現類的對象。

服務端的開發步驟:

  1. 開發AIDL接口的源文件,並保證擴展名爲.aidl。
  2. 在Service中開發AIDL接口的實現類並實現接口內定義的抽象方法。
  3. 實現Service的onBind()方法,並將AIDL接口類的實現對象作爲該方法的返回值。
  4. 註冊Service類,並配置intent-filter(確保該Service可以被隱式激活)
  5. 部署服務端應用程序到設備上

客戶端的開發步驟:

  1. 將服務端的AIDL相關文件複製到客戶端中(相關包名類名不可更改)
  2. 開發ServiceConnection接口的實現類
  3. 使用隱式意圖綁定服務端的Service
  4. 在ServiceConnection實現類的onServiceConnected方法中,通過Stub的asInterface方法將IBinder對象轉換爲AIDL接口實現類對象

接下來用一個例子來演示下它的用法,這個例子實現如下功能:
在服務端播放音樂,具有播放,暫停,上一曲,下一曲等功能,在客戶端對應有相關按鈕。

服務端代碼如下:
PlayerService.java
public class PlayerService extends Service {
	private MediaPlayer player;//定義播放器
	private int currentPosition;//當前的播放位置
	private int musicPosition=0;
	String[] musics=new String[]{//歌曲的存放路徑
			Environment.getExternalStorageDirectory().getAbsolutePath()+"/Music/d.mp3",
			Environment.getExternalStorageDirectory().getAbsolutePath()+"/Music/b.mp3",
			Environment.getExternalStorageDirectory().getAbsolutePath()+"/Music/c.mp3",
			Environment.getExternalStorageDirectory().getAbsolutePath()+"/Music/a.mp3",
	};
	@Override
	public void onCreate() {
		player=new MediaPlayer();
		//監聽一首歌是否播放完成
		player.setOnCompletionListener(new OnCompletionListener() {
			
			@Override
			public void onCompletion(MediaPlayer mp) {
				//完成後播放下一首
				doNext();
			}
		});
		super.onCreate();
	}
	private void doPlay(){
		player.reset();
		try {
			player.setDataSource(musics[musicPosition]);
			player.prepare();
			//播放前先到上次播放位置
			player.seekTo(currentPosition);
			player.start();
			currentPosition=0;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	private void doPause(){
			if(player.isPlaying()){
				currentPosition=player.getCurrentPosition();
				player.pause();
			}
		}
	private void doPrevious(){
		musicPosition--;
		if(musicPosition<0){
			//第一首上一首爲最後一首
			musicPosition=musics.length-1;
		}
		currentPosition=0;
		doPlay();
	}
	private void doNext(){
		musicPosition++;
		if(musicPosition>=musics.length){
			//最後一首下一首爲第一首
			musicPosition=0;
		}
		currentPosition=0;
		doPlay();
	}

	@Override
	public IBinder onBind(Intent intent) {
		return new InnerBinder();
	}
	
	private class InnerBinder extends IPlayerAIDL.Stub{

		@Override
		public void play() throws RemoteException {
			doPlay();
		}

		@Override
		public void pause() throws RemoteException {
			doPause();
		}

		@Override
		public void previous() throws RemoteException {
			doPrevious();
		}

		@Override
		public void next() throws RemoteException {
			doNext();
		}
		
	}
	@Override
	public void onDestroy() {
		player.release();
		player=null;
		super.onDestroy();
	}

}

IPlayerAIDL.aidl
interface IPlayerAIDL {
	void play();
	void pause();
	void previous();
	void next();
}
AndroidManifest.xml中添加如下代碼:
<service android:name=".PlayerService">
            <intent-filter>
                <action android:name="android.intent.action.PLAYMUSIC_SERVICE" />

                <category android:name="android.intent.category.DEFAULT" />
                
            </intent-filter>
            
 </service>
          <!-- 添加讀sd卡的權限 -->
           <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

客戶端代碼如下:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.wanghongxiang.playmusicclient.MainActivity" >

    <ImageButton
        android:id="@+id/imageButton2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/imageButton1"
        android:layout_below="@+id/imageButton1"
        android:layout_marginTop="21dp"
        android:onClick="pause"
        android:src="@android:drawable/ic_media_pause" />

    <ImageButton
        android:id="@+id/imageButton3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/imageButton2"
        android:layout_centerVertical="true"
        android:onClick="next"
        android:src="@android:drawable/ic_media_next" />

    <ImageButton
        android:id="@+id/imageButton1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="26dp"
        android:layout_marginTop="63dp"
        android:onClick="play"
        android:src="@android:drawable/ic_media_play" />

    <ImageButton
        android:id="@+id/imageButton4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/imageButton3"
        android:layout_below="@+id/imageButton3"
        android:layout_marginTop="14dp"
        android:onClick="previous"
        android:src="@android:drawable/ic_media_previous" />

</RelativeLayout>
MainActivity.java
public class MainActivity extends Activity {
	private ServiceConnection conn;
	private IPlayerAIDL player;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Intent intent=new Intent("android.intent.action.PLAYMUSIC_SERVICE");
		conn=new InnerServiceConnection();
		bindService(intent, conn, BIND_AUTO_CREATE);
	}
	private class InnerServiceConnection implements ServiceConnection{

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			player=IPlayerAIDL.Stub.asInterface(service);
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
		}
		
	}
	public void play(View v){
	    	try {
				player.play();
			} catch (RemoteException e) {
				e.printStackTrace();
			}
	    }
	public void pause(View v){
		try {
			player.pause();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	public void next(View v){
		try {
			player.next();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	public void previous(View v){
    	try {
			player.previous();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
    }
	@Override
	protected void onDestroy() {
		unbindService(conn);
		super.onDestroy();
	}
}


關於AIDL使用自定義數據類型傳輸可以參考這裏-------------- AIDL自定義數據類型





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