Android. app. Fragment $ InstantiationException Cause Analysis
1. Fragment $ InstantiationException Cause Analysis
When writing Fragment class code, Android Lint sometimes prompts the following error:
Avoid not-default constructors in fragments: use a default constructor plus Fragment $ setArguments (Bundle) instead
From the Fragment documentation:
Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. it is stronugly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments (Bundle) and later retrieved by the Fragment with getArguments ().
Each Fragment must have a construction method without parameters, so that the Fragment can be instantiated only when the Activity recovers. It is strongly recommended that the child classes of Fragment do not have other constructor methods with parameters, because these constructor methods will not be called when Fragment is re-instantiated. Instead, set the parameters through setArguments (Bundle) and get the parameters through getArguments.
If the Fragment does not have a construction method without parameters, crash occurs when the app resumes the Activity (such as rotating the device.
Q: Why is there a no-argument constructor required, and the constructor containing parameters is not called when Fragment is re-instantiated?
Next, we will analyze the process of Activity restoration.
1. The onCreate (Bundle savedInstanceState) method of the Activity
package android.app;public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback { final FragmentManagerImpl mFragments = new FragmentManagerImpl(); protected void onCreate(@Nullable Bundle savedInstanceState) { ... if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.fragments : null); } mFragments.dispatchCreate(); ... } ...}
When the savedInstanceState is not null, The restoreAllState (Parcelable state, ArrayList NonConfig) method to restore Fragment.
2. FragmentManagerImpl restoreAllState (Parcelable state, ArrayList NonConfig) Method
This method calls the instantiate (Activity activity, Fragment parent) method of the FragmentState.
package android.app;final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 { ... void restoreAllState(Parcelable state, ArrayList
nonConfig) { // If there is no saved state at all, then there can not be // any nonConfig fragments either, so that is that. if (state == null) return; FragmentManagerState fms = (FragmentManagerState)state; if (fms.mActive == null) return; ... for (int i=0; i
(); } if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i); mAvailIndices.add(i); } } ... }}
3. instantiate (Activity activity, Fragment parent) method of FragmentState
The static factory method instantiate (Context context, String fname, @ Nullable Bundle args) of Fragment will be called ).
package android.app;final class FragmentState implements Parcelable { ... public Fragment instantiate(Activity activity, Fragment parent) { if (mInstance != null) { return mInstance; } if (mArguments != null) { mArguments.setClassLoader(activity.getClassLoader()); } mInstance = Fragment.instantiate(activity, mClassName, mArguments); if (mSavedFragmentState != null) { mSavedFragmentState.setClassLoader(activity.getClassLoader()); mInstance.mSavedFragmentState = mSavedFragmentState; } mInstance.setIndex(mIndex, parent); mInstance.mFromLayout = mFromLayout; mInstance.mRestored = true; mInstance.mFragmentId = mFragmentId; mInstance.mContainerId = mContainerId; mInstance.mTag = mTag; mInstance.mRetainInstance = mRetainInstance; mInstance.mDetached = mDetached; mInstance.mFragmentManager = activity.mFragments; if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, "Instantiated fragment " + mInstance); return mInstance; }}
4. Fragment static factory method instantiate (Context context, String fname, @ Nullable Bundle args)
The re-instantiation of Fragment uses the Java reflection mechanism and calls the non-argument constructor of Fragment. Therefore, this step will not call other constructor methods with parameters. Clazz. newInstance () throws InstantiationExecption, and then prints a common Exception, "Unable to instantiate fragment" + fname + ": make sure class name exists, is public, and has an "+" empty constructor that is public ".
package android.app;public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener { private static final ArrayMap
> sClassMap = new ArrayMap
>(); public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) { try { Class
clazz = sClassMap.get(fname); if (clazz == null) { // Class not found in the cache, see if it's real, and try to add it clazz = context.getClassLoader().loadClass(fname); if (!Fragment.class.isAssignableFrom(clazz)) { throw new InstantiationException("Trying to instantiate a class " + fname + " that is not a Fragment", new ClassCastException()); } sClassMap.put(fname, clazz); } Fragment f = (Fragment)clazz.newInstance(); if (args != null) { args.setClassLoader(f.getClass().getClassLoader()); f.mArguments = args; } return f; } catch (ClassNotFoundException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } catch (java.lang.InstantiationException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } catch (IllegalAccessException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } }}
Q: In practice, there is another situation where the Fragment has no constructor and an InstantiationException is still thrown. Why?
When your Fragment is used as a non-static internal class, this Fragment cannot be instantiated using the Java reflection mechanism. In my actual project, a DialogFragment is defined as an internal class of Activity, resulting in this Exception. Because the instantiation of non-static internal class objects requires the first instantiation of external class objects, only the instantiation of non-static internal classes will inevitably throw the InstantiationException.