Android的AIDL 解釋Demo
前言
Android的AIDL不僅可以在繫結服務中傳遞一些Android規定的資料型別的資料,還可以傳遞一些複雜型別的資料。但是與傳遞系統允許的資料型別相比,複雜型別資料的傳遞要做更多的工作,本篇部落格就講解一下如何使用AIDL介面通過繫結服務在程序間傳遞資料。關於AIDL傳遞規定型別資料的內容,不瞭解的朋友可以看看之前的部落格: AIDL傳遞系統允許型別資料。
本篇部落格的主要內容:
AIDL傳遞複雜型別物件的特殊處理
前面已經介紹了通過AIDL介面在程序間傳遞系統允許的資料,如果需要傳遞一個複雜型別的物件,就沒那麼簡單了,需要額外做一些處理。如下:
- 定義資料介面的AIDL檔案中,使用parcelable關鍵字,例如:parcelable Message;
- 在其資料實現類中實現Parcelable介面,並實現對應的方法。
- 在業務介面的AIDL檔案中,使用import引入資料介面AIDL的包名。
例如:Message.aidl
1 parcelable Message;
例如:IGetMsg.aidl
1 package com.example.aidlservicedemo.domain;
2
3 // 這是兩個自定義類
4 import com.example.aidlservicedemo.domain.Message;
5 import com.example.aidlservicedemo.domain.User;
6
7 interface IGetMsg{
8 // 在AIDL介面中定義一個getMes方法
9 List<Message> getMes(in User us);
10 }
Parcelable與Parcel介面
先來說說Android物件序列化,在Android中序列化物件主要有兩種方式,實現Serializable介面或是實現Parcelable介面。Serializable介面是JavaSE原生支援的,而Parcelable介面是Android所特有的,它的序列化和反序列化的效率均比Serializable介面高,而AIDL進行在程序間通訊(IPC),就是需要實現這個Parcelable介面。
Parcelable介面的作用:實現了Parcelable介面的例項,可以將自身的資料資訊寫入一個Parcel物件,也可以從parcel中恢復到物件的狀態。而Parcel就是完成資料序列化寫入的載體。
上面提到Parcel,再來聊聊Parcel是什麼?Android系統設計之初,定位就是針對記憶體受限的裝置,因此對效能要求更好,所以系統中採用程序間通訊(IPC)機制,必然要求效能更優良的序列化方式,所以Parcel就被設計出來了,其定位就是輕量級的高效的物件序列化機制與反序列化機制。如果讀一下Android的底層程式碼,會發現Parcel是使用C++實現的,底層直接通過Parcel指標操作記憶體實現,所以它的才更高效。
Parcel也提供了一系列的方法幫助寫入資料與讀取資料,這裡簡單介紹一下:
- obtain():在池中獲取一個新的Parcel。
- dataSize():得到當前Parcel物件的實際儲存空間。
- dataPostion():獲取當前Parcel物件的偏移量。
- setDataPosition():設定當前Parcel物件的偏移量。
- recyle():清空、回收當前Parcel物件的記憶體。
- writeXxx():向當前Parcel物件寫入資料,具有多種過載。
- readXxx():從當前Parcel物件讀取資料,具有多種過載。
簡單來說,Parcelable通過writeToParcel()方法,對複雜物件的資料寫入Parcel的方式進行物件序列化,然後在需要的時候,通過其內定義的靜態屬性CREATOR.createFromParcel()進行反序列化的操作。Parcelable對Parcel進行了包裝,其內部就是通過操作Parcel進行序列化與反序列化的。
Parcelable與Parcel均定義在android.os包下,而這種機制不僅用於AIDL,還可以用於Intent傳遞資料等其他地方,這不是本篇部落格的主題,以後用到再詳細介紹。
實現Parcelable介面
定義好資料介面的AIDL檔案後,需要定義一個數據實現類,實現Parcelable介面,並實現對應的方法,Parcelable有如下幾個必須要實現的抽象方法:
- abstract int describeContents():返回一個位掩碼,表示一組特殊物件型別的Parcelable,一般返回0即可。
- asbtract void writeToParcel(Parcel dest,int flags):實現物件的序列化,通過Parcel的一系列writeXxx()方法序列化物件。
除了上面兩個方法,還需要在實現類中定義一個名為"CREATOR",型別為"Parcelable.Creator<T>"的泛型靜態屬性,它實現了物件的反序列化。它也有兩個必須實現的抽象方法:
- abstract T createFromParcel(Parcel source):通過source物件,根據writeToParcel()方法序列化的資料,反序列化一個Parcelable物件。
- abstract T[] newArray(int size):建立一個新的Parcelable物件的陣列。
例如:
1 @Override
2 public int describeContents() {
3 return 0;
4 }
5
6 @Override
7 public void writeToParcel(Parcel dest, int flags) {
8 Log.i("main", "服務端Message被序列化");
9 dest.writeInt(id);
10 dest.writeString(msgText);
11 dest.writeString(fromName);
12 dest.writeString(date);
13 }
14
15 public static final Parcelable.Creator<Message> CREATOR = new Creator<Message>() {
16
17 @Override
18 public Message[] newArray(int size) {
19 return new Message[size];
20 }
21
22 @Override
23 public Message createFromParcel(Parcel source) {
24 Log.i("main", "服務端Message被反序列化");
25 return new Message(source.readInt(), source.readString(),
26 source.readString(), source.readString());
27 }
28 };
從上面示例中可以看出,使用writeToParcel()方法進行序列化,通過CREATOR.createFromParcel進行反序列化,它們都傳遞一個Parcel型別的物件,這裡要注意的是兩個方法中Parcel物件的writeXxx()和readXxx()方法的順序必須一致,因為一般序列化資料是以鏈的形式序列化的,如果順序不對,反序列化的資料會出錯。
AIDL傳遞複雜型別物件Demo
關鍵點已經講到, 下面通過一個簡單的Demo來演示AIDL傳遞複雜物件。
AIDL介面:
com.example.aidlservicedemo.domain.Message.aidl
Message.aidlcom.example.aidlservicedemo.domain.Message.java
Message.javacom.example.aidlservicedemo.domain.User.aidl
User.aidlcom.example.aidlservicedemo.domain.User.java
User.java服務:
com.example.aidlservicedemo.
CustomTypeService.java客戶端:
com.example.aidlClientdemo.
CustomTypeActivity.java效果展示:
AIDL傳遞物件序列化過程詳解
通過上面Demo列印的日誌,解釋一下序列化的過程,開啟Logcat檢視日誌。
從上圖的PID列可以看出這是兩個執行緒間的互動。
流程是這樣的,客戶端傳遞一個User物件給服務端,服務端通過User物件處理資料,返回兩個Message物件給客戶端。
首先,在客戶端傳遞給服務端User物件前,客戶端先把User物件序列化,然後傳遞給服務端之後,服務端接收到的是一段序列化後的資料,它再按照原定的規則對資料進行反序列化,然後處理User。當服務端查到這個User有兩條Message時,需要傳遞這兩條Message物件給客戶端,在傳遞前對Message物件進行序列化,客戶端收到服務端傳遞過來的序列化後的資料,再根據既定的規則進行反序列化,得到正確的物件。
從這個流程可以看出,在程序間傳遞的資料必定是被序列化過的,否則無法傳遞。而對於那些AIDL預設允許傳遞的資料型別(int、double、String、List等),它們其實內部已經實現了序列化,所以無需我們再去指定序列化規則。但是對於複雜型別物件而言,系統無法知道如何去序列化與反序列化,所以需要我們指定規則。