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

Android 片段真的需要一个空构造函数吗?,android,android-fragments,Android,Android Fragments,我有一个片段,带有一个接受多个参数的构造函数。我的应用在开发过程中运行良好,但在生产过程中,我的用户有时会看到这种崩溃: android.support.v4.app.Fragment$instanceionException:无法实例化片段 确保类名存在,是公共的,并且有一个公共的空构造函数 正如这个错误消息所暗示的,我可以创建一个空构造函数,但这对我来说没有意义,因为那时我必须调用一个单独的方法来完成片段的设置 我很好奇为什么这次坠机只是偶尔发生。也许我使用的是查看页面错误?我自己实例化所

我有一个
片段
,带有一个接受多个参数的构造函数。我的应用在开发过程中运行良好,但在生产过程中,我的用户有时会看到这种崩溃:

android.support.v4.app.Fragment$instanceionException:无法实例化片段
确保类名存在,是公共的,并且有一个公共的空构造函数
正如这个错误消息所暗示的,我可以创建一个空构造函数,但这对我来说没有意义,因为那时我必须调用一个单独的方法来完成
片段的设置


我很好奇为什么这次坠机只是偶尔发生。也许我使用的是
查看页面
错误?我自己实例化所有
片段
s,并将它们保存在
活动
中的列表中。我不使用
FragmentManager
事务,因为我看到的
ViewPager
示例不需要它,而且在开发过程中一切似乎都正常。

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

是的,它们需要

无论如何,您不应该真的重写构造函数。您应该定义一个
newInstance()
静态方法,并通过参数(bundle)传递任何参数

例如:

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;
}
当然,通过这种方式抓取args:

@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();
    }
}
这样,如果分离并重新附加对象,则可以通过参数存储对象状态。很像意图的捆绑

原因-额外阅读

我想我会为那些想知道原因的人解释原因

如果您选择:

您将看到
片段中的
实例化(..)
方法调用
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);
    }
}

正如Commonware在这个问题中所指出的,如果您正在创建一个片段的匿名子类,那么这个错误也会发生,因为匿名类不能有构造函数


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

以下是我的简单解决方案:

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-享受它

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

如果您暂停活动或将其销毁。因此,您进入主屏幕,然后Android会停止该活动以节省空间。片段状态将被保存(使用args),然后gc对象(通常)。因此,在返回到活动时,应该尝试使用保存的状态重新创建片段,new Default(),然后使用onCreate等。。。此外,如果活动试图节省资源(低内存电话),它可能会删除刚刚暂停的对象。。这家伙应该能更好地解释。简言之,你不知道!:)@mahkie如果你需要很多对象/模型,你应该从数据库或ContentProvider异步获取它们。@Chris.Jenkins如果我不清楚,很抱歉。。。我的观点是,与活动不同,片段并没有明确说明构造函数不能用于传递/共享数据。虽然转储/恢复是可以的,但我相信保存多个数据副本有时会占用比视图销毁所能恢复的内存更多的内存。在某些情况下,可以选择将活动/片段的集合视为一个单元,将其作为一个整体销毁或完全不销毁,这样我们就可以通过构造函数传递数据。就目前而言,关于这个问题,空构造函数是唯一可以拥有的构造函数。为什么要保存多个数据副本?Bundles | Parcelable实际上可以在状态/片段/活动之间传递内存引用(实际上它会导致一些奇怪的状态问题),Parcelable真正有效地“复制”数据的唯一时间是在进程和整个生命周期之间。例如,如果您将活动中的对象传递给片段,则传递的引用不是克隆。你唯一真正的额外开销是额外的碎片对象。@Chris.Jenkins那么,这就是我对Parcelable的无知。阅读了Parcelable的简短javadoc,以及Parcelable中不远超过“reconstructed”一词的一部分,我还没有达到“Active Objects”部分,我认为它只是一个低级的、更优化但通用性较差的序列化。在此,我戴上羞耻的帽子,喃喃自语“仍然无法共享非随身物品,制作随身物品可能会很麻烦”:)在某些版本的android(至少是ICS)中,你可以进入设置->开发者选项并启用“不保留活动”。这样做将为您提供一种确定性的方法来测试需要无参数构造函数的情况。我将bundle数据分配给成员变量(使用非默认的构造函数)。当我关闭应用程序时,我的程序没有崩溃。只有当调度器将我的应用程序放在backburner上以“节省空间”时,才会发生这种情况。我发现这一点的方法是转到Task Mgr并打开大量其他应用程序,然后在debug中重新打开我的应用程序。它每次都会崩溃。当我使用Chris Jenkins answer来使用bundle args时,这个问题就解决了。您可能会对这个线程感兴趣:未来读者的一个附带说明:如果您的
片段
子类根本没有声明任何构造函数,那么默认情况下会隐式为您创建一个空的公共构造函数(这是一个)。您不必显式声明空构造函数,除非您还声明了其他构造函数(例如,带有参数的构造函数)。我只想指出,IntelliJ IDEA(至少对于14.1版而言)提供了一个警告,提醒您在片段中不应该有非默认构造函数。空片段构造函数应该调用super()构造器与否?我这样问是因为我发现空的公共构造函数是强制性的。如果呼叫
    myfragment = new MyFragment();
    myfragment.setParameter("here the value of my parameter");