Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Parcelable work even though I did not implement the necessary functions?

I wanted to retain a complex java object during screen rotation, so I made the object Parcelable and implemented necessary methods:

  1. in writeToParcel(Parcel dest, int flags) method, I some saved values to "dest".
  2. in Parcelable.Creator's createFromParcel(Parcel source) method, I obtained the values from "source" in correct order and returned the appropriate object.

Then in Fragment's onSaveInstanceState I saved the Parcelable :

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable("myObject", myObject);
}

and got my object in Fragment's onCreate :

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  MyObject myObject = savedInstanceState.getParcelable("myObject");
}

This worked Perfectly.

Then I made the following test:

  1. Deleted all my code in writeToParcel method.
  2. Returned null in createFramParcel method.

When I ran the app I got THE EXACT SAME RESULT! I got an object with all the appropriate values in it.

Why did this work? Does Parcelable create Parcelable objects "automatically"?

like image 845
Nika Kurdadze Avatar asked Jan 15 '16 17:01

Nika Kurdadze


1 Answers

Yeah, so this has to do with the way Bundle handles caching and parceling internally. When you call putParcelable(), it runs the following code:

public void putParcelable(@Nullable String key, @Nullable Parcelable value) {
    unparcel();
    mMap.put(key, value);
    mFdsKnown = false;
}

So basically, data in a Bundle is not immediately written to a Parcel -- mMap is an ArrayMap<String, Object> and it contains a cache of all the objects in the Bundle as they're inserted or removed.

At some point, writeToParcel() will be called on the Bundle, at which point everything in mMap gets written into mParcelledData.

So basically, when you do a config change, the Bundle still hasn't been written to a Parcel, so the same instance of the object you passed in is still stored in the Bundle's mMap (so your Object also has never had writeToParcel() invoked -- you can confirm this by asserting that the object before and after config change have the same System.identityHashCode()).

You can see notes about this in BaseBundle:

// Invariant - exactly one of mMap / mParcelledData will be null
// (except inside a call to unparcel)

ArrayMap<String, Object> mMap = null;

/*
 * If mParcelledData is non-null, then mMap will be null and the
 * data are stored as a Parcel containing a Bundle.  When the data
 * are unparcelled, mParcelledData willbe set to null.
 */
Parcel mParcelledData = null;

So if you were to write your Parcelable object to the save state bundle, and put your app in the background until the process dies (or I believe you can force that by running adb shell am kill <application_id>) and then resume, you'll then run into the problem where your data isn't parceled correctly.

like image 112
Kevin Coppock Avatar answered Oct 30 '22 01:10

Kevin Coppock