之前寫過一篇關于AIDL的入門篇,相信看過的朋友對AIDL多少會有點了解,如果對AIDL還不是很了解的可以看一下這篇文章Android進程通信(IPC)之AIDL快速入門.今天這篇文章主要講講在AIDL中如何傳遞對象。
在AIDL中可以傳輸的數據有:
- 基本數據類型(除short)
- String
- CharSequence
- List
- Map
我們知道Android的每個進程都運行在獨立的虛擬機中,所以進程之間通信不是直接的,如果我們要傳遞一個對象我們得把對象拆分成Android系統可識別的數據單元,然后系統再重新將數據單元合成傳遞給另一個進程。這個過程其實也就是對象的序列化。在AIDL中傳遞對象我們要做以下幾個步驟:
- 讓你的對象類實現
Parcelable
接口 - 在該類中實現
writeToParcel
方法 - 添加一個靜態構造器
CREATOR
- 創建你要傳遞的對象的
aidl
文件
接下我們來看一個具體的例子,這里我沒有新建工程還是用的上篇文章的工程,如果對該工程目錄結構不清楚的童鞋可以猛戳這里Android進程通信(IPC)之AIDL快速入門
首先在我們服務端建立要傳遞的對象類:
package com.example.administrator.aidldemo;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
//從Parcel中讀出之前寫進的數據
protected Person(Parcel in) {
this.name = in.readString();
this.age = in.readInt();
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
//反序列化,把我們通過writeToParcel方法寫進的數據再讀出來
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//實現 Parcelable 接口所需重寫的方法1,一般不用管
@Override
public int describeContents() {
return 0;
}
//實現 Parcelable 接口所需重寫的方法2,將對象的每個字段寫入到Parcel中
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
我們看到,這個對象實現Parcelable
接口,覆寫了接口中的兩個方法:describeContents()
、writeToParcel(Parcel dest, int flags)
,前一個方法我們暫且不用管它,主要看writeToParcel(Parcel dest, int flags)
這個方法,這個方法就是把該對象中的字段寫進Parcel,相當于一艘輪船,我們把要運輸的貨物都裝進去。通過查看文檔我們發現Parcel
是沒有writeShort()
這個方法的,這也正是AIDL無法傳遞short
類型的數據的原因。然后我們再靜態創建構造器CREATOR
,注意要加final
修飾且名字必須為CREATOR
不能更改。其中我們要實現兩個方法,主要看createFromParcel(Parcel in)
這個方法,這個方法其實就是把我們之前寫進Parcel中的數據在讀出來。這里要注意,我們之前怎么寫的就怎么讀出來,即哪個字段先寫進去,就先讀取哪個字段。最后覆寫了toString
方法,以便之后打印數據。好了這樣我們實體類就建好了。
然后我們在服務端創建該類的aidl
文件,文件名為Person
。
package com.example.administrator.aidldemo;
parcelable Person;
結構很簡單,首先聲明包名,然后在你建的對象類名前加上parcelable
,這樣我們的類aidl文件也建好了。
然后我們新建一個aidl
接口,叫做DataTestAidlInterface
package com.example.administrator.aidldemo;
import com.example.administrator.aidldemo.Person;
interface DataTestAidlInterface {
List<Person> getPersonListIn(Person person);
}
這個接口定義了一個方法,方法很簡單,傳入一個Person
對象返回一個List,注意要導入之前我們創建的 Person
對象。好了我們現在構建下。
Fuck!報錯了。。。好吧我們來看一下報的什么,這里有句話很關鍵
aidl.exe E 4888 4892 type_namespace.cpp:130] 'Person' can be an out type, so you must declare it as in, out or inout.
在AIDL進程傳遞數據時,都有一個數據拆包和打包的過程,這是一個很耗系統內存的,我們要在傳遞的數據前面加上in
、out
、inout
來表示數據傳遞的方向。
-
in
表示客戶端向服務端傳遞數據,當服務端的數據發生改變,不會影響到客戶端 -
out
表示服務端對數據修改,客戶端會同步變動 -
inout
表示客戶端和服務端的數據總是同步的
然后我們修改一下代碼:
package com.example.administrator.aidldemo;
import com.example.administrator.aidldemo.Person;
interface DataTestAidlInterface {
List<Person> getPersonListIn(in Person person);
}
這樣我們的aidl
接口也定義好了
然后我們新建一個服務
package com.example.administrator.aidldemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class IRemoteService extends Service {
List<Person> persons;
@Nullable
@Override
public IBinder onBind(Intent intent) {
persons = new ArrayList<>();
return iBinder;
}
private IBinder iBinder = new DataTestAidlInterface.Stub(){
@Override
public List<Person> getPersonList(Person person) throws RemoteException {
persons.add(person);
return persons;
}
}
}
這個服務很簡單,當綁定該服務是新建一個List
,用于存放客戶端添加的Person
對象。這樣我們的服務端代碼就都寫完了,然后我們把服務端的Person
類文件和aidl
文件,以及我們定義的DataTestAidlInterface.aidl
文件都拷貝到客戶端對應的文件夾下
在客戶端,我們新建一個布局文件,里面就一個按鈕,用于傳遞對象
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="傳遞對象"/>
</LinearLayout>
接下來看一下主界面:
public class DataTestActivity extends Activity{
private Button btn;
private List<Person> persons;
private DataTestAidlInterface mDataTestAidlInterface;
private ServiceConnection conn = new ServiceConnection() {
//服務連接成功時回調
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mDataTestAidlInterface = DataTestAidlInterface.Stub.asInterface(service);
}
//斷開服務時回調
@Override
public void onServiceDisconnected(ComponentName name) {
if(mDataTestAidlInterface != null){
mDataTestAidlInterface = null;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceSate){
super.onCreate(savedInstanceSate);
setContentView(R.layout.data_test_layout);
btn = (Button)findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Person person = new Person("張三", 10);
try {
persons = mDataTestAidlInterface.getPersonListIn(person);
} catch (RemoteException e) {
e.printStackTrace();
}
Toast.makeText(DataTestActivity.this,persons.toString(),
Toast.LENGTH_SHORT).show();
}
});
bindService();
}
//綁定服務
private void bindService(){
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.administrator.aidldemo",
"com.example.administrator.aidldemo.IRemoteService"));
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy(){
super.onDestroy();
unbindService(conn);
}
}
當界面啟動時,就綁定到我們之前在服務端中的IRemoteService
,綁定成功后,將調用conn中的onServiceConnected
方法來獲取在服務端實現好的接口。這樣獲取到接口后,當我們點擊按鈕時,新建一個Person
對象,然后調用獲取到的DataTestAidlInterface
接口中getPersonListIn
方法,之后把返回的List
顯示出來。
運行后效果如下: