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。

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • vue学习笔记(二)——字符组件传值「建议收藏」

    vue学习笔记(二)——字符组件传值「建议收藏」1、子组件向父组件传值、vue中splice和$emit使用:https://blog.csdn.net/BlackPlus28/article/details/100136811<body> <div id=”app”> <!– v-model 数据双向绑定 –> …

    2022年6月13日
    28
  • C语言xml配置文件换行的方法[通俗易懂]

    C语言xml配置文件换行的方法[通俗易懂]/options参数设定成XML_PARSE_NOBLANKS,否则的话是不会在结点后面添加回车的。/doc=xmlReadFile(docname,“UTF-8”,XML_PARSE_NOBLANKS);//读取xml文件时忽略空格/把xmlSaveFormatFile的format参数修改成1,否则在使用xmlReadFile打开的xml文件时,在生成的xml文件里是会把所有的结点都放到一行里显示。/xmlSaveFormatFile(docname,doc,1);以上内容

    2022年7月12日
    16
  • 用浏览器怎样监控网页内容变化

    用浏览器怎样监控网页内容变化随着互联网的发展,一般单位或企业都通过网站对外发布动态消息;各种管理软件、saas系统也通过web页面实现订单管理、工单派遣等。如何在第一时间接收消息或工单提醒,就需要实时刷新监控页面内容变化。…

    2022年7月17日
    68
  • BD和DVD区域划分

    BD和DVD区域划分BD和DVD区域划分BD:A区:只能是在美国、日本以及香港、台湾等东南亚地区正常播放;B区:只能在欧洲和澳洲等国家正常播放;C区:是在中国大陆、俄罗斯和印度三国才能正常播放。DVD:第一区为:美国、加拿大;第二区为:日本、欧洲、埃及、南非、中东;第三区为:中国台湾、中国香港特别行政区、南韩、东南亚;第四区为:澳洲、新西兰、中南美洲、南太平洋岛屿;第五区为:俄罗斯、蒙古、印度

    2022年7月11日
    34
  • Java数组元素求和[通俗易懂]

    Java数组元素求和[通俗易懂]今天给大家解析,Java中数组元素求和的过程一听到求和我们应该首先想到,要运用到**+=**publicclassQiuhe{publicstaticvoidmain(String[]args){//定义一个静态初始化int[]arr={1,2,3,4,5,6,7,8,9};//再定义一个数据用来接收最后的和…

    2022年7月16日
    26
  • http://www.doframe.com/jetoolweb/index.html

    http://www.doframe.com/jetoolweb/index.htmlhttp://www.doframe.com/jetoolweb/index.htmlhttp://www.doframe.com/jetoolweb/html/tasks/orders.html#_mainDetail  http://www.sdpsoft.com/newsview.html?guid=C838FD44-2947-47D8-BE58-59AAF77BE86A…

    2022年9月29日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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