Parcelable与Serializable

Serializable是Java为我们提供的一个标准化的序列化接口。

Parcelable是Android为我们提供的序列化的接口。

比较:

  1. Parcelable相对于Serializable的使用相对复杂一些。
  2. Parcelable的效率相对Serializable也高很多。Serializable是使用IO流完成的,Parcelable 通过对象指针共享内存,指针挪动。
  3. Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable在外界有变化的情况下不能很好的保证数据的持续性。尽管Serializable效率低点,但此时还是建议使用Serializable。存储到设备或者网络传输上选择Serializable。

Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下Parcelable和Serializable的效率对比 Parcelable vs Serializable 号称快10倍的效率。

选择序列化方法的原则

  1. 在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
  2. Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
  3. Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

序列化与反序列化

序列化:将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据 。

反序列化:从二进制流(序列)转化为对象的过程。

Parcelable的使用

进行Android开发的时候,无法将对象的引用传给Activities或者Fragments,我们需要将这些对象放到一个Intent或者Bundle里面,然后再传递。简单看一下:

实现Parcelable基本有三个步骤:序列化、反序列化、描述。

序列化和反序列化前面已经了解过了,不再赘述。描述指的是返回的是内容的描述信息,只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.lautung.myapplication;

import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {

private String name;
private int age;
private int gender;

//反序列化
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
gender = in.readInt();
}


public Person(String name, int age, int gender) {
this.name = name;
this.age = age;
this.gender = gender;
}


//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeInt(gender);
}

@Override
public int describeContents() {
return 0;
}

//反序列化
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}

@Override
public Person[] newArray(int size) {
return new Person[size];
}
};

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
}

writeToParcel:序列化过程
Creator与protected Book(Parcel in)配合实现反序列化,转换为对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

//反序列化
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
gender = in.readInt();
}

//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeInt(gender);
}

注意:writeToParcel中writeString的顺序与protected Book(Parcel in) {}中readString的顺序是一样的。

Parcelable中对象和集合的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import android.os.Parcel;
import android.os.Parcelable;

import java.util.ArrayList;

/**
* Created by fengxing on 2018/3/28.
*/

public class ParcelDemo implements Parcelable {

private int count;
private String name;
private ArrayList<String> tags;
private Book book;
// ***** 注意: 这里如果是集合 ,一定要初始化 *****
private ArrayList<Book> books = new ArrayList<>();


/**
* 序列化
*
* @param in
*/
protected ParcelDemo(Parcel in) {
count = in.readInt();
name = in.readString();
tags = in.createStringArrayList();

// 读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
book = in.readParcelable(Book.class.getClassLoader());


//读取集合也分为两类,对应写入的两类

//这一类需要用相应的类加载器去获取
in.readList(books, Book.class.getClassLoader());// 对应writeList


//这一类需要使用类的CREATOR去获取
in.readTypedList(books, Book.CREATOR); //对应writeTypeList

//books = in.createTypedArrayList(Book.CREATOR); //对应writeTypeList


//这里获取类加载器主要有几种方式
getClass().getClassLoader();
Thread.currentThread().getContextClassLoader();
Book.class.getClassLoader();


}

public static final Creator<ParcelDemo> CREATOR = new Creator<ParcelDemo>() {
@Override
public ParcelDemo createFromParcel(Parcel in) {
return new ParcelDemo(in);
}

@Override
public ParcelDemo[] newArray(int size) {
return new ParcelDemo[size];
}
};

/**
* 描述
*
* @return
*/
@Override
public int describeContents() {
return 0;
}

/**
* 反序列化
*
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(count);
dest.writeString(name);
//序列化一个String的集合
dest.writeStringList(tags);
// 序列化对象的时候传入要序列化的对象和一个flag,
// 这里的flag几乎都是0,除非标识当前对象需要作为返回值返回,不能立即释放资源
dest.writeParcelable(book, 0);

// 序列化一个对象的集合有两种方式,以下两种方式都可以


//这些方法们把类的信息和数据都写入Parcel,以使将来能使用合适的类装载器重新构造类的实例.所以效率不高
dest.writeList(books);


//这些方法不会写入类的信息,取而代之的是:读取时必须能知道数据属于哪个类并传入正确的Parcelable.Creator来创建对象
// 而不是直接构造新对象。(更加高效的读写单个Parcelable对象的方法是:
// 直接调用Parcelable.writeToParcel()和Parcelable.Creator.createFromParcel())
dest.writeTypedList(books);


}
}

Book类,需要先实现Parcelable,实现步骤就不贴出来了,和普通的对象一样,实现三个过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

import android.os.Parcel;
import android.os.Parcelable;

/**
* Created by fengxing on 2018/3/28.
*/

public class Book implements Parcelable {

protected Book(Parcel in) {
}

public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}

@Override
public Book[] newArray(int size) {
return new Book[size];
}
};

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
}
}

写入和读取集合有两种方式:

  • 一种是写入类的相关信息,然后通过类加载器去读取, –> writeList | readList
  • 二是不用类相关信息,创建时传入相关类的CREATOR来创建 –> writeTypeList | readTypeList | createTypedArrayList

第二种效率高一些,但是一定要注意如果有集合定义的时候一定要初始化public ArrayList<T> demo = new ArrayList<>();

Parcel的简介

Parcel翻译过来是打包的意思,其实就是包装了我们需要传输的数据,然后在Binder中传输,也就是用于跨进程传输数据。

简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,下图是这个过程的模型。

Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。
Parcelable通过Parcel实现了read和write的方法,从而实现序列化和反序列化

02-Parcel源码分析

(看源码的工具,使用SourceInsight)

①:初始化流程源码分析

Parcel.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
mNativePtr == 拿到了 Parcel.cpp 对象指针地址

package android.os;

public final class Parcel {

/**
* 用于初始化 Parcel对象的函数
* 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;
}
}
}
// 注意:这里的 new Parcel(0); 操作,会进入下面代码
return new Parcel(0);
}
}
1
2
3
4
5
6
7
8
9
private Parcel(long nativePtr) {
if (DEBUG_RECYCLE) {
mStack = new RuntimeException();
}
//Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack

// 注意:这里的 init函数,会继续你下面代码
init(nativePtr);
}
1
2
3
4
5
6
7
8
9
private void init(long nativePtr) { // mNativePtr == Parcel.cpp 对象指针地址
if (nativePtr != 0) {
mNativePtr = nativePtr;
mOwnsNativeParcelObject = false;
} else {
mNativePtr = nativeCreate(); // 注意:这里的nativeCreate();会返回C++的头指针内存地址
mOwnsNativeParcelObject = true;
}
}
1
private static native long nativeCreate(); // 注意:从这里进入Native层代码

android_os_Parcel.cpp的nativeCreate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
static const JNINativeMethod gParcelMethods[] = {
// @CriticalNative
{"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize},
// @CriticalNative
{"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
// @CriticalNative
{"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition},
// @CriticalNative
{"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity},
// @FastNative
{"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize},
// @CriticalNative
{"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition},
// @FastNative
{"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity},

// @CriticalNative
{"nativePushAllowFds", "(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
// @CriticalNative
{"nativeRestoreAllowFds", "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},

{"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeByteArray},
{"nativeWriteBlob", "(J[BII)V", (void*)android_os_Parcel_writeBlob},
// @FastNative
{"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt},
// @FastNative
{"nativeWriteLong", "(JJ)V", (void*)android_os_Parcel_writeLong},
// @FastNative
{"nativeWriteFloat", "(JF)V", (void*)android_os_Parcel_writeFloat},
// @FastNative
{"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble},
{"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
{"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
{"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},

{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
{"nativeReadByteArray", "(J[BI)Z", (void*)android_os_Parcel_readByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
// @CriticalNative
{"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt},
// @CriticalNative
{"nativeReadLong", "(J)J", (void*)android_os_Parcel_readLong},
// @CriticalNative
{"nativeReadFloat", "(J)F", (void*)android_os_Parcel_readFloat},
// @CriticalNative
{"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble},
{"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
{"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
{"nativeReadFileDescriptor", "(J)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},

{"openFileDescriptor", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
{"dupFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor},
{"closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},

// 在这里就找到了,【nativeCreate】
{"nativeCreate", "()J", (void*)android_os_Parcel_create},
{"nativeFreeBuffer", "(J)J", (void*)android_os_Parcel_freeBuffer},
{"nativeDestroy", "(J)V", (void*)android_os_Parcel_destroy},

{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
{"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall},
{"nativeCompareData", "(JJ)I", (void*)android_os_Parcel_compareData},
{"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom},
// @CriticalNative
{"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},

{"getGlobalAllocSize", "()J", (void*)android_os_Parcel_getGlobalAllocSize},
{"getGlobalAllocCount", "()J", (void*)android_os_Parcel_getGlobalAllocCount},

// @CriticalNative
{"nativeGetBlobAshmemSize", "(J)J", (void*)android_os_Parcel_getBlobAshmemSize},
};

再接着搜索,(void*) android_os_Parcel_create 函数即可

1
2
3
4
5
6
7
8
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
// 实例化C++ 对象,此对象 就是一个 内存地址指针
Parcel* parcel = new Parcel();

// 返回:内存地址指针,并且用reinterpret_cast 转换jlong类型,这代码一看就知道比OpenCV写得好
return reinterpret_cast<jlong>(parcel);
}
②:writeInt源码分析
Parcel.java:
1
2
3
4
5
6
7
8
9
/**
* Write an integer value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public final void writeInt(int val) {

// 注意:这里会调用native层,下面就会分析这个代码
nativeWriteInt(mNativePtr, val);
}
1
2
@FastNative // 思考:行参一的作用是可以通过此内存地址去寻找native层的C++对象
private static native void nativeWriteInt(long nativePtr, int val); // 下面进入native

android_os_Parcel.cpp 的 nativeWriteInt:

1
2
3
4
5
6
7
8
9
10
11
{"nativeWriteByteArray",      "(J[BII)V", (void*)android_os_Parcel_writeByteArray},
{"nativeWriteBlob", "(J[BII)V", (void*)android_os_Parcel_writeBlob},
// @FastNative

// 还是采用动态注册的方式,搜索函数实现 android_os_Parcel_writeInt 即可
{"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt},

// @FastNative
{"nativeWriteLong", "(JJ)V", (void*)android_os_Parcel_writeLong},
// @FastNative
{"nativeWriteFloat", "(JF)V", (void*)android_os_Parcel_writeFloat},

android_os_Parcel_writeInt 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {

// 通过在Java层保存的,C++对象首地址,来查找到C++对象 Parcel* parcel
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
// 把内容写入进去,这里是调用到哪里去? 看下面代码...
const status_t err = parcel->writeInt32(val);
if (err != NO_ERROR) {
// 抛出异常
signalExceptionForError(env, clazz, err);
}
}
}

Parcel.cpp 的 writeInt32:

1
2
3
4
status_t Parcel::writeInt32(int32_t val)
{
return writeAligned(val);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<class T> // 相当于Java的泛型
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));

// mDataPos:内存首地址的当前挪动位置
// mDataCapacity:共享内存的总大小
// mData:共享内存的首地址

if ((mDataPos+sizeof(val)) <= mDataCapacity) {

// 就相当于,指针++ 挪动好位置 然后存放刚刚挪动的位置
// * mData共享内存的首地址 = val赋值给左边;
restart_write: *reinterpret_cast<T*>(mData+mDataPos) = val;

// 下面分析 finsihWrite函数
return finishWrite(sizeof(val));
}

status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}

finsihWrite函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
status_t Parcel::finishWrite(size_t len)
{
if (len > INT32_MAX) {
// don't accept size_t values which may have come from an
// inadvertent conversion from a negative int.
return BAD_VALUE;
}

//printf("Finish write of %d\n", len);
mDataPos += len; // 【上面函数存值后,把指针位置进行挪动一次,方便后续的值 存放】

ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos);
if (mDataPos > mDataSize) {
mDataSize = mDataPos;
ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize);
}
//printf("New pos=%d, size=%d\n", mDataPos, mDataSize);
return NO_ERROR;
}

参考文献

  1. https://www.cnblogs.com/tangZH/p/10998065.html
  2. https://blog.csdn.net/jdsjlzx/article/details/109064067