Android遠程接口之AIDL——Parcelable、in、out、inout簡例

AIDL簡義

Android中的數據傳輸、方法調用等,常見的是集中在應用程序內的Activity之間,如Activity-A傳遞到Activity-B。

這樣的數據傳輸、方法等都是在一個應用程序間調用,也就是在一個進程內。那如果我們要在不同的進程間傳遞數據,我們要怎麼做呢?不在同一個進程間,它們是無法共用內存的,Android爲了實現進程間的數據共享,提供了AIDL機制(安卓遠程接口定義語言)——(AIDL: Android Interface definition language)。

AIDL的原理以及原理分析,可參考網上其他的解釋。

下文將主要介紹AIDL的各種修飾符(in、out、inout)以及類序列化Parcelable的使用示例(網上原理解釋很多,但對out、inout的示例很少見到)。

AIDL的實現

AIDL的應用場景,一般情況下是有兩個進程,一個進程提供方法,一個進程調用方法。

我們習慣將提供方法的進程定義爲Service端、將調用方法的進程定義爲Client,就是我們常說的AIDL服務端和AIDL客戶端。

AIDL的數據傳輸支持類型有特殊要求,並非所有的數據類型都能像以往一樣傳遞:

支持數據類型如下:
1. Java 的原生類型
2. String 和CharSequence
3. List 和 Map ,List和Map 對象的元素必須是AIDL支持的數據類型;  以上三種類型都不需要導入(import)
4. AIDL 自動生成的接口  需要導入(import)
5. 實現android.os.Parcelable 接口的類.  需要導入(import)。 

那我們接下來演示,如何提供AIDL的服務端和客戶端。

這裏重點是in、out、inout修飾符以及Parcelable的使用!常見的是in、Parcelable,少用的out、inout。

這幾種修飾符,可理解如下:

in:客戶端的參數輸入;

out:服務端的參數輸入;

inout:這個可以叫輸入輸出參數,客戶端可輸入、服務端也可輸入。客戶端輸入了參數到服務端後,服務端也可對該參數進行修改等,最後在客戶端上得到的是服務端輸出的參數。

AIDL的服務端(Service端)實現

常用做法:
1、定義一個AIDL接口,在該接口中寫方法;
2、方法中參數修飾符可以是in、out、inout,也有自定義的類,該類需要實現Parcelable接口;
3、實現該接口;
4、開放給客戶端一個標誌,用於訪問服務端接口方法。
按以上的三個步驟,我們來寫下示例代碼:

1、定義AIDL接口:

新建一個文件,文件名爲IBase.aidl,內容爲:
package com.example.aidl;
import com.example.aidl.UserInfo;//注意引用
interface IBase
{
     int    add(int i,int j);
     String getUserInfo(in UserInfo userinfo);
     void   getaList(out String[] list);  
     void   setaList(in String[] list);
     void   gettList(inout String[] list);
}
上方的接口中的方法,我們演示了各種修飾符以及Parcelable。
這裏有個需要注意的地方,我們在文件頭中有import一個類,這個是必要的,雖然UserInfo類和我們定義的接口是在同一個包下。
Parcelable的使用,我們首先要實現這個UserInfo的Parcelable接口實現,然後引用它,如下:
package com.example.aidl;

import android.os.Parcel;
import android.os.Parcelable;

public class UserInfo implements Parcelable{

	
	private String name;
	private String adress;
	private int  age;
	
	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the adress
	 */
	public String getAdress() {
		return adress;
	}

	/**
	 * @param adress the adress to set
	 */
	public void setAdress(String adress) {
		this.adress = adress;
	}

	/**
	 * @return the age
	 */
	public int getAge() {
		return age;
	}

	/**
	 * @param age the age to set
	 */
	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public int describeContents() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags) {
		// TODO Auto-generated method stub
		dest.writeString(name);
		dest.writeString(adress);
		dest.writeInt(age);
	}
   
	public static final Parcelable.Creator<UserInfo> CREATOR=new Creator<UserInfo>() {

		@Override
		public UserInfo createFromParcel(Parcel source) {
			// TODO Auto-generated method stub
			UserInfo userInfo=new UserInfo();
			userInfo.setName(source.readString());
			userInfo.setAdress(source.readString());
			userInfo.setAge(source.readInt());
			return userInfo;
		}

		@Override
		public UserInfo[] newArray(int size) {
			// TODO Auto-generated method stub
			return new UserInfo[size];
		}
	};
}
聲明這個自定義類:
在同一個包下,創建一個UserInfo.aidl文件,內容如下:
package com.example.aidl;

parcelable UserInfo;

綜合以上,在使用自定義類時,需要有幾個步驟:
(1)實現Parcelable接口,具體過程可參考:http://blog.csdn.net/yangzhaomuma/article/details/50452651
(2)在同一包名下,創建類同名的AIDL文件;
(3)在使用該類時,需要在文件頭引用(import)。

2、實現接口方法:

新建一個java文件,我們暫命名爲:AIDLService.java,該文件是實現AIDL的接口。內容如下:
package com.example.aidl_server_csdn;


import com.example.aidl.IBase;
import com.example.aidl.UserInfo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

public class AIDLService extends Service{

	String info="";
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return stub;
	}

	private IBase.Stub stub=new IBase.Stub() {

		/**
		 * 基本類型的使用示例
		 */
		@Override
		public int add(int i, int j) throws RemoteException {
			// TODO Auto-generated method stub
			return i+j;
		}
		/**
		 * Parcelable類userinfo的使用示例
		 */
		@Override
		public String getUserInfo(UserInfo userinfo) throws RemoteException {
			// TODO Auto-generated method stub
			String resultString="name:"+userinfo.getName()+" "+"adress:"+userinfo.getAdress()+" "+"age:"+userinfo.getAge();
			return resultString;
		}
		/**
		 * out修飾類型的使用
		 * 表示服務端輸入
		 */
		@Override
		public void getaList(String[] list) throws RemoteException {
			// TODO Auto-generated method stub
			list[0]="服務端賦值信息:"+info;
		}

		/**
		 * inout修飾類型的使用示例
		 */
		@Override
		public void gettList(String[] list) throws RemoteException {
			// TODO Auto-generated method stub
			String totalString="";
			/**
			 * 從客戶端取得的信息
			 */
			String receviceFromClientString="";
			for(int i=0;i<list.length;i++)
			{
				receviceFromClientString+=list[i];
			}
			/**
			 * 從服務端返回的信息
			 */
			totalString+="從客戶端收到的信息爲:"+receviceFromClientString+" \n在此我們新增一段返回信息:good";
			list[0]=totalString;
		}
		/**
		 * in修飾類型的使用示例
		 */
		@Override
		public void setaList(String[] list) throws RemoteException {
			// TODO Auto-generated method stub
			/**
			 * 取得客戶端傳入的值
			 */
			if(list.length>0)
				info=list[0];
		}
		
	};
}

3、開放一個標誌,用於客戶端訪問

常用的做法,我們可以在AndroidManifest.xml中做如下定義:
 <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <service
            android:name="com.example.aidl_server_csdn.AIDLService"
            >
            <intent-filter>
             <action android:name="com.service.use"></action>
                </intent-filter>
            </service>
    </application>
我們設定了一個過濾值:com.service.use,客戶端可以通過這個來訪問服務端。


至此,我們服務端的代碼就寫完了。當你運行該服務端在android系統上時,系統會安裝一個service.apk並運行。

AIDL客戶端(Client端)的實現

服務端已經實現好了,那客戶端要如何調用呢?
按我們樸素的思想,應該就是獲取服務端的實例,並用這個實例調用相應的方法了。AIDL也是這麼想的。但AIDL的做法有點特別。
1、複製服務端的AIDL接口和Parcelable類等到服務端(習慣的做法,將AIDL的整個包都複製到客戶端);
2、連接服務端;
3、獲取服務端的接口實現的實例;
4、調用方法;
我們也按這步驟來實現我們的客戶端。

1、複製整個AIDL包到客戶端

這個你複製,黏貼即可。

2、連接服務端

/**
	 * 連接AIDL
	 */
	public void Connect()
	{
		bindService(new Intent("com.service.use"), serviceConnection, Context.BIND_AUTO_CREATE);
	}
	/**
	 * 連接類實現
	 */
	ServiceConnection serviceConnection=new ServiceConnection() {
		
		@Override
		public void onServiceDisconnected(ComponentName name) {
			// TODO Auto-generated method stub
			iBase=null;
			Toast.makeText(MainActivity.this, "連接斷開", Toast.LENGTH_SHORT).show();
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// TODO Auto-generated method stub
			iBase=IBase.Stub.asInterface(service);
			Toast.makeText(MainActivity.this, "連接成功", Toast.LENGTH_SHORT).show();
		}
	};
	

3、獲取服務端的實例

其實,這個一般在連接服務端成功的時候,就已經做了,就如上面代碼中的iBase=IBase.Stub.asInterface(service);

4、調用方法

我們在上一步驟中,已經獲得了iBase實例,調用方法時,我們用以下方法:
 iBase.add(7, 8);
iBase.setaList(new String[]{"戰國劍"});
等。
下面,貼出客戶端調用的所有代碼:
package com.example.aidl_csdn;

import com.example.aidl.IBase;
import com.example.aidl.UserInfo;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import android.R.integer;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;

public class MainActivity extends Activity implements OnClickListener{

	IBase iBase;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Button btn=(Button)findViewById(R.id.btn);
		btn.setOnClickListener(this);
		Button btn1=(Button)findViewById(R.id.btn1);
		btn1.setOnClickListener(this);
		Button btn2=(Button)findViewById(R.id.btn2);
		btn2.setOnClickListener(this);
		Button btn3=(Button)findViewById(R.id.btn3);
		btn3.setOnClickListener(this);
		Button btn4=(Button)findViewById(R.id.btn4);
		btn4.setOnClickListener(this);
		Button btn5=(Button)findViewById(R.id.btn5);
		btn5.setOnClickListener(this);
	}

	/**
	 * 連接AIDL
	 */
	public void Connect()
	{
		bindService(new Intent("com.service.use"), serviceConnection, Context.BIND_AUTO_CREATE);
	}
	/**
	 * 連接類實現
	 */
	ServiceConnection serviceConnection=new ServiceConnection() {
		
		@Override
		public void onServiceDisconnected(ComponentName name) {
			// TODO Auto-generated method stub
			iBase=null;
			Toast.makeText(MainActivity.this, "連接斷開", Toast.LENGTH_SHORT).show();
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// TODO Auto-generated method stub
			iBase=IBase.Stub.asInterface(service);
			Toast.makeText(MainActivity.this, "連接成功", Toast.LENGTH_SHORT).show();
		}
	};
	
	/**
	 * 基礎類型相加
	 * @return
	 * @throws RemoteException
	 */
	public  int  sum() {
		if(iBase!=null)
		{
			int result=0;
			try {
				result = iBase.add(7, 8);
				Toast.makeText(getApplicationContext(), "基礎類型相加結果:"+result, Toast.LENGTH_SHORT).show();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return result;
		}
		return 0;
	}
	/**
	 * in型傳值到服務端
	 */
	public void setaList()
	{
		if(iBase!=null)
		{
			try {
				iBase.setaList(new String[]{"戰國劍"});
				Toast.makeText(getApplicationContext(), "傳值'戰國劍'到服務端", Toast.LENGTH_SHORT).show();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	/**
	 * out型取服務端返回值
	 */
	public void getaList()
	{
		if(iBase!=null)
		{
			String[] list =new String[1];
			try {
				iBase.getaList(list);
				Toast.makeText(getApplicationContext(), "服務端返回內容:"+list[0], Toast.LENGTH_SHORT).show();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	/**
	 * Parcelable類的傳入
	 */
	public void ParcelableUse()
	{
		if(iBase==null)
			return;
		UserInfo userInfo=new UserInfo();
		userInfo.setName("戰國劍");
		userInfo.setAdress("中國");
		userInfo.setAge(18);
		 try {
			 String resultString=iBase.getUserInfo(userInfo);
			Toast.makeText(getApplicationContext(), "服務端返回內容:"+resultString, Toast.LENGTH_SHORT).show();
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * inout類型修飾的使用
	 */
	public void inoutUse()
	{
		if(iBase==null)
			return;
		try {
			String[] inStrings={"inout中in的傳入"};
			iBase.gettList(inStrings);
			Toast.makeText(getApplicationContext(), "inout服務端返回內容:"+inStrings[0], Toast.LENGTH_SHORT).show();
			
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch (v.getId()) {
		case R.id.btn:
		Connect();
			break;
		case R.id.btn1:
			sum();
			break;
		case R.id.btn2:
			ParcelableUse();
			break;
		case R.id.btn3:
			setaList();
			break;
		case R.id.btn4:
			getaList();
			break;
		case R.id.btn5:
			inoutUse();
			break;

		default:
			break;
		}
	}

}

至此,我們的客戶端實現也完成了。運行後,你就可以看到你想要的效果。

注意信息

這是一個簡單的AIDL實例,主要是爲了說明AIDL中各種修飾符的使用和自定義類的傳遞。AIDL中,與我們常用方法不同的也就是這些東西,希望這個例子的分享有助於理解AIDL機制。

源碼


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