Background of serialization

At the bottom of the system, data transmission is in the form of simple byte sequence transmission, that is, at the bottom, the system does not know objects but only byte sequence. In order to achieve the purpose of process communication, data serialization is needed first, and serialization is the process of converting objects into byte sequence. Conversely, when the byte sequence is shipped to the corresponding process, the process, in order to recognize the data, deserializes it, that is, converting the byte sequence into an object

serialization

The process of converting data structures or objects into binary strings

deserialization

The process of converting serialized binary strings into data structures or objects.

Purpose of serialization/deserialization

Serialization: Mainly used for network transmission, data persistence, commonly known as Encode deserialization: mainly used for reading byte arrays from the network, disk to restore the original object, commonly known as Decode deserialization

Objective:

  • Store object data permanently
  • Object data is transferred over the network through serialization operations
  • Object data is passed between processes
  • Java platform allows us to create reusable Java objects in memory, but in general, only when the JVM at runtime, these objects can only exist, but in real application, there may be a JVM stop running after still keep the specified object, and read again in the future are stored objects, so that you can use the Java object serialization
  • When serializing objects, you serialize only variables, not methods
  • Data is passed between intEnts, and serialization is required if the data type being passed is complex.

Serialization and deserialization commonly used in Android

  • Serializable Interface (Java)

    Identifies that the current class can be serialized by ObjectOutputStream and deserialized by ObjectInputStream

  • Parcelable Interface (Android SDK)

    Data is written and recovered via Parcel and must have a non-empty static variable CREATOR

Serializable

The characteristics of the Serializable
  • In a Serializable class, a property state that does not implement Serializable cannot be serialized/deserialized
  • That is, when a class is deserialized, its non-serializable attributes are recreated by calling the no-argument constructor
  • Therefore, the no-argument constructor for this property must be accessible, otherwise an error will be reported at runtime
  • A class that implements serialization and whose subclasses are also serializable
serialVersionUID

Used to indicate compatibility between different versions of a class. If you modify this class, modify this value. Otherwise, a class previously serialized with an older version of the class will return an error: InvalidClassException

Serialization step
  • Outputs the class metadata associated with the object instance.
  • Recursively outputs the superclass description of the class until there are no more superclasses.
  • Once the class metadata is finished, it begins to output the actual data values of the object instance from the topmost superclass.
  • Output the instance’s data recursively from top to bottom
Binary opens serialized files

  • AC ED: STREAM_MAGIC. Declares that the serialization protocol is used.
  • 00 05: STREAM_VERSION. Serialization protocol version.
  • 0x73: TC_OBJECT. Declare that this is a new object.
  • 0x72: TC_CLASSDESC. Declare this to start a new Class.
  • 00 2E: Class Indicates the length of the name.
Serialization (writeObject) source analysis

– > > > into Java. IO. ObjectOutputStream classes

1. The ObjectOutputStream constructor sets enableOverride = false

public ObjectOutputStream(OutputStream out) throws IOException { ... this.enableOverride = false; . }Copy the code

2. Enter the writeObject0 method during serialization

public final void writeObject(Object obj) throws IOException { if (this.enableOverride) { this.writeObjectOverride(obj);  } else { try { this.writeObject0(obj, false); } catch (IOException var3) { if (this.depth == 0) { this.writeFatalException(var3); } throw var3; }}}Copy the code

3. WriteObject0 is typed into the writeOrdinaryObject method

Private void writeObject0(Object obj, Boolean unshared) throws IOException {... if (obj instanceof String) { this.writeString((String)obj, unshared); return; } else { if (cl.isArray()) { this.writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { this.writeEnum((Enum)obj, desc, unshared); } else { if (! (obj instanceof Serializable)) { if (extendedDebugInfo) { throw new NotSerializableException(cl.getName() + "\n" + this.debugInfoStack.toString()); } throw new NotSerializableException(cl.getName()); } this.writeOrdinaryObject(obj, desc, unshared); } break; }... }Copy the code

4. WriteOrdinaryObject mainly performs logic

Private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, Boolean Unshared) throws IOException {... if (desc.isExternalizable() && ! Desc.isproxy ()) {// If the object implements the Externalizable interface, //writeExternalData((Externalizable) obj) this.writeExternalData((Externalizable)obj); } else { this.writeSerialData(obj, desc); }... }Copy the code

5. We go to the writeSerialData method

Private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException { ClassDataSlot[] slots = desc.getClassDataLayout(); for(int i = 0; i < slots.length; ++i) { ObjectStreamClass slotDesc = slots[i].desc; // Determine if there is a writeObject method in the target class, If any call in the target class if (slotDesc. HasWriteObjectMethod ()) {ObjectOutputStream. PutFieldImpl oldPut = this. CurPut; this.curPut = null; SerialCallbackContext oldContext = this.curContext; if (extendedDebugInfo) { this.debugInfoStack.push("custom writeObject data (class "" + slotDesc.getName() + "")"); } try { this.curContext = new SerialCallbackContext(obj, slotDesc); this.bout.setBlockDataMode(true); slotDesc.invokeWriteObject(obj, this); this.bout.setBlockDataMode(false); this.bout.writeByte(120); } finally { this.curContext.setUsed(); this.curContext = oldContext; if (extendedDebugInfo) { this.debugInfoStack.pop(); } } this.curPut = oldPut; } else {// Call the default this.defaultWriteFields(obj, slotDesc); }}}Copy the code

The ObjectOutputStream class will look for the private writeObject (readObject) method in the target class and assign it to the variable writeObjectMethod (readObjectMethod).

private ObjectStreamClass(final Class<? > cl) {... if (this.serializable) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Void run() { if (ObjectStreamClass.this.isEnum) { ObjectStreamClass.this.suid = 0L; ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS; return null; } else if (cl.isArray()) { ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS; return null; } else { ObjectStreamClass.this.suid = ObjectStreamClass.getDeclaredSUID(cl); try { ObjectStreamClass.this.fields = ObjectStreamClass.getSerialFields(cl); ObjectStreamClass.this.computeFieldOffsets(); } catch (InvalidClassException var2) { ObjectStreamClass.this.serializeEx = ObjectStreamClass.this.deserializeEx = new ObjectStreamClass.ExceptionInfo(var2.classname, var2.getMessage()); ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS; } if (ObjectStreamClass.this.externalizable) { ObjectStreamClass.this.cons = ObjectStreamClass.getExternalizableConstructor(cl); } else { ObjectStreamClass.this.cons = ObjectStreamClass.getSerializableConstructor(cl); ObjectStreamClass.this.writeObjectMethod = ObjectStreamClass.getPrivateMethod(cl, "writeObject", new Class[]{ObjectOutputStream.class}, Void.TYPE); ObjectStreamClass.this.readObjectMethod = ObjectStreamClass.getPrivateMethod(cl, "readObject", new Class[]{ObjectInputStream.class}, Void.TYPE); ObjectStreamClass.this.readObjectNoDataMethod = ObjectStreamClass.getPrivateMethod(cl, "readObjectNoData", (Class[])null, Void.TYPE); ObjectStreamClass.this.hasWriteObjectData = ObjectStreamClass.this.writeObjectMethod ! = null; } ObjectStreamClass.this.domains = ObjectStreamClass.this.getProtectionDomains(ObjectStreamClass.this.cons, cl); ObjectStreamClass.this.writeReplaceMethod = ObjectStreamClass.getInheritableMethod(cl, "writeReplace", (Class[])null, Object.class); ObjectStreamClass.this.readResolveMethod = ObjectStreamClass.getInheritableMethod(cl, "readResolve", (Class[])null, Object.class); return null; }}}); } else { this.suid = 0L; this.fields = NO_FIELDS; }... }Copy the code

Serializable Precautions
  • Static static variables and transient modified fields are not serialized
  • SerialVersionUID problem
  • If the member variable of a serialized class is an object type, the class of that object type must implement serialization
  • The subclass realizes serialization, the parent class does not realize serialization, and the field loss problem in the parent class
  • Serialization of a singleton pattern requires the readResolve method to be overridden, otherwise it will cause the singleton pattern to fail after serialization -> deserialization

Parcelable

The implementation process of Parcelable is mainly divided into serialization and deserialization. The three processes are described below

What is the Parcel

Before we get to Parcelable we need to know what a Parcel is.

A Parcel simply wraps the data we need to transfer and delivers it in a Binder, which is used to transfer data across processes

Simply put, a Parcel provides a mechanism for writing serialized data to a shared memory from which other processes can read byte streams and deserialize them into objects

A Parcel can contain raw data types (written in various corresponding methods, such as writeInt(),writeFloat(), etc.), a Parcelable object, and a reference to an active IBinder object, This reference causes the other end to receive a proxy IBinder that points to the IBinder.

Parcelable implements read and write methods through Parcel to serialize and deserialize

Common methods
Public class Sync implements Parcelable {protected Sync(Parcel in) {} public static final Creator<Sync> Creator  = new Creator<Sync>() { @Override public Sync createFromParcel(Parcel in) { return new Sync(in); } @Override public Sync[] newArray(int size) { return new Sync[size]; }}; Description @override public int describeContents() {return 0; Public void writeToParcel(Parcel dest, int flags) {}Copy the code

DescribeContents: Responsible for file description. Only for some special objects that need to be described, return 1. Otherwise, return 0

WriteToParcel: A Parcel is returned, so we can call the write method in a Parcel. The basic write methods are available, except for objects and collections. Boolean can be stored using either int or byte

CREATOR: The interface for CREATOR in Parcelable is implemented through an anonymous inner class

Frequently seen exam

Difference and comparison between Parcelable and Serializable

Both Parcelable and Serializable can be serialized and can be used to transfer data between intEnts. Serializable is a Java implementation, which may require frequent I/O operations, so the consumption is high, but the implementation is simple Parcelable is the method provided by Android, which is relatively efficient, but complicated to implement. The selection rules of the two are: Choose Parcelable for memory serialization, and Serializable for storage to devices or network transmission

Why do you design bundles in Android instead of using maps directly

The Bundle is implemented internally by an ArrayMap, which consists of two arrays: an int array that stores the subscripts of object data, and an object array that stores keys and values. Internal dichotomies are used to sort keys, so when adding, deleting, or looking up data, Both use binary search, only suitable for small amount of data operations, if the data is relatively large, then its performance will degrade. The internal structure of HashMap is Array + linked list. Therefore, when the amount of data is small, the Entry Array of HashMap takes up more memory than that of ArrayMap. Since most of the scenarios using bundles are small data volumes, I’ve never seen more than 10 data transfers between two activities, so using ArrayMap to store data in this case has an advantage in terms of speed and memory footprint, so using the Bundle to transfer data. Faster speeds and less memory footprint are guaranteed.

Another reason is that in Android, if an Intent is used to carry data, the data needs to be of either a basic or Serializable type. HashMap Serializable, whereas Bundle Serializable is Parcelable. On the Android platform, it is more recommended to use Parcelable to realize serialization. Although the writing method is complicated, the cost is lower. Therefore, in order to carry out data serialization and deserialization more quickly, the system encapsulates the Bundle class to facilitate data transmission.

The communication principle and size limitation of Intents/bundles in Android

Bundles in intEnts use binders to deliver data. The size of the Binder buffer is limited (some phones have 2 MB), and a process has 16 Binder threads by default, so the size of the buffer is much smaller (some people have tested this before, and it is around 128 KB for a thread).

Why can’t IntEnts pass objects directly between components rather than through serialization?

Intent when start the other components, will leave the current application process, enter the ActivityManagerService process (Intent. PrepareToLeaveProcess ()), and what this means is that Intent carries data can be transmitted between different processes. First of all, we know that Android is based on Linux and Java objects cannot be transferred between different processes, so we need to serialize objects here to transfer them between application processes and ActivityManagerService processes.

Either Parcel or Serializable can serialize objects. Serializable is easy to use but not as good as Parcel, which is an Android interface for things like interprocess communication