1. 程式人生 > >Android遠端介面之AIDL——Parcelable、in、out、inout簡例

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;

綜合以上,在使用自定義類時,需要有幾個步驟: (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機制。

原始碼