preface
In Android, when serialization is common, Google officially recommends using Parcelable because it is much more efficient (about 10 times) than Serializable provided by the JDK.
Here we first discuss how to use Parcelable, and then from the source of the interpretation of Parcelable efficiency why so high. Finally, analyze the application scenarios of Parcelable and the differences between Parcelable and Serializable.
How to use
Define a POJO object that implements Parcelable as follows,
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private int id;
private String name;
//setter & getter & constructor
/ /...
// Here is how to implement the Parcelable interface
// Except for special file descriptor scenarios to serialize, return zero
@Override
public int describeContents(a) {
return 0;
}
/ / the serialization
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
// A custom private constructor to deserialize the corresponding member variable value
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return newBook[size]; }};// Generate an object with the same contents as the previous object based on the deserialized properties
private Book(Parcel in) {
// Remember to deserialize attributes in the same order as before!!id = in.readInt(); name = in.readString(); }}Copy the code
The following code shows how to serialize data from the Book object between activities,
// Pass Book Book = new Book(123, "Hello world"); Intent intent = new Intent(FirstActivity.this, SecondActivity.class); intent.putExtra("book_data", book); startActivity(intent); / / accept Book Book. = (Book) getIntent getParcelableExtra (" book_data);Copy the code
Analysis of the source code
Looking at the source code, Parcelable is just an interface and the implementation is handed to a Parcel object. For example, when we call the methods in the Parcel class at writeToParcel, we can see that the implementation is handed over to Native.
.public final void writeInt(int val) { nativeWriteInt(mNativePtr, val); }...@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); .Copy the code
Since it is a native implementation, you need to look at the Android source code. We open androidXref.com (fQ may be needed, aoSPxref.com is recommendable) and search for nativeWriteInt in Android source code. Orientation to the corresponding c + + implementation frameworks in directory/base/core/jni android_os_Parcel. CPP, interested friends can look at.
In Android source code, many native methods are dynamically registered. Here, we will not repeat how to find the corresponding implementation of C, but let’s go straight down.
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
// Force a Parcel object by pointer reinterpretation
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if(parcel ! =NULL) {
// The writeInt32 function in the Parcel is called after all
const status_t err = parcel->writeInt32(val);
if(err ! = NO_ERROR) { signalExceptionForError(env, clazz, err); }}}...// A generic template method is actually called
status_t Parcel::writeInt32(int32_t val)
{
returnwriteAligned(val); }...// Template method
/ / which
//mData indicates the first address pointing to the Parcel memory
//mDataPos indicates the first address pointing to the Parcel's free memory
//mDataCapacity specifies the size of memory allocated by a Parcel
template<class T>
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
// Determine if adding val will exceed the available size
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
//reinterpret_cast is a c++ reinterpret_cast operation
// mData + mDataPos gets the physical address, which is converted to a pointer to type T (the actual type of the input parameter)
// Then assign val to what the pointer points to
*reinterpret_cast<T*>(mData+mDataPos) = val;
// The main logic is to change the offset address of mDataPos
// Add the offset address to the number of bytes of newly added data
return finishWrite(sizeof(val));
}
// If the available size is exceeded, execute the growth function
// Then goto the restart_write tag above to perform the write logic
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
Copy the code
Using the code above, writeInt32 writes data to a shared memory, so we use a Parcel object to read values from readInt. Do the same analysis, as follows,
template<class T>
status_t Parcel::readAligned(T *pArg) const {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(T)) <= mDataSize) {
// Get the address to read the data
const void* data = mData+mDataPos;
//mDataPos points to the next data
mDataPos += sizeof(T);
// Fetch data according to the data pointer type
*pArg = *reinterpret_cast<const T*>(data);
return NO_ERROR;
} else {
returnNOT_ENOUGH_DATA; }}Copy the code
Write data to a Parcel memory address 12,34; The data is read from the start address (the Android system resets mDataPos to the start value when reading)+ the number of bytes corresponding to the pointer type, first 12, then 34.
This is why it is important to keep the corresponding member variables in the same read/write order when writing serialization methods.
Differences from Serializable
Parcelable
It is very quick to serialize in memory, becauseSerializable
Frequent I/ OS are required.Parcelable
The implementation is complex and the consistency of the read and write order should be paid attention to.Serializable
The implementation is relatively simple.Parcelable
Not suitable for data persistence, but Serializable.