在项目使用AIDL中,发现自己以前对于AIDL的理解有些误区。
以前使用AIDL增加方法,只要iadl文件里的方法顺序不变,在所有的方法之后,增加新方法,新的AIDL是可以兼容原先的AIDL的。即用新的AIDL也可以和老的AIDL进行通讯 (只要不调用新增加的方法就不会有问题)。
但是,在传递LIst<Bean>
的过程中,发现,如果Bean
的字段有增减,则新Aidl和老Aidl通讯会有问题,无法兼容。此时,Client端和Server端的Bean
必须是同一个,不能有差别。
以下是做的相关的实验:
RemoteSong.java
public class RemoteSong implements Parcelable {
private int id;
private String title;
private int duration;
//get / set 省略
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.id);
dest.writeString(this.title);
dest.writeInt(this.duration);
}
public RemoteSong() {
}
protected RemoteSong(Parcel in) {
readFromParcel(in);
}
public void readFromParcel(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
this.duration = in.readInt();
}
public static final Creator<RemoteSong> CREATOR = new Creator<RemoteSong>() {
@Override
public RemoteSong createFromParcel(Parcel source) {
return new RemoteSong(source);
}
@Override
public RemoteSong[] newArray(int size) {
return new RemoteSong[size];
}
};
}
RemotePlayList.java
public class RemotePlayList implements Parcelable {
private int id;
private String name;
private List<RemoteSong> songs = new ArrayList<>();
//get / set 省略
public List<RemoteSong> getSongs() {
if (songs == null) {
return new ArrayList<>();
}
return songs;
}
public void setSongs(List<RemoteSong> songs) {
this.songs = songs;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.id);
dest.writeString(this.name);
dest.writeTypedList(this.songs);
}
public RemotePlayList() {
}
protected RemotePlayList(Parcel in) {
readFromParcel(in);
}
public void readFromParcel(Parcel in) {
this.id = in.readInt();
this.name = in.readString();
this.songs = in.createTypedArrayList(RemoteSong.CREATOR);
}
public static final Creator<RemotePlayList> CREATOR = new Creator<RemotePlayList>() {
@Override
public RemotePlayList createFromParcel(Parcel source) {
return new RemotePlayList(source);
}
@Override
public RemotePlayList[] newArray(int size) {
return new RemotePlayList[size];
}
};
}
IMyAidlInterface.aidl
// IMyAidlInterface.aidl
package com.heiko.aidltest;
import com.heiko.aidltest.bean.Person;
import com.heiko.aidltest.bean.RemotePlayList;
interface IMyAidlInterface {
RemotePlayList getLocalList();
}
此时,在Aidl Client端程序中,调用getLocalList()获取的数据是正确的
[{
"duration": 1000,
"id": 0,
"title": "标题0"
}, {
"duration": 1001,
"id": 1,
"title": "标题1"
}, {
"duration": 1002,
"id": 2,
"title": "标题2"
}, {
"duration": 1003,
"id": 3,
"title": "标题3"
}, {
"duration": 1004,
"id": 4,
"title": "标题4"
}]
接着,我们把 Aidl Server端程序的RemoteSong中增加一个fileName字段
public class RemoteSong implements Parcelable {
private int id;
private String title;
private int duration;
private String fileName;
//get / set 省略
public RemoteSong() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.id);
dest.writeString(this.title);
dest.writeInt(this.duration);
dest.writeString(this.fileName);
}
protected RemoteSong(Parcel in) {
readFromParcel(in);
}
private void readFromParcel(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
this.duration = in.readInt();
this.fileName = in.readString();
}
public static final Creator<RemoteSong> CREATOR = new Creator<RemoteSong>() {
@Override
public RemoteSong createFromParcel(Parcel source) {
return new RemoteSong(source);
}
@Override
public RemoteSong[] newArray(int size) {
return new RemoteSong[size];
}
};
}
此时,可以发现,再运行Server端和Client端程序,会发现,获取的数据不正确。
[{
"duration": 1000,
"id": 0,
"title": "标题0"
}, {
"duration": 0,
"id": 1324770695
}, {
"duration": 1001,
"id": 1,
"title": "标题1"
}, {
"duration": 0,
"id": 1324770695
}, {
"duration": 1002,
"id": 2,
"title": "标题2"
}]
为什么数据会出现偏差
先附上IMyAidlInterface.java
@Override
public com.heiko.aidltest.bean.RemotePlayList getLocalList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
com.heiko.aidltest.bean.RemotePlayList _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getLocalList, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
_result = com.heiko.aidltest.bean.RemotePlayList.CREATOR.createFromParcel(_reply);
} else {
_result = null;
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
查看AIDL生成的IMyAidlInterface.java文件及RemotePlayList里的Parcelable 读写规则,可以发现,是通过Parcelable来进行读写的
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.id);
dest.writeString(this.name);
dest.writeTypedList(this.songs);
}
public void readFromParcel(Parcel in) {
this.id = in.readInt();
this.name = in.readString();
this.songs = in.createTypedArrayList(RemoteSong.CREATOR);
}
进入writeTypedList方法,我们来看下
public final <T extends Parcelable> void writeTypedList(List<T> val) {
if (val == null) {
writeInt(-1);
return;
}
int N = val.size();
int i=0;
writeInt(N);
while (i < N) {
T item = val.get(i);
if (item != null) {
writeInt(1);
item.writeToParcel(this, 0);
} else {
writeInt(0);
}
i++;
}
}
可以看到,List会遍历,逐个调用RemoteSong的writeToParcel进行存储,我们知道,上面Demo中,Client端的RemoteSong只写入了4个字段
private void readFromParcel(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
this.duration = in.readInt();
this.fileName = in.readString();
}
而Server端的RemoteSong却只读取了3个字段
public void readFromParcel(Parcel in) {
this.id = in.readInt();
this.title = in.readString();
this.duration = in.readInt();
}
所以,多个遍历读取的时候,就会出现混乱,数据读取会出现错误。
public final <T> ArrayList<T> createTypedArrayList(Parcelable.Creator<T> c) {
int N = readInt();
if (N < 0) {
return null;
}
ArrayList<T> l = new ArrayList<T>(N);
while (N > 0) {
if (readInt() != 0) {
l.add(c.createFromParcel(this));
} else {
l.add(null);
}
N--;
}
return l;
}
小结
所以,我们在使用AIDL传递对象集合的时候,尽量不要修改Bean对象,如果要修改,要确保Server端和Client端的Bean对象保持统一。