android 遠端服務傳遞自定義資料型別
在Android系統中,程序間傳遞的資料包括Java語言支援的基本資料型別和使用者自定義的資料型別,為了使資料能夠穿越程序邊界,所有資料都必須是“可打包”。對於Java語言的基本資料型別,打包過程是自動完成的。但對於自定義的資料型別,使用者需要實現Parcelable介面,使自定義的資料型別能夠轉換為系統級原語儲存在Parcel物件中,穿越程序邊界後可再轉換為初始格式。
AIDL支援的資料型別如下表:
型別 | 說明 | 需要引入 |
---|---|---|
基本資料型別 | boolean、byte、short、int、 long、char、float、double |
否 |
String | java.lang.String | 否 |
CharSequence | java.lang.CharSequence | 否 |
List | 其中元素都必須是AIDL支援的資料型別 | 否 |
Map | 其中ket和value都必須是AIDL支援的資料型別 | 否 |
其他AIDL介面 | 任何其他使用AIDL語言生成的介面型別 | 是 |
Parcelable物件 | 實現Parcelable介面的物件 | 是 |
下面以ParcelMathServiceDemo示例為參考,說明如何在遠端服務中使用自定義型別。這個示例是RemoteMathServiceDemo示例的延續,檢視我的RemoteMathServiceDemo示例文章
不同之處在於MathService服務涉及到了自定義資料型別,在接受到輸入引數後,將不再只向呼叫者返回long型別的資料,而是返回一個包含“加、減、乘、除”全部運算結果的物件。這個物件是一個自定義的類,為了能夠使其他AIDL檔案可以使用這個自定義的類,需要使用AIDL語言宣告這個類。
首先建立AllResult.aidl檔案
然後在AllResult.aidl檔案中宣告AllResult類,AllResult.aidl檔案中的程式碼如下:
// AllResult.aidl
package com .example.remotemathservicedemo;
//在這裡宣告任何非預設型別
parcelable AllResult;
(這個簡陋的aidl檔案可不會自動生成對應的java介面檔案哦)
然後在IMathService.aidl中為全運算增加新函式,返回型別就是在AllResult.aidl中定義的AllResult,程式碼如下:
// IMathService.aidl
package com.example.remotemathservicedemo;
// Declare any non-default types here with import statements
interface IMathService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
long Add(long a,long b);
AllResult ComputeAll(long a,long b);
}
然後Build->Make Project 重新生成介面檔案,使增加的新函式生效。這時候在新生成的IMathService.java中會提示錯誤,因為其中需要關於AllResult的資訊都找不到。
接下來手動構造AllResult類,可以放在和自動生成的IMathService.java同目錄下。下面先把AllResult.java的完整程式碼貼出來:
package com.example.remotemathservicedemo;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by yinghao on 2016/5/7.
*/
public class AllResult implements Parcelable {
public long addResult;
public long subResult;
public long mulResult;
public double divResult;
public AllResult(long addResult, long subResult, long mulResult, double divResult) {
this.addResult = addResult;
this.subResult = subResult;
this.mulResult = mulResult;
this.divResult = divResult;
}
//從Parcel物件得到資料,拆包函式
public AllResult(Parcel parcel) {
addResult = parcel.readLong();
subResult = parcel.readLong();
mulResult = parcel.readLong();
divResult = parcel.readDouble();
}
@Override
public int describeContents() {
return 0;
}
//顧名思義 wiiteToParcel 打包函式
//將AllResult類內部的資料按照特定順序寫入Parcel物件
//寫入順序必須與建構函式讀取順序一致
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(addResult);
dest.writeLong(subResult);
dest.writeLong(mulResult);
dest.writeDouble(divResult);
}
//實現靜態公共欄位Creator,用來使用Parcel物件構造AllResult物件
public static final Parcelable.Creator<AllResult> CREATOR = new Creator<AllResult>() {
@Override
public AllResult createFromParcel(Parcel parcel) {
return new AllResult(parcel);
}
@Override
public AllResult[] newArray(int size) {
return new AllResult[size];
}
};
}
AllResult繼承於Parcelable,其中的資料就是全運算的運算結果。
AllResult類除了基本的建構函式以外,還需要以Parcel物件為輸入的建構函式,並且要過載打包函式writeToParcel()。 把這個類寫完後,你就會發現IMathService.java中的錯誤提示消失了。
到這裡,關於自定義資料型別的工作就完成了,然後在MathService.java檔案中,增加用來進行全運算的ComputAll()函式,並將運算結果儲存在AllResult物件中。MathService.java中的完整程式碼如下:
package com.example.remotemathservicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.widget.Toast;
/**
* Created by yinghao on 2016/5/7.
*/
public class MathService extends Service {
/*
1. 建立 IMathService.Stub的例項mBinder並實現AIDL檔案定義的遠端服務介面
2. 在onBind()方法中將mBinder返回給遠端呼叫者
*/
private final IMathService.Stub mBinder = new IMathService.Stub(){
@Override
public long Add(long a, long b) throws RemoteException {
return a + b;
}
@Override
public AllResult ComputeAll(long a, long b) throws RemoteException {
long addResult = a + b;
long subResult = a - b;
long mulResult = a * b;
double divResult = a / b;
AllResult allResult = new AllResult(addResult, subResult, mulResult, divResult);
return allResult;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(MathService.this, "遠端繫結:MathService", Toast.LENGTH_SHORT).show();
return mBinder;
}
//Return true if you would like to have the service's onRebind method later called when new clients bind to it.
@Override
public boolean onUnbind(Intent intent) {
Toast.makeText(MathService.this, "取消遠端繫結", Toast.LENGTH_SHORT).show();
return false;
}
}
到這裡,服務端的工作就全部完成了,當然如果你沒看過我的上篇關於遠端服務傳遞基本資料型別的話,那還缺少一步,註冊Service。
然後就是呼叫者如何去繫結和呼叫服務,首先需要將服務端的Module中的兩個aidl檔案以及對應的java介面檔案和自己構造的AllResult.java類全部拷貝到呼叫端Module中。(關於原因可在上篇文章中檢視)
然後將原來的“加法運算”功能改為“全運算”功能,關於remoteMathCallerDemo中MainActivity.java中的完整程式碼如下:
package com.example.remotemathcallerdemo;
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.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.remotemathservicedemo.AllResult;
import com.example.remotemathservicedemo.IMathService;
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button bind;
private Button unbind;
private Button add;
private boolean isBound = false;
private IMathService mathService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mathService = IMathService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mathService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
bind = (Button) findViewById(R.id.bind);
unbind = (Button) findViewById(R.id.unbind);
add = (Button) findViewById(R.id.add);
bind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isBound) {
final Intent serviceIntent = new Intent();
serviceIntent.setAction("com.example.remote.MathService");
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
isBound = true;
}
}
});
unbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isBound) {
unbindService(mConnection);
isBound = false;
mathService = null;
}
}
});
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mathService == null) {
textView.setText("未繫結遠端服務");
return;
}
long a = Math.round(Math.random() * 100);
long b = Math.round(Math.random() * 100);
AllResult result = null;
try {
result = mathService.ComputeAll(a, b);
} catch (RemoteException e) {
e.printStackTrace();
}
String msg = "";
if (result != null) {
msg += String.valueOf(a) + "+" + String.valueOf(b) + "=" +
String.valueOf(result.addResult) + "\n";
msg += String.valueOf(a) + "-" + String.valueOf(b) + "=" +
String.valueOf(result.subResult) + "\n";
msg += String.valueOf(a) + "*" + String.valueOf(b) + "=" +
String.valueOf(result.mulResult) + "\n";
msg += String.valueOf(a) + "/" + String.valueOf(b) + "=" +
String.valueOf(result.divResult) + "\n";
}
textView.setText(msg);
}
});
}
}
執行效果如下:
對於遠端服務的一些基礎知識總結以及遠端服務傳遞基本資料型別的Demo在Android Service 遠端服務(點選檢視),歡迎大家指出差錯或交流。