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介面:
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;
}
}
}
至此,我們的客戶端實現也完成了。執行後,你就可以看到你想要的效果。