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)
上一篇 2022年7月16日 上午6:36
下一篇 2022年7月16日 上午6:36


相关推荐

  • 吞食天地2刘蜀传1.8版本图文攻略

    吞食天地2刘蜀传1.8版本图文攻略此版本在悟饭趣玩 APP 可下载 跟以前吞食天地 2 刘蜀传攻略是不一样的版本 欢迎收藏转载 一 装备 1 武器篇 剑 枪 刀 弓 斧 鞭 青釭剑 235 会心枪 235 偃月刀 235 李广弓 235 落雷斧 235 金鞭 131 倚天剑 211

    2026年3月17日
    2
  • 项目范围管理计划模板[通俗易懂]

    项目范围管理计划模板[通俗易懂]XX项目项目范围管理计划     文档编号:文档信息:文档名称:文档类别:工程类密   级:内部基准:版本信息:1.0建立日期:创建人:批准人:批准日期: 保管人:存放位置:配置库编辑软件:MicrosoftOffice2007中文版     文档修订记录

    2025年11月30日
    6
  • 不堪入目!某酒店社交App充斥大量涉黄信息

    点击上方“全栈程序员社区”,星标公众号重磅干货,第一时间送达          正文    近年来网…

    2021年6月28日
    123
  • vscode前端插件安装「建议收藏」

    vscode前端插件安装「建议收藏」1.修改语言,如果英语六级的话,便就可以不用修改,按住ctrl+shift+x打开拓展,安装LanguagePacks插件,然后按住Ctrl+Shift+P打开命令调色板,搜索ConfigureDisplayLanguage命令然后按Enter键,将locale.json创建一个文件,其默认值设置为您的操作系统语言。修改为zh-cn语言即可。2.HTMLSnippets:超级实用且初级的H5代码片段以及提示;3.HTMLHint:html代码检测;4.HTMLCSSSupp

    2022年7月25日
    14
  • 2017中国程序员薪资生存现状调查报告结论_程序员的收入

    2017中国程序员薪资生存现状调查报告结论_程序员的收入程序员一直都是一个备受人们关注的群体。2014年,据IDC统计,全球约有1850万名程序员,中国占10%。随着近年全国互联网创业热潮的兴起,“互联网+”、“云计算”以及“智能硬件”等领域发展迅速,市场对程序员的需求更为旺盛。  由程序员客栈联合稀土掘金通过对北京、广东、浙江、上海等全国28个省、直辖市及特别行政区的10W+优秀程开发者进行了一次调查。调查报告里对程序员的年龄组成、性别比例、

    2022年10月11日
    3
  • macbook 安装vue环境及pycharm中编辑vue

    macbook 安装vue环境及pycharm中编辑vue1 安装 brewbrew 是 Mac 下的一个包管理工具 作用类似于 centos 下的 yum brew 可以用一条命令 就可以在 mac 上安装 卸载 更新各种软件包 安装命令 usr bin ruby e curl fsSLhttps raw githubuserco com Homebrew install master install 如果提示你 Failedtoconn githubuserco comport443 Co

    2026年3月19日
    2

发表回复

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

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