標籤:
目錄(?)[-]
- 自訂的Parcelable類
- AIDL檔案
- 服務的實現
- Client的實現
- 同步和非同步
文章轉載只能用於非商業性質,且不能帶有虛擬貨幣、積分、註冊等附加條件。轉載須註明出處:http://blog.csdn.net/flowingflying/
在之前的StockQuote遠程服務的介面中的方法為double getQuote(String ticker);。在遠程服務中的方法的資料類型支援原始類型(primitive),如int這類的;支援String、CharSequence;複雜是類型支援List、Map,但在使用中有一些限制;如果我們希望使用自訂的類作為類型,需要使用Parcelable。本筆記將學習如何通過Parcelable封裝在遠程服務中的方法調用中進行複雜的資料傳遞。
遠程服務實際是處理序間通訊,因此在介面的串連中,並不是傳遞對象,而是向原始類型那樣,複製資料。Java的對象實際是C中的指標,Java並非沒有指標,而是除了primitive類型外,全部是指標,當都是指標時,開發人員感覺不到指標和非指標的差異,有時會有錯誤的感覺,以為Java無指標。由於不同進程有各自的記憶體空間,另一個進程不能操控其他進程的記憶體空間,也就是不能操控其他進程記憶體空間的對象。client和遠程服務建立了AIDL介面的串連,在操作時,將資料的內容整份進行傳遞,類似我們在socket中傳遞資料,我們傳遞資料的地址(指標/對象)是毫無意義的,我們必須傳遞資料的內容。
自訂的Parcelable類
Parcel是訊息(資料和對象)的容器,可以在IBinder(即遠程服務的串連)中傳遞。Parcel是Android中設計用於高效能的IPC傳輸,因此我們不要將Parcel資料直接寫到物理存貯中,因為Parcel中某個資料的改變,會使得其他資料變得不可讀。Parcelable則是介面,我們自訂的資料類型,需要實現該介面,才能作為Parcel在IBinder中傳遞。
下面的例子很簡單,我們自訂的資料類型Person含有兩個資料,一是int age,一是String name。
public class Person implements Parcelable{
//【1】自訂的類型具體包含的資料,本例為age和name。
private int age = 0;
private String name = null;
@Override
public int describeContents() {
return 0;
}
/* 【2】要實現Parcelable介面,需要實現writeToParcel()和readFromParcel(),實現將對象(資料)寫入Parcel,和從Parcel中讀出對象。需要注意,寫的順序和讀的順序必須一致,因為Parcel類是快速serialization和deserialization機制,和bundle不同,沒有索引機制,是線性資料存貯和讀取。
* 注意其中readFromParcel()並不是overrider,而是我們自己提供的方法,如果我們不提供,就要在
* private Person(Parcel in){
* age = in.readInt();
* name = in.readString();
* }
* 鑒於實際的資料類型會比小例子複雜,以及便於代碼閱讀,我們仿照writeToParcel()的命名,給出readFromParcel() */
@Override
public void writeToParcel(Parcel out, int flag) {
out.writeInt(age); //先寫入age
out.writeString(name); //其次寫如name
}
public void readFromParcel(Parcel in){
age = in.readInt(); //先讀出age,保持與寫同順序
name = in.readString(); //其次讀出name,保持與寫同順序
}
/* 【3】:提供建構函式,用於從Parcel中建立對象,也即是讀的過程。這裡設定為private,禁止外部調用 */
private Person(Parcel in){
readFromParcel(in);
}
// 這個建構函式,是方便我們在client中創新相關對象,並將之作為介面串連中調用方法的的參數
public Person(){
}
/* 下面是我們在自訂類中的自訂方法,本例簡單提供age和name的讀寫 */
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
/*【4】 實現Parcelable介面的類必須要有一個static field稱為CREATOR,用於實現Parcelable.Creator介面的對象。在AIDL檔案自動產生的Java介面中,IBinder將調用Parcelable.Creator來獲得傳遞對象:_arg1 = cn.wei.flowingflying.proandroidservice.Person.CREATOR.createFromParcel(data); */
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source); //見【3】
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
AIDL檔案
我們定義Person.aidl對Parcelable進行說明,由於我們已經有一個Person.java,所以系統不會再自動產生相關的java代碼。
package cn.wei.flowingflying.proandroidservice;
parcelable Person;
當定義了Person.aidl中,我們可以在介面定義中使用該類型。在非原語類型,非String類型,其他的類型在介面中作為參數需要描述傳遞的方向in、out或者inout。
package cn.wei.flowingflying.proandroidservice;
import cn.wei.flowingflying.proandroidservice.Person;
interface IStockQuoteService2{
String getQuote(in String ticker, in Person requester);
}
服務的實現
服務的實現和之前的遠程服務沒有什麼區別,只是方法中資料類型的不同,下面是StockQuoteRemoteService2.java的片段,為了更好地和使用者互動,Service會在通知欄上出現,詳細可以下載我們的原始碼進行查看。此外我們需要在AndroidManifest.xml中對service進行定義。
public class StockQuoteRemoteService2 extends Service{
public class StockQuoteServiceImpl extends IStockQuoteService2.Stub{
private int count = 0;
@Override
public String getQuote(String ticker, Person requester) throws RemoteException {
return "Hello " + requester.getName() + "! Quote for " + ticker + " is " + (20.0+(count++));
}
}
... ...
@Override
public IBinder onBind(Intent arg0) {
return new StockQuoteServiceImpl();
}
}
Client的實現
我們建立一個project來作為client。這個client同樣需要瞭解介面,瞭解介面中所涉及的parcelable Person的定義,因此我們需要將service中的IStockQuoteService2.aidl,Person.aidl以及Person.java拷貝過來。
和之前的client只是串連介面後,調用方法的參數不同,相關代碼如下:
public class MainActivity extends Activity {
private IStockQuoteService2 stockService2 = null;
… …
private ServiceConnection servConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
… …
stockService2 = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
… …
stockService2 = IStockQuoteService2.Stub.asInterface(service);
}
};
private void callService(){
try{
Person person = new Person();
person.setAge(25);
person.setName("Flowingflying");
String response = stockService2.getQuote("WEI", person);
Toast.makeText(this, response, Toast.LENGTH_LONG).show();
}catch(RemoteException e){
Log.e("Client2",e.toString());
}
}
}
同步和非同步
這裡學習的服務都是同步的,因為我們在UI thread中進行調用。如果service需要大量的運算,我們希望能夠運行在後台,也就是client在後台線程中對服務進行調用。
本博文涉及的例子代碼,可以在Pro Android學習:Android service小例子中下載。
相關連結: 我的Android開發相關文章
【轉】 Pro Android學習筆記(八一):服務(6):複雜資料Parcel