问题
能否深入讲解一下Binder中的序列化
一、什么是序列化
百度的结果
序列化 (Serialization)是将对象的状态[信息转换]为可以存储或传输的形式的过程。
在序列化期间,对象将其当前状态写入到临时或[持久性]存储区。
以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
举个例子,看看这个Person对象如何序列化。
class Person {
String name;
int age;
}
序列化的时候将String和int写个某个内存区域,反序列化的时候读取这个内存区域,重新构造一个Person对象。
class Person implements Parcelable {
String name;
int age;
public static final Parcelable.Creator CREATOR = new Creator() {
@Override
public Object createFromParcel(Parcel parcel) {
Person person = new Person();
person.name = parcel.readString();
person.age = parcel.readInt();
return person ;
}
@Override
public Object[] newArray(int i) {
return new Object[0];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeInt(age);
}
}
Parcelable就是按照上述的意思实现的。
这块内存区域就是Parcel对象对应的`内存区域。
writeToParcel就是序列化,代码的内部实现就会将name和age按照规则写到parcel中
createFromParcel就是反序列化,代码的内部实现就会从Parcel中读取name和age,然后重新构造Person类。
难道Binder的序列化就这么简单,当然还没有,Binder考虑的更多。
二、序列化后是同一个对象吗?
当然可以很明显的看到序列化后就不是一个对象。你操作反序列之后对象,其实在操作一个新的对象,和原始对象没有关系。
Binder的目标就是让对象在经过Binder接口传递之后,反序列化后的对象用起来和原始对象一样。
对Person类进行扩展,加入一个IBinder对象和一个File。
class Person {
String name;
int age;
IBinder action;//Binder对象
File file;//sdcard/1.txt
}
经过Binder的传递这个对象之后,对端将会拿到一个对象
class Person {
String name;
int age;
IBinder action;//BinderProxy对象
File file;//sdcard/1.txt
}
IBinder的对象从Binder对象变成BinderProxy对象,File对象变成了新的File都指向了同一个文件。
我们在来好好理解一下这段话:反序列化后的对象用起来和原始对象一样。
2.1 IBinder用起来是不是和原始的IBinder一样
当左边的进程调用Binder.transact最后调用的是Binder.onTransact
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
当右边的进程调用BinderProxy的transact接口会通过Binder驱动跨进程调用Binder.onTransact。
//Entry point from android_util_Binder.cpp's onTransact.
@UnsupportedAppUsage
private boolean execTransact(int code, long dataObj, long replyObj,
int flags) {
return execTransactInternal(code, dataObj, replyObj, flags, callingUid);
}
private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,
int callingUid) {
res = onTransact(code, data, reply, flags);
return res;
}
因为BinderProxy和Binder都实现了IBinder接口,都实现了transact接口,所以将BinderProxy.transact和Binder.transact来说两者的效果是一样的。
思考
如果把右边的对象再传递给左边的进程,会发生什么,IBinder对象是Binder还是BinderProxy
答案:https://www.jianshu.com/p/740f1ee32fd1
2.2 File用起来是不是和原始的File一样
这个部分我就不另外将了,可以参考我的另外一篇文章,https://www.jianshu.com/p/0f300d539ff5,直接说结论。
经过Binder传递的File指向内核中同一file结构体,所以右边进程读写File和左边进程读写File是一样。
需要注意的是如果用ParcelFileDescriptor,因为在传递File之前重新打开了一次File,这样子虽然操作的是同一个文件,可能无法共享读写指针了。
三、Binder作出的努力
Binder为以下的对象类型实现了跨进程传递,但是本质上只实现了Binder对象,BinderProxy对象,和FD的传递,配合上IBinder对象的跨进程调用和Linux的一切皆文件的设计理念,基本达成了Parcelable对象在经过Binder接口传递后,用起来和原始的对象一样目标。
enum {
BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),//Binder
BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),//Binder
BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),//BinderProxy
BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),//BinderProxy
BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),//文件fd
BINDER_TYPE_FDA = B_PACK_CHARS('f', 'd', 'a', B_TYPE_LARGE),//文件fd数组,但是似乎没看到用的地方
BINDER_TYPE_PTR = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE),//这是什么类型,留一个思考题
};
思考题
BINDER_TYPE_PTR目前只支持在hwbinder中,看看他的本质是什么,是否实现用起来和原始对象一样。
需要查看的代码
system/libhwbinder/Parcel.cpp中的writeBuffer
common/drivers/android/binder.c中case BINDER_TYPE_PTR的处理
总结
如果你想要真正了解Binder的序列化只需要去研究parcel.cpp和binder.c,整个步骤分成三步:
第一步:客户端使用parcel.cpp提供接口负责将对象A打包成binder驱动可以识别格式,并传递给Binder驱动
第二步:binder驱动按照自己支持的能力,将对象转化成对服务端可以访问的区域以及parcel.cpp可以识别的格式
第三步:服务端使用parcel.cpp提供接口将驱动传递过来的数据重新解析对象A`。
任何序列化和反序列化的机制,让我去研究,我都按照反序列化后的对象用起来和原始对象一样。
要求去研究,然后去探究对应的跨进程,跨芯片通信机制是如何实现这个目标的。
尾巴
去了解设计者的设计理念和设计目标,有助于你真正的透过现象看到本质。
网友评论