1. Fragment$InstantiationException的原因分析
在编写Fragment类的代码时候,Android Lint有时会提示如下error:
如果的Fragment没有无参构造方法,app在恢复Activity时(例如旋转设备),会出现crash。
Q: 为什么必须要有一个无参构造方法,而且含参构造方法在Fragment重新实例化时不会调用?
接下来分析一下Activity恢复状态的过程。
1. Activity的onCreate(Bundle savedInstanceState)的方法
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(); ... } ... }当savedInstanceState不为null的时候,会调用FragmentManagerImpl的restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig)方法恢复Fragment。
2. FragmentManagerImpl的restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig)方法
该方法会调用FragmentState的instantiate(Activity activity, Fragment parent)方法。
package android.app; final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 { ... void restoreAllState(Parcelable state, ArrayList<Fragment> 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<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); } } ... } }3. FragmentState的instantiate(Activity activity, Fragment parent)方法
最终会调用Fragment的静态工厂方法instantiate(Context context, String fname, @Nullable Bundle args)。
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的静态工厂方法instantiate(Context context, String fname, @Nullable Bundle args)
Fragment的重新实例化是利用Java反射机制,并且调用的是Fragment的无参构造方法,所以这一步是不会调用其他有参构造方法的。若Fragment没有无参构造方法,则clazz.newInstance()会抛出InstantiationExecption,然后就会打印出常见的一个Exception,"Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public"。
</pre><p><pre name="code" class="java">package android.app; public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener { private static final ArrayMap<String, Class<?>> sClassMap = new ArrayMap<String, Class<?>>(); 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: 实际中还会遇到另一种情况,该Fragment有无参构造方法,依然抛出了InstantiationException。这又是为什么呢?