前言
上一篇我们研究了一下Serializable,虽然它使用方便,但是效率和兼容性上确实还存在一些问题。为此Android提供了特有的Parcelable机制,按照官方说法,速度是Serializable的十倍左右。
正文
public class TestBean implements Parcelable {
private int x;
private int y;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public static final Parcelable.Creator<TestBean> CREATOR = new Creator<TestBean>() {
@Override
public TestBean[] newArray(int size) {
return new TestBean[size];
}
@Override
public TestBean createFromParcel(Parcel in) {
TestBean bean = new TestBean();
bean.setX(in.readInt());
bean.setY(in.readInt());
return bean;
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(x);
dest.writeInt(y);
}
}
上面是一个Parcelable的示例,对比Serializable,可以看出有以下优劣:
- Serializable用法简单,Parcelable实现要相对复杂
- Serializable只能序列化所有属性,Parcelable可以在write和read方法选择性的序列化
- Serializable具有可继承性,Parcelable虽然也具有,但是仍然需要完善实现,因为CREATOR是静态的。
通过用法我们已经对两者的区别有了一些认识,接下来我们看看Parcelable的源码工作原理。 提到跨进程通信,AIDL是常见的解决方案之一。
如果你对AIDL的使用还不够了解,可以先阅读我之前学过的博客:
AIDL使用学习(一):基础使用学习
AIDL使用学习(二):跨进程回调以及RemoteCallbackList
AIDL使用学习(三):源码深入分析
我们就看生成的文件ITestInterface:
public interface ITestInterface extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.lzp.aidlstudy.ITestInterface {
private static final java.lang.String DESCRIPTOR = "com.lzp.aidlstudy.ITestInterface";
...
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getCalculateResult: {
data.enforceInterface(descriptor);
com.lzp.aidlstudy.bean.TestBean _arg0;
if ((0 != data.readInt())) {
_arg0 = com.lzp.aidlstudy.bean.TestBean.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
int _result = this.getCalculateResult(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.lzp.aidlstudy.ITestInterface {
...
@Override
public int getCalculateResult(com.lzp.aidlstudy.bean.TestBean bean) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((bean != null)) {
_data.writeInt(1);
bean.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_getCalculateResult, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
}
我们简化了部分与分析无关的代码,从上面的代码我们已经看到了Parcelable熟悉的身影,首先我们得到的代理对象Proxy,通过Proxy对象调用getCalculateResult()方法:
// 如果参数TestBean不等于null
if ((bean != null)) {
_data.writeInt(1);
// 调用writeToParcel把要序列化的数据写入到某处
bean.writeToParcel(_data, 0);
} else {
// 参数等于null,就写个0
_data.writeInt(0);
}
我们先不管序列化的数据到底写到哪去了,反正是保存起来了,接着调用:
mRemote.transact(Stub.TRANSACTION_getCalculateResult, _data, _reply, 0);
mRemote就是Stub类的实例,通过Binder的源码,在transact调用了onTransact:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
......
// 如果参数不是空的,通过反序列化得到参数
if ((0 != data.readInt())) {
_arg0 = com.lzp.aidlstudy.bean.TestBean.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
// 本地计算结果,再次返回
int _result = this.getCalculateResult(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
......
}
}
过程就是这么简单,现在我们唯一的困惑是:
这些序列化的数据到底写到哪去了呢
Parcelable只是一个接口,具体的序列化原理是借助Parcel,我们刚才也看到这样的代码:
android.os.Parcel _data = android.os.Parcel.obtain();
bean.writeToParcel(_data, 0);
Parcel的write和read方法全是native方法:
// 截取的部分native方法
@FastNative
private static native void nativeWriteInt(long nativePtr, int val);
@FastNative
private static native void nativeWriteLong(long nativePtr, long val);
@FastNative
private static native void nativeWriteFloat(long nativePtr, float val);
@FastNative
private static native void nativeWriteDouble(long nativePtr, double val);
static native void nativeWriteString(long nativePtr, String val);
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
一般来说使用JNI的速度肯定是比使用Java方法要快,因为他绕过了Java层的api,不过这个并不是速度的保证,真正的优势是避免的大量的反射操作,减少了临时变量的创建,提高了序列化的效率。
根据Parcel的注释,我们了解了数据的去向:
有一个专门负责IBinder传输数据的容器。
(一个可以进程共享的内存区)
Parcel可以把数据压入,另一端的Parcel可以把数据取走
(通过实现我们可以推断出保存数据的是一个先进先出的堆栈)
Parcel仅仅是为了实现高性能的IPC通信,在其他的持久化方案在并不推荐。
到这里Parcelable的序列化机制就已经分析结束了,如果我们非要把Parcelable保存到本地怎么办呢?
我这里给出一个简单的示例:
private fun writeTestData() {
Thread(Runnable {
val student = Student("zhangsan", 18)
val parcel = Parcel.obtain()
// 因为Parcel内部有缓存复用
// 设置数据的位置指针为头部
parcel.setDataPosition(0)
student.writeToParcel(parcel, 0)
val fos = FileOutputStream(path)
fos.write(parcel.marshall())
fos.flush()
fos.close()
parcel.recycle()
}).start()
}
private fun readTestData() {
Thread(Runnable {
val fis = FileInputStream(path)
val data = fis.readBytes()
val parcel = Parcel.obtain()
// 因为Parcel内部有缓存复用
// 设置数据的位置指针为头部
parcel.unmarshall(data, 0, data.size)
parcel.setDataPosition(0)
val student = Student(parcel)
parcel.recycle()
runOnUiThread {
findViewById<TextView>(R.id.text).text = student.toString()
}
}).start()
}
刚才提到了Parcel内部有缓存,推荐使用Parcel.obtain()来获取一个可用的Parcel对象,类似的还有Message.obtain():
/**
* Retrieve a new Parcel object from the pool.
*/
public static Parcel obtain() {
final Parcel[] pool = sOwnedPool;
synchronized (pool) {
Parcel p;
for (int i=0; i<POOL_SIZE; i++) {
p = pool[i];
if (p != null) {
pool[i] = null;
if (DEBUG_RECYCLE) {
p.mStack = new RuntimeException();
}
p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
return p;
}
}
}
return new Parcel(0);
}
总结
最后对Parcelable的序列化做一个总结:
- Parcelable的序列化需要借助Parcel。
- Parcel通过JNI把序列化数据写入到进程的共享内存中,或从进程共享内存中读数据。
- Parcel推荐使用Parcel.obtain()方法获取可用实例。
- 与Serializable相比,Parcelable避免了大量反射操作,在效率上有很大提升。
- Parcelable仅仅是IPC的高效实现方案,其他场景慎用。
ok,这一篇就结束了,有什么问题欢迎大家留言指正。