Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android.app.Fragment$InstantiationException的原因分析

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

編輯:關於Android編程

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的靜態工廠方法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"。


 

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。


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved