SoFunction
Updated on 2025-03-02

Analysis of the source code layer of call of Android Parcleable interface

Android Parcelable source code analysis

As we all know, there are two ways to pass some non-basic data in an Intent, one is to implement Parcelable and the other is to implement the Serializable interface. Today, let’s not talk about the Serializable interface, but only Parcelable. We know that Parcelable is just an interface with several key methods:

1. writeToParcel

   /** 
     * Flatten this object in to a Parcel. 
     * 
     * @param dest The Parcel in which the object should be written. 
     * @param flags Additional flags about how the object should be written. 
     * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. 
     */ 
    public void writeToParcel(Parcel dest, @WriteFlags int flags); 

This method will allow you to write the data you need to save into Parcel. flags can be written 0 or PARCELABLE_WRITE_RETURN_VALUE. What is the difference between these two? Let’s talk about it later.

In this, you need to call the Parcel object passed to you to pass the data you need into it. Such as:

     public void writeToParcel(Parcel out, int flags) { 
         (mData); 
     } 

At the same time, a Creator needs to be implemented to restore objects. If this Creator is not implemented, an error will be reported during recovery.

  /** 
     * Interface that must be implemented and provided as a public CREATOR 
     * field that generates instances of your Parcelable class from a Parcel. 
     */ 
    public interface Creator<T> { 
        /** 
         * Create a new instance of the Parcelable class, instantiating it 
         * from the given Parcel whose data had previously been written by 
         * {@link Parcelable#writeToParcel ()}. 
         * 
         * @param source The Parcel to read the object's data from. 
         * @return Returns a new instance of the Parcelable class. 
         */ 
        public T createFromParcel(Parcel source); 
        /** 
         * Create a new array of the Parcelable class. 
         * 
         * @param size Size of the array. 
         * @return Returns an array of the Parcelable class, with every entry 
         * initialized to null. 
         */ 
        public T[] newArray(int size); 
    } 

2. createFromParcel(Parcel source)

This method is that when you restore the object, the source will be passed to you for reading.

Official examples:

  public static final  MyParcelable&gt; CREATOR 
             = new &lt;MyParcelable&gt;() { 
         public MyParcelable createFromParcel(Parcel in) { 
             return new MyParcelable(in); 
         } 
         public MyParcelable[] newArray(int size) { 
             return new MyParcelable[size]; 
         } 
     }; 
     private MyParcelable(Parcel in) { 
         mData = (); 
     } 

So why can these methods put an object into an intent? Then can you take it out?

Let's look at the source code:

  /** 
     * Add extended data to the intent.  The name must include a package 
     * prefix, for example the app  would use names 
     * like "". 
     * 
     * @param name The name of the extra data, with package prefix. 
     * @param value The Parcelable data value. 
     * 
     * @return Returns the same Intent object, for chaining multiple calls 
     * into a single statement. 
     * 
     * @see #putExtras 
     * @see #removeExtra 
     * @see #getParcelableExtra(String) 
     */ 
    public @NonNull Intent putExtra(String name, Parcelable value) { 
        if (mExtras == null) { 
            mExtras = new Bundle(); 
        } 
        (name, value); 
        return this; 
    } 

We can see that it is actually put in mExtras.

3. Private Bundle mExtras

It is actually a Bundle, and Bundle actually implements the Parcelable interface

public final class Bundle extends BaseBundle implements Cloneable, Parcelable { 

Let's take a look at the implementation of Bundle putParcelable:

/** 
 * Inserts a Parcelable value into the mapping of this Bundle, replacing 
 * any existing value for the given key.  Either key or value may be null. 
 * 
 * @param key a String, or null 
 * @param value a Parcelable object, or null 
 */ 
public void putParcelable(@Nullable String key, @Nullable Parcelable value) { 
    unparcel(); 
    (key, value); 
    mFlags &= ~FLAG_HAS_FDS_KNOWN; 
} 

Enter unparcel();

/** 
 * If the underlying data are stored as a Parcel, unparcel them 
 * using the currently assigned class loader. 
 */ 
/* package */ void unparcel() { 
    synchronized (this) { 
        final Parcel source = mParcelledData; 
        if (source != null) { 
            initializeFromParcelLocked(source, /*recycleParcel=*/ true); 
        } else { 
            if (DEBUG) { 
                (TAG, "unparcel " 
                        + ((this)) 
                        + ": no parcelled data"); 
            } 
        } 
    } 
} 

Under normal circumstances, mParcelledData is null. We can see that in fact, it is just a simple put in.

OK, when passing data, the Bundle needs to be passed on, and it will definitely call writeToParcel.

  /** 
     * Writes the Bundle contents to a Parcel, typically in order for 
     * it to be passed through an IBinder connection. 
     * @param parcel The parcel to copy this bundle to. 
     */ 
    @Override 
    public void writeToParcel(Parcel parcel, int flags) { 
        final boolean oldAllowFds = ((mFlags & FLAG_ALLOW_FDS) != 0); 
        try { 
            (parcel, flags); 
        } finally { 
            (oldAllowFds); 
        } 
    } 

Called (parcel, flags);

Let's take a look at the writeToParcelInner(parcel, flags); of BaseBundle:

/** 
 * Writes the Bundle contents to a Parcel, typically in order for 
 * it to be passed through an IBinder connection. 
 * @param parcel The parcel to copy this bundle to. 
 */ 
void writeToParcelInner(Parcel parcel, int flags) { 
    // If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first. 
    if (()) { 
        unparcel(); 
    } 
    // Keep implementation in sync with writeToParcel() in 
    // frameworks/native/libs/binder/. 
    final ArrayMap<String, Object> map; 
    synchronized (this) { 
        // unparcel() can race with this method and cause the parcel to recycle 
        // at the wrong time. So synchronize access the mParcelledData's content. 
        if (mParcelledData != null) { 
            if (mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL) { 
                (0); 
            } else { 
                int length = (); 
                (length); 
                (BUNDLE_MAGIC); 
                (mParcelledData, 0, length); 
            } 
            return; 
        } 
        map = mMap; 
    } 
    // Special case for empty bundles. 
    if (map == null || () <= 0) { 
        (0); 
        return; 
    } 
    int lengthPos = (); 
    (-1); // dummy, will hold length 
    (BUNDLE_MAGIC); 
    int startPos = (); 
    (map); 
    int endPos = (); 
    // Backpatch length 
    (lengthPos); 
    int length = endPos - startPos; 
    (length); 
    (endPos); 
} 

There are a lot of writing in it, the key is (map); This sentence writes map into parcel.

Let's take a look at Parcel's writeArrayMapInternal method:

  /** 
     * Flatten an ArrayMap into the parcel at the current dataPosition(), 
     * growing dataCapacity() if needed.  The Map keys must be String objects. 
     */ 
    /* package */ void writeArrayMapInternal(ArrayMap<String, Object> val) { 
        if (val == null) { 
            writeInt(-1); 
            return; 
        } 
        // Keep the format of this Parcel in sync with writeToParcelInner() in 
        // frameworks/native/libs/binder/. 
        final int N = (); 
        writeInt(N); 
        if (DEBUG_ARRAY_MAP) { 
            RuntimeException here =  new RuntimeException("here"); 
            (); 
            (TAG, "Writing " + N + " ArrayMap entries", here); 
        } 
        int startPos; 
        for (int i=0; i<N; i++) { 
            if (DEBUG_ARRAY_MAP) startPos = dataPosition(); 
            writeString((i)); 
            writeValue((i)); 
            if (DEBUG_ARRAY_MAP) (TAG, "  Write #" + i + " " 
                    + (dataPosition()-startPos) + " bytes: key=0x" 
                    + ((i) != null ? (i).hashCode() : 0) 
                    + " " + (i)); 
        } 
    } 

First write the length, then write k and value. Let's take a look at the writeValue method here

   public final void writeValue(Object v) { 
        if (v == null) { 
            writeInt(VAL_NULL); 
        } else if (v instanceof String) { 
            writeInt(VAL_STRING); 
            writeString((String) v); 
        } else if (v instanceof Integer) { 
            writeInt(VAL_INTEGER); 
            writeInt((Integer) v); 
        } else if (v instanceof Map) { 
            writeInt(VAL_MAP); 
            writeMap((Map) v); 
        } else if (v instanceof Bundle) { 
            // Must be before Parcelable 
            writeInt(VAL_BUNDLE); 
            writeBundle((Bundle) v); 
        } else if (v instanceof PersistableBundle) { 
            writeInt(VAL_PERSISTABLEBUNDLE); 
            writePersistableBundle((PersistableBundle) v); 
        } else if (v instanceof Parcelable) { 
            // IMPOTANT: cases for classes that implement Parcelable must 
            // come before the Parcelable case, so that their specific VAL_* 
            // types will be written. 
            writeInt(VAL_PARCELABLE); 
            writeParcelable((Parcelable) v, 0); 
        } else if (v instanceof Short) { 
            writeInt(VAL_SHORT); 
            writeInt(((Short) v).intValue()); 
        } 
.... 
    } 

If you find that you are writing Parcelable, write Parcelable

public final void writeParcelable(Parcelable p, int parcelableFlags) { 
    if (p == null) { 
        writeString(null); 
        return; 
    } 
    writeParcelableCreator(p); 
    (this, parcelableFlags); 
} 
public final void writeParcelableCreator(Parcelable p) { 
    String name = ().getName(); 
    writeString(name); 
} 

Here we will first write the class name of the Parcelable object, and then call the writeToParcel of the Parcelable object. That is, the method we implement will write the data we want to pass into Parcel.

OK, so the writeToParcel method of the Parcelable interface is called.

Let's take a look at Parcel's readFromParcel

    /** 
     * Reads the Parcel contents into this Bundle, typically in order for 
     * it to be passed through an IBinder connection. 
     * @param parcel The parcel to overwrite this bundle from. 
     */ 
    public void readFromParcel(Parcel parcel) { 
        (parcel); 
        mFlags = FLAG_ALLOW_FDS; 
        maybePrefillHasFds(); 
    } 
(parcel); 
    private void readFromParcelInner(Parcel parcel, int length) { 
        if (length < 0) { 
            throw new RuntimeException("Bad length in parcel: " + length); 
        } else if (length == 0) { 
            // Empty Bundle or end of data. 
            mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL; 
            return; 
        } 
        final int magic = (); 
        if (magic != BUNDLE_MAGIC) { 
            throw new IllegalStateException("Bad magic number for Bundle: 0x" 
                    + (magic)); 
        } 
        if (()) { 
            // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just 
            // unparcel right away. 
            synchronized (this) { 
                initializeFromParcelLocked(parcel, /*recycleParcel=*/ false); 
            } 
            return; 
        } 
        // Advance within this Parcel 
        int offset = (); 
        ((offset, length)); 
        Parcel p = (); 
        (0); 
        (parcel, offset, length); 
        (parcel); 
        if (DEBUG) (TAG, "Retrieving "  + ((this)) 
                + ": " + length + " bundle bytes starting at " + offset); 
        (0); 
        mParcelledData = p; 
    } 

It's very simple, assign the current mParcelledData value.

When we call getParcelable, we will first unparcel();

    public <T extends Parcelable> T getParcelable(@Nullable String key) { 
        unparcel(); 
        Object o = (key); 
        if (o == null) { 
            return null; 
        } 
        try { 
            return (T) o; 
        } catch (ClassCastException e) { 
            typeWarning(key, o, "Parcelable", e); 
            return null; 
        } 
    } 
    /* package */ void unparcel() { 
        synchronized (this) { 
            final Parcel source = mParcelledData; 
            if (source != null) { 
                initializeFromParcelLocked(source, /*recycleParcel=*/ true); 
            } else { 
                if (DEBUG) { 
                    (TAG, "unparcel " 
                            + ((this)) 
                            + ": no parcelled data"); 
                } 
            } 
        } 
    } 
private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel) { 
        if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) { 
            (TAG, "Attempting to unparcel a Bundle while in transit; this may " 
                    + "clobber all data inside!", new Throwable()); 
        } 
        if (isEmptyParcel(parcelledData)) { 
            if (DEBUG) { 
                (TAG, "unparcel " 
                        + ((this)) + ": empty"); 
            } 
            if (mMap == null) { 
                mMap = new ArrayMap<>(1); 
            } else { 
                (); 
            } 
            mParcelledData = null; 
            return; 
        } 
        final int count = (); 
        if (DEBUG) { 
            (TAG, "unparcel " + ((this)) 
                    + ": reading " + count + " maps"); 
        } 
        if (count < 0) { 
            return; 
        } 
        ArrayMap<String, Object> map = mMap; 
        if (map == null) { 
            map = new ArrayMap<>(count); 
        } else { 
            (); 
            (count); 
        } 
        try { 
            (map, count, mClassLoader); 
        } catch (BadParcelableException e) { 
            if (sShouldDefuse) { 
                (TAG, "Failed to parse Bundle, but defusing quietly", e); 
                (); 
            } else { 
                throw e; 
            } 
        } finally { 
            mMap = map; 
            if (recycleParcel) { 
                recycleParcel(parcelledData); 
            } 
            mParcelledData = null; 
        } 
        if (DEBUG) { 
            (TAG, "unparcel " + ((this)) 
                    + " final map: " + mMap); 
        } 
    }

4. (map, count, mClassLoader)

Finally, the readArrayMapInternal of the Parcel class was called

/* package */ void readArrayMapInternal(ArrayMap outVal, int N, 
    ClassLoader loader) { 
    if (DEBUG_ARRAY_MAP) { 
        RuntimeException here =  new RuntimeException("here"); 
        (); 
        (TAG, "Reading " + N + " ArrayMap entries", here); 
    } 
    int startPos; 
    while (N > 0) { 
        if (DEBUG_ARRAY_MAP) startPos = dataPosition(); 
        String key = readString(); 
        Object value = readValue(loader); 
        if (DEBUG_ARRAY_MAP) (TAG, "  Read #" + (N-1) + " " 
                + (dataPosition()-startPos) + " bytes: key=0x" 
                + ((key != null ? () : 0)) + " " + key); 
        (key, value); 
        N--; 
    } 
    (); 
} 

Called readValue

public final Object readValue(ClassLoader loader) { 
        int type = readInt(); 
        switch (type) { 
        case VAL_NULL: 
            return null; 
        case VAL_STRING: 
            return readString(); 
        case VAL_INTEGER: 
            return readInt(); 
        case VAL_MAP: 
            return readHashMap(loader); 
        case VAL_PARCELABLE: 
            return readParcelable(loader); 
    ....... 
    } 

5. readParcelable(loader)

    public final <T extends Parcelable> T readParcelable(ClassLoader loader) { 
        <?> creator = readParcelableCreator(loader); 
        if (creator == null) { 
            return null; 
        } 
        if (creator instanceof <?>) { 
          <?> classLoaderCreator = 
              (<?>) creator; 
          return (T) (this, loader); 
        } 
        return (T) (this); 
    } 
public final <?> readParcelableCreator(ClassLoader loader) { 
        String name = readString(); 
        if (name == null) { 
            return null; 
        } 
        <?> creator; 
        synchronized (mCreators) { 
            HashMap<String,<?>> map = (loader); 
            if (map == null) { 
                map = new HashMap<>(); 
                (loader, map); 
            } 
            creator = (name); 
            if (creator == null) { 
                try { 
                    // If loader == null, explicitly emulate (String) "caller 
                    // classloader" behavior. 
                    ClassLoader parcelableClassLoader = 
                            (loader == null ? getClass().getClassLoader() : loader); 
                    // Avoid initializing the Parcelable class until we know it implements 
                    // Parcelable and has the necessary CREATOR field. http://b/1171613. 
                    Class<?> parcelableClass = (name, false /* initialize */, 
                            parcelableClassLoader); 
                    if (!(parcelableClass)) { 
                        throw new BadParcelableException("Parcelable protocol requires that the " 
                                + "class implements Parcelable"); 
                    } 
                    Field f = ("CREATOR"); 
                    if ((() & ) == 0) { 
                        throw new BadParcelableException("Parcelable protocol requires " 
                                + "the CREATOR object to be static on class " + name); 
                    } 
                    Class<?> creatorType = (); 
                    if (!(creatorType)) { 
                        // Fail before calling (), not after, to avoid initializing 
                        // parcelableClass unnecessarily. 
                        throw new BadParcelableException("Parcelable protocol requires a " 
                                + " object called " 
                                + "CREATOR on class " + name); 
                    } 
                    creator = (<?>) (null); 
                } 
                catch (IllegalAccessException e) { 
                    (TAG, "Illegal access when unmarshalling: " + name, e); 
                    throw new BadParcelableException( 
                            "IllegalAccessException when unmarshalling: " + name); 
                } 
                catch (ClassNotFoundException e) { 
                    (TAG, "Class not found when unmarshalling: " + name, e); 
                    throw new BadParcelableException( 
                            "ClassNotFoundException when unmarshalling: " + name); 
                } 
                catch (NoSuchFieldException e) { 
                    throw new BadParcelableException("Parcelable protocol requires a " 
                            + " object called " 
                            + "CREATOR on class " + name); 
                } 
                if (creator == null) { 
                    throw new BadParcelableException("Parcelable protocol requires a " 
                            + "non-null  object called " 
                            + "CREATOR on class " + name); 
                } 
                (name, creator); 
            } 
        } 
        return creator; 
    } 

Your Parcel class will be loaded inside, and if you find that there is no creator, an exception will be thrown. Wait, and finally called the createFromParcel of your class.

This is the article about the source code layer analysis of the Android Parcleable interface. For more information about the Android Parcleable interface, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!