android.app.Fragment$InstantiationException的原因分析

來源:互聯網
上載者:User

android.app.Fragment$InstantiationException的原因分析

1. Fragment$InstantiationException的原因分析

在編寫Fragment類的代碼時候,Android Lint有時會提示如下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 strongly 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().

每個Fragment必須要有一個無參構造方法,這樣該Fragment在Activity恢複狀態的時候才可以被執行個體化。強烈建議,Fragment的子類不要有其他含參構造方法,因為這些構造方法在Fragment重新執行個體化時不會被調用。取而代之的方式是,通過setArguments(Bundle)設定參數,然後通過getArguments獲得參數。

 

如果的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 nonConfig)方法恢複Fragment。

 

2. FragmentManagerImpl的restoreAllState(Parcelable state, ArrayList nonConfig)方法

該方法會調用FragmentState的instantiate(Activity activity, Fragment parent)方法。

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. FragmentState的instantiate(Activity activity, Fragment parent)方法

 

最終會調用Fragment的靜態Factory 方法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的靜態Factory 方法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"。


 

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: 實際中還會遇到另一種情況,該Fragment有無參構造方法,依然拋出了InstantiationException。這又是為什麼呢?
當你的Fragment是作為的一個非靜態內部類的時候,這個時候利用Java反射機制也是無法執行個體化該Fragment的。我在實際項目中就是將一個DialogFragment定義為Activity的內部類,導致了這個Exception。因為非靜態內部類對象的執行個體化需要先執行個體化外部類對象,僅僅執行個體化非靜態內部類必然拋出InstantiationException。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.