片段真的需要一个空的构造函数吗?


258

我有一个Fragment带有多个参数的构造函数。我的应用在开发过程中运行良好,但是在生产环境中,我的用户有时会看到此崩溃:

android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment 
make sure class name exists, is public, and has an empty constructor that is public

我可以按照此错误消息的建议制作一个空的构造函数,但是对我来说这没有意义,因为那样的话我就必须调用一个单独的方法来完成的设置Fragment

我很好奇为什么这次崩溃只是偶尔发生。也许我使用ViewPager不正确?我Fragment自己实例化了所有s并将它们保存在列表中Activity。我不使用FragmentManager事务,因为ViewPager我所看到的示例并不需要事务,并且在开发过程中一切似乎都可以正常工作。


22
在某些版本的android(至少是ICS)中,您可以转到“设置”->“开发人员选项”并启用“不要保留活动”。这样做将为您提供确定性的方法,以测试需要无参数构造函数的情况。
基思(Keith)

我有同样的问题。我正在将捆绑数据分配给成员变量(使用非默认ctor)。当我杀死该应用程序时,我的程序没有崩溃-它只是在调度程序将我的应用程序放到backburner上以“节省空间”时发生的。我发现此问题的方法是转到“任务管理器”并打开大量其他应用程序,然后在调试中重新打开我的应用程序。每次都崩溃了。当我使用克里斯·詹金斯(Chris Jenkins)答案使用捆绑软件参数时,此问题已解决。
2014年

:你可能有兴趣在这个线程stackoverflow.com/questions/15519214/...
斯特凡Haustein

5
供将来读者使用的附带说明:如果您的Fragment子类根本没有声明任何构造函数,则默认情况下将为您隐式创建一个空的公共构造函数(这是标准Java行为)。你不会明确的声明一个空的构造,除非你也宣告其他构造(例如那些带有参数)。
东尼·陈

我仅提及至少在14.1版中使用的IntelliJ IDEA,它会警告您以下事实:片段中不应包含非默认构造函数。
RenniePet 2015年

Answers:


349

是的,他们愿意。

无论如何,您都不应该真正覆盖构造函数。您应该newInstance()定义一个静态方法,并通过参数传递所有参数(捆绑)

例如:

public static final MyFragment newInstance(int title, String message) {
    MyFragment f = new MyFragment();
    Bundle bdl = new Bundle(2);
    bdl.putInt(EXTRA_TITLE, title);
    bdl.putString(EXTRA_MESSAGE, message);
    f.setArguments(bdl);
    return f;
}

当然,可以这样获取参数:

@Override
public void onCreate(Bundle savedInstanceState) {
    title = getArguments().getInt(EXTRA_TITLE);
    message = getArguments().getString(EXTRA_MESSAGE);

    //...
    //etc
    //...
}

然后,您将像这样从片段管理器实例化:

@Override
public void onCreate(Bundle savedInstanceState) {
    if (savedInstanceState == null){
        getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.content, MyFragment.newInstance(
                R.string.alert_title,
                "Oh no, an error occurred!")
            )
            .commit();
    }
}

这样,如果分离并重新附加了对象状态,则可以通过参数存储它们。很像捆绑到Intent的包。

原因-额外阅读

我以为我会为那些想知道为什么的人解释为什么。

如果您检查:https : //android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

您将instantiate(..)Fragment类中调用该newInstance方法:

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.getConstructor().newInstance();
        if (args != null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.setArguments(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);
    } catch (NoSuchMethodException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": could not find Fragment constructor", e);
    } catch (InvocationTargetException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": calling Fragment constructor caused an exception", e);
    }
}

http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance()说明为什么在实例化时检查访问器是否是public访问器,以及该类加载器是否允许访问它。

总的来说,这是一个非常讨厌的方法,但是它允许使用状态FragmentManger杀死和重新创建Fragments。(Android子系统使用做了类似的事情Activities)。

示例类

我被问到很多有关打电话的问题newInstance。不要将此与class方法混淆。这个整个类的例子应该显示用法。

/**
 * Created by chris on 21/11/2013
 */
public class StationInfoAccessibilityFragment extends BaseFragment implements JourneyProviderListener {

    public static final StationInfoAccessibilityFragment newInstance(String crsCode) {
        StationInfoAccessibilityFragment fragment = new StationInfoAccessibilityFragment();

        final Bundle args = new Bundle(1);
        args.putString(EXTRA_CRS_CODE, crsCode);
        fragment.setArguments(args);

        return fragment;
    }

    // Views
    LinearLayout mLinearLayout;

    /**
     * Layout Inflater
     */
    private LayoutInflater mInflater;
    /**
     * Station Crs Code
     */
    private String mCrsCode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCrsCode = getArguments().getString(EXTRA_CRS_CODE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mInflater = inflater;
        return inflater.inflate(R.layout.fragment_station_accessibility, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mLinearLayout = (LinearLayout)view.findViewBy(R.id.station_info_accessibility_linear);
        //Do stuff
    }

    @Override
    public void onResume() {
        super.onResume();
        getActivity().getSupportActionBar().setTitle(R.string.station_info_access_mobility_title);
    }

    // Other methods etc...
}

2
如果您暂停活动或将其销毁。因此,您转到主屏幕,然后该活动将被Android杀死以节省空间。将保存碎片状态(使用args),然后保存gc对象(通常)。因此,在返回活动时,应尝试使用保存的状态,新的Default()然后onCreate等来重新创建片段。此外,如果活动正在尝试节省资源(低内存电话),则可能会删除刚刚暂停的对象.. Commonsguy应该能够更好地解释。总之你不知道!:)
克里斯·詹金斯(Chris.Jenkins)2012年

1
@mahkie确实,如果需要大量的对象/模型,则应该从数据库或ContentProvider异步获取它们。
克里斯·詹金斯(Chris.Jenkins)2012年

1
@ Chris.Jenkins对不起,如果我不清楚...我的意思是,与活动不同,片段并不能明确表明不得使用构造函数来传递/共享数据。尽管转储/恢复很好,但我认为保存多个数据副本有时会占用比视图破坏所能恢复的更多的内存。在某些情况下,可以选择将“活动/片段”集合作为一个单元来对待,或者将其作为一个整体进行销毁,或者根本不销毁-然后我们可以通过构造函数传递数据。目前,关于此问题,只有一个空的构造函数。
kaay

3
您为什么要持有多个数据副本?Bundles | Parcelable实际上可以在状态/片段/活动之间传递内存引用,(实际上会引起一些奇怪的状态问题),实际上,Parcelable唯一有效地“复制”数据的时间是在进程和整个生命周期之间。例如,如果您通过活动将对象传递给片段,则传递的引用不是克隆。您唯一真正的额外开销是额外的片段对象。
克里斯·詹金斯(Chris.Jenkins),2013年

1
@ Chris.Jenkins好吧,那是我对Parcelable的无知。阅读了Parcelable的简短Javadoc,以及不超过单词“ reconstructed”的Parcel的一部分之后,我还没有到达“ Active Objects”部分,因此得出结论,它只是一个底层的,经过优化的,但用途较少的Serializable。在此,我不要羞愧和咕哝的帽子“仍然不能共享nonparcelables,使Parcelables并可以是一个麻烦” :)
kaay

17

正如CommonsWare在此问题https://stackoverflow.com/a/16064418/1319061中指出的那样,如果您正在创建Fragment的匿名子类,则也会发生此错误,因为匿名类不能具有构造函数。

不要创建Fragment的匿名子类:-)


1
或者,正如该帖子中提到的CommonsWare一样,请确保将内部Activity / Fragment / Reciever声明为“静态”,以避免此错误。
托尼·威克姆

7

是的,正如您所见,支持包也实例化了片段(当片段被破坏并重新打开时)。您的Fragment子类需要一个公共的空构造函数,因为框架正在调用它。


空片段构造函数是否应该调用super()构造函数?我问这是因为我发现空的公共构造函数是强制性的。如果调用super()对于空的公共构造函数没有意义
TNR 2012年

@TNR,因为所有Fragment抽象都有一个空的构造函数,super()这将是徒劳的,因为父类打破了空的公共构造函数规则。因此,不,您不需要传递super()内部构造函数。
克里斯·詹金斯(Chris.Jenkins),2013年

4
实际上,并不需要在Fragment中明确定义一个空的构造函数。无论如何,每个Java类都有一个隐式的默认构造函数。摘自:docs.oracle.com/javase/tutorial/java/javaOO/constructors.html〜 “编译器会为没有构造函数的任何类自动提供无参数的默认构造函数。”
IgorGanapolsky

-6

这是我的简单解决方案:

1-定义片段

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

    public void setParameter(String parameter) {
        this.parameter = parameter;
    } 
}

2-创建新片段并填充参数

    myfragment = new MyFragment();
    myfragment.setParameter("here the value of my parameter");

3-享受吧!

显然,您可以更改参数的类型和数量。快捷方便。


5
但是,这不会处理系统对片段的重新加载。
Vidia
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.