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

android.app.Fragment$InstantiationException的原因分析每个Fragment必须要有一个无参构造方法,这样该Fragment在Activity恢复状态的时候才可以被实例化。强烈建议,Fragment的子类不要有其他含参构造方法,因为这些构造方法在Fragment重新实例化时不会被调用。取而代之的方式是,通过setArguments(Bundle)设置参数,然后通过getArguments获得参数。

大家好,又见面了,我是你们的朋友全栈君。

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<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。这又是为什么呢?

当你的Fragment是作为的一个非静态内部类的时候,这个时候利用Java反射机制也是无法实例化该Fragment的。我在实际项目中就是将一个DialogFragment定义为Activity的内部类,导致了这个Exception。因为非静态内部类对象的实例化需要先实例化外部类对象,仅仅实例化非静态内部类必然抛出InstantiationException。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160367.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 贴片电阻功率与尺寸对照表文库_贴片电阻功率怎么看

    贴片电阻功率与尺寸对照表文库_贴片电阻功率怎么看电阻封装尺寸与功率关系,通常来说:02011/20W 04021/16W 06031/10W 08051/8W 12061/4W电容电阻外形尺寸与封装的对应关系是:0402=1.0×0.51/16W 0603=1.6×0.81/16W~1/10W 0805=2.0×1.21/10W~1/8W 1206=3.2×1.61/8…

  • oninput事件

    oninput事件js监听input等表单输入框的变化事件oninput,手机页面开发中使用到文本框textarea输入字符监听文本框变化计算还可以输入多少字符,如果使用onkeyup的话是无法监听到输入法输入的文本变化的,复制粘贴也不能监听到,于是就用到了oninput事件来监听文本框value值的改变。由于是手机端页面没有考虑IE这货。js代码如下://计算文本输入统计(function

  • array.sort排序_javascript数组排序

    array.sort排序_javascript数组排序数组sort排序方法Array数组对象中的sort方法是根据数组中数组元素的字符编码进行排序的,所以对数字的排序,会跟想要的升序结果不一样通过设置sort()方法的参数可以按照自定义的排序方式对数组进行排序,sort()方法的参数是一个函数,需要自定义该函数,sort()方法会根据函数的返回结果对数组进行排序functioncompare(a,b){returna-b;}…

  • 深圳外包公司名单

    深圳外包公司名单深圳外包公司名单

  • pycharm如何调试python程序_Pycharm断点调试Python程序的步骤方法

    pycharm如何调试python程序_Pycharm断点调试Python程序的步骤方法利用Pycharm断点调试Python程序的方法1.代码准备没有语法错误的Python程序:#!/usr/bin/pythonimportnumpyasnpclassNetwork:def__init__(self,sizes):self.num_layers=len(sizes)self.sizes=sizesself.biases=[np.random.randn(y,1)for…

    2022年10月30日
  • 原地算法矩阵置0_原地排序算法有哪些

    原地算法矩阵置0_原地排序算法有哪些给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。进阶:一个直观的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。你能想出一个仅使用常量空间的解决方案吗?示例 1:输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]输出:[[1,0,1],[0,0,0],[1,0,1]]示例 2:输入:matrix

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号