In my last post, where I described a problem of incorrect usage of Fragments instantiation inside FragmentPageAdapter
& ViewPager
, I wrote:
After orientation change all fragments currently added to FragmentManager
are automatically restored and instantiated so there is no need to create them once again.
Today, I want to focus more specifically on an issue how this automatic restoration works under the hood.
Beginning
The simplest way to use a fragment:
1 2 3 |
Fragment newFragment = new OurSuperFragment(); FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); ft.add(android.R.id.content, newFragment).commit(); |
A custom fragment instance is created and added to FragmentManager
with help of FragmentTransaction
.
We could add that our Fragment will be added to container identified by android.R.id.content
.
This is the easiest case. I want to point out that we are not using setRetainInstance(true)
inside our custom fragment implementation, and our activity is not protected in manifest agains any type of configuration changes.
The Question
Now, what will happen with our fragment if suddenly our device configuration will change, i.e. orientation?
Android Source Code (especially FragmentActivity
and FragmentManager
/FragmentManagerImpl
) is a place where we should look for the answer.
1. Saving the state
Before Activity will be destroyed itsonSaveInstanceState
method will be called. Take a look what this method is doing internally.
1 2 3 4 5 6 7 8 |
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Parcelable p = mFragments.saveAllState(); if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); } } |
It takes mFragments (reference to FragmentManager
held in this Activity) and callsFragmentManager.saveAllState()
method which will return a parcelable object ready to be saved inside the bundle which, you can already guess… later will be used to restore Fragments.
In reality result of saveAllState
method call is an object of typeFragmentManagerState
which consists of information about all active fragments and back stack entries
1 2 3 4 5 |
class FragmentManagerState implements Parcelable { FragmentState[] mActive; int[] mAdded; BackStackState[] mBackStack; ... |
Last quick look at FragmentState
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
final class FragmentState implements Parcelable { final String mClassName; final int mIndex; final boolean mFromLayout; final int mFragmentId; final int mContainerId; final String mTag; final boolean mRetainInstance; final boolean mDetached; final Bundle mArguments; Bundle mSavedFragmentState; Fragment mInstance; ... |
It consists of all data which describes a specific fragment object instance, like container id, tag, arguments but also savedFragmentState.
It looks sufficient to create fragments from scratch, and set them like they were before.
2. Destroying fragments and activities.
After state of fragments is saved via FragmentManager
activity object is destroyed (removed from memory) with all its fragments (those which are not retained with setRetainInstance(true)
).
3. Creating activity and recreating fragments
Final point is a recreation of the activity and recreation of the fragments. It starts within the first Activity lifecycle callback method onCreate(Bundle savedInstanceState)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Override protected void onCreate(Bundle savedInstanceState) { mFragments.attachActivity(this, mContainer, null); // Old versions of the platform didn't do this! if (getLayoutInflater().getFactory() == null) { getLayoutInflater().setFactory(this); } super.onCreate(savedInstanceState); NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { mAllLoaderManagers = nc.loaders; } if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, nc != null ? nc.fragments : null); } mFragments.dispatchCreate(); } |
The previously saved fragment manager state bundle now is used inside FragmentManager.restoreAllState
method. This metod declaration is quite long but I want to focus on the most important part.
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 |
void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) { ... if (state == null) return; FragmentManagerState fms = (FragmentManagerState)state; ... mActive = new ArrayList<Fragment>(fms.mActive.length); if (mAvailIndices != null) { mAvailIndices.clear(); } for (int i=0; i<fms.mActive.length; i++) { FragmentState fs = fms.mActive[i]; if (fs != null) { Fragment f = fs.instantiate(mActivity, mParent); if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f); mActive.add(f); // Now that the fragment is instantiated (or came from being // retained above), clear mInstance in case we end up re-restoring // from this FragmentState again. fs.mInstance = null; } else { mActive.add(null); if (mAvailIndices == null) { mAvailIndices = new ArrayList<Integer>(); } if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i); mAvailIndices.add(i); } } ... } |
Array with all FragmentState
objects is iterated. And every FragmentState
object is used to recreate (create new instance with state like before) specific Fragments.
A new instance of a fragment is created by platform with usage of reflection and default constructor – that’s why you must remember to always ensure the existence of public non-argument fragment constructor and initialize your fragment through arguments bundle (not with usage of fragment object setters from strange places).
This is short story how restoration of Fragments works.
Conclusion
Points to remember:
- Already created fragments are restored automatically after orientation change
- Magic behind this is just code written in Activity together with FragmentManager logic/implementation
- Avoid setter methods and parametrized constructors to modify fragment state, because platform uses only default (0 parameter) constructor, arguments bundle and saved state bundle to restore it later
- Fragments are not so bad