Parcel作为Android Binder通信的基础,从源码的角度,了解下parcel的特性,还是很有必要的。
Serializable vs Parcelable
这两者都是Android的序列化方式,不过还是有很大的区别的
Serializable
的底层是通过IO来实现的,序列化是通过ObjectOutputStream
来实现,比如字符串的序列化方法,就是标准的io方法
# java.io.ObjectOutputStream
/**
* Writes given string to stream, using standard or long UTF format
* depending on string length.
*/
private void writeString(String str, boolean unshared) throws IOException {
handles.assign(unshared ? null : str);
long utflen = bout.getUTFLength(str);
if (utflen <= 0xFFFF) {
bout.writeByte(TC_STRING);
bout.writeUTF(str, utflen);
} else {
bout.writeByte(TC_LONGSTRING);
bout.writeLongUTF(str, utflen);
}
}
而Parcelable
的内部,是通过Parcel
来实现的,本质是native层的共享内存,不涉及io,性能更好,我们应该是尽量避免使用Serializable
来序列化
parcel的Java层只是一个壳
先看下Java层的代码,首先通过Parce.obtain()来获取一个parcel对象
代码语言:javascript复制/**
* Retrieve a new Parcel object from the pool.
*/
@NonNull
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;
p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
return p;
}
}
}
return new Parcel(0);
}
有一个缓存池,复用Parcel对象,第一次调用,返回新建的Parcel对象
代码语言:javascript复制private Parcel(long nativePtr) {
init(nativePtr);
}
private void init(long nativePtr) {
if (nativePtr != 0) {
mNativePtr = nativePtr;
mOwnsNativeParcelObject = false;
} else {
mNativePtr = nativeCreate();
mOwnsNativeParcelObject = true;
}
}
走到了底层的nativeCreate方法
另外看下parcel Java层的其他方法,比如写入跟读取int、long
代码语言:javascript复制public final void writeInt(int val) {
nativeWriteInt(mNativePtr, val);
}
public final void writeLong(long val) {
nativeWriteLong(mNativePtr, val);
}
public final void writeInt(int val) {
nativeWriteInt(mNativePtr, val);
}
public final void writeLong(long val) {
nativeWriteLong(mNativePtr, val);
}
可以发现,parcel的Java层,只是一个壳,具体的实现,都是在native层处理的
native层Parcel的初始化
代码语言:javascript复制# frameworks/base/core/jni/android_os_Parcel.cpp
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
Parcel* parcel = new Parcel();
//返回的是parcel指针的long值
return reinterpret_cast<jlong>(parcel);
}
parcel*是指针类型,reinterpret_cast是返回这个指针转成long类型的值
对于reinterpret_cast的这个特性,可以用下面这个简单的demo验证下
代码语言:javascript复制using namespace std;
int main() {
auto *data = new Sales_data();
auto lValue = reinterpret_cast<long>(data);
cout << "hex value " << data << " long value " << lValue;
return EXIT_SUCCESS;
}
运行后的结果hex value 0x7f9c184059a0 long value 140308398496160
16进制的值0x7f9c184059a0转成10进制,刚好就是140308398496160,所以nativeCreate()方法,返回的值,就是这个parcel对象指针的值(也就是在内存中的位置)
parcel的本质其实是一个连续的内存空间
先看下parcel的一些本地变量
代码语言:javascript复制# frameworks/native/libs/binder/include/binder/Parcel.h
uint8_t* mData; //内存空间的位置指针
size_t mDataSize; //当前保存的内容大小
size_t mDataCapacity;//总的容量大小
mutable size_t mDataPos; //当前位置的偏移量
mData就是保存内容的地方,其在这里赋值
代码语言:javascript复制# frameworks/native/libs/binder/Parcel.cpp
uint8_t* data = (uint8_t*)malloc(desired);
if (!data) {
mError = NO_MEMORY;
return NO_MEMORY;
}
mData = data;
malloc方法,开辟了一个长度为desired的连续空间,mData就是指向这个内存空间的指针,当这个连续空间不足的时候,就会扩容,开辟更大的空间
代码语言:javascript复制status_t Parcel::growData(size_t len)
{
if (len > SIZE_MAX - mDataSize) return NO_MEMORY; // overflow
if (mDataSize len > SIZE_MAX / 3) return NO_MEMORY; // overflow
size_t newSize = ((mDataSize len)*3)/2;
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
: continueWrite(std::max(newSize, (size_t) 128));
}
可以看到,新的空间大小,是按照现有的内容大小的1.5倍扩容
写入一个int值
Java层是调用的nativeWriteInt方法,实际走到了下面这里
代码语言:javascript复制static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
//nativePtr其实就是parcel指针的值,可以转成parcel指针
if (parcel != NULL) {
const status_t err = parcel->writeInt32(val);
//parcel指针调用parcel的writeInt32方法
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
}
}
status_t Parcel::writeInt32(int32_t val)
{ //继续往下调用
return writeAligned(val);
}
template<class T>
status_t Parcel::writeAligned(T val) {
static_assert(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
if ((mDataPos sizeof(val)) <= mDataCapacity) {
restart_write:
*reinterpret_cast<T*>(mData mDataPos) = val;
//把val的值写入当前指针mDataPos偏移量位置
return finishWrite(sizeof(val));
}
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
status_t Parcel::finishWrite(size_t len)
{
//重新更新mDataPos的偏移量
mDataPos = len;
if (mDataPos > mDataSize) {
mDataSize = mDataPos;
}
return NO_ERROR;
}
int的写入,先在当前偏移量的位置,写入int值,然后再更新偏移量mDataPos到int值后面
写入一个string
由于string的长度是不固定的,需要先写入string的长度,然后再写入string的内容
代码语言:javascript复制# frameworks/native/libs/binder/Parcel.cpp
status_t Parcel::writeString16(const String16& str)
{
return writeString16(str.string(), str.size());
}
status_t Parcel::writeString16(const char16_t* str, size_t len)
{
if (str == nullptr) return writeInt32(-1);
// 先写入string的长度
status_t err = writeInt32(len);
if (err == NO_ERROR) {
len *= sizeof(char16_t);
//len是指string占用内存空间的长度,writeInplace返回合适的写入的位置
uint8_t* data = (uint8_t*)writeInplace(len sizeof(char16_t));
//len后面要加sizeof(char16_t),因为字符串的结尾,系统会默认加一个'