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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 小米BL不解锁刷机「建议收藏」

    小米BL不解锁刷机「建议收藏」关于小米NOTE顶配近期解锁的问题中发现还有很多人不会用9008模式刷机,现出个简单教程方便米粉们救砖。硬件:小米NOTE顶配手机win10系统的电脑手机与电脑相连的数据线软件:老版本的miflash(版本号:2015.10.28。0)最新版本miflash最新解BL锁工具或者小米助手(用来装驱动)开发版6.17及以前的线刷包随便下一个一个你最终想刷的版本(推荐最新稳定版)…

    2022年4月30日
    165
  • c# 连接数据库2 OleDbDataAdapter DataSet[通俗易懂]

    c# 连接数据库2 OleDbDataAdapter DataSet[通俗易懂]    privatevoidbutton2_Click(objectsender,EventArgse)    {      stringcon=@"Provider=Microsoft.Jet.OleDb.4.0;DataSource=fruit.mdb";      OleDbConnectiondbconn=newO…

    2022年10月26日
    0
  • Dreamweaver 2020安装教程[通俗易懂]

    Dreamweaver 2020安装教程[通俗易懂]装前先关闭杀毒软件和360卫士,注意安装路径不能有中文,安装包路径也不要有中文。1.鼠标右击【Dreamweaver2020】压缩包选择【解压到Dreamweaver2020】。2.双击打开解压后的【Dreamweaver2020】文件夹。3.双击打开【setup】文件夹。4.鼠标右击【Set-up】选择【以管理员身份运行】。5.点击【继续】。6.点击文件夹图标,然后点击【更改位置】更改安装路径。7.建议安装在除…

    2022年5月25日
    37
  • potplayer自定义加速播放[通俗易懂]

    potplayer自定义加速播放[通俗易懂]进入选项如下图如下图,其中第三步输入快捷键时使用键盘,如冲突会有响铃和提示,建议使用图中ctrl+方向键一定要点击应用

    2022年5月21日
    54
  • 基于matlab的声源定位系统_matlab电流源在哪

    基于matlab的声源定位系统_matlab电流源在哪##一、获取代码方式**获取代码方式1:**完整代码已上传我的资源:[【声源定位】基于matlab广义互相关声源定位【含Matlab源码548期】](https://download.csdn.net/download/TIQCmatlab/31339120)点击上面蓝色字体,直接付费下载,即可。**获取代码方式2:**[付费专栏语音处理(Matlab)](https://blog.csdn.net/tiqcmatlab/category_11941450.html)…

    2022年9月15日
    0
  • 算法-动态规划 Dynamic Programming–从菜鸟到老鸟

    算法-动态规划 Dynamic Programming–从菜鸟到老鸟前言最近在牛客网上做了几套公司的真题,发现有关动态规划(DynamicProgramming)算法的题目很多。相对于我来说,算法里面遇到的问题里面感觉最难的也就是动态规划(DynamicProgramming)算法了,于是花了好长时间,查找了相关的文献和资料准备彻底的理解动态规划(DynamicProgramming)算法。一是帮助自己总结知识点,二是也能够帮助他人更好的理解这个算法。后面的参

    2022年6月11日
    83

发表回复

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

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