Fragment 必须是公共静态类,才能从实例状态正确重新创建

Fragment must be a public static class to be properly recreated from instance state

提问人:Falling Into Infinity 提问时间:9/2/2016 最后编辑:Falling Into Infinity 更新时间:7/9/2022 访问量:31832

问:

更新到最新的支持存储库后,

compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:design:24.2.0'
compile 'com.android.support:percent:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'

我得到了一个奇怪的例外。

java.lang.IllegalStateException: Fragment null must be a public static class to be  properly recreated from instance state.
at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:435)
at android.support.v4.app.BackStackRecord.add(BackStackRecord.java:414)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:154)
at com.androidapp.base.BaseActivity.showDialogFragment(BaseActivity.java:78)
at com.androidapp.MainActivity.showNewDialog(MainActivity.java:304)
at com.androidapp.MainActivity$6.onClick(MainActivity.java:228)

在我的 BaseActivity 类中,我创建了一个可重用的片段,该片段可用于扩展 BaseActivty 的活动类

public void showDialogFragment(DialogFragment newFragment) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
        if (prev != null) {
            ft.remove(prev);
        }
        ft.addToBackStack("dialog");
        newFragment.show(ft, "dialog");
    }

回到MainActivty,我像这样使用了这个片段,

public class MainActivity extends BaseActivity {

    @SuppressLint("ValidFragment")
        public void showNewDialog(int type, String title, String message) {
            final DialogNew dialog = new DialogNew() {
                @Override
                public void success(boolean isLandscape) {
                    .......
                }

                @Override
                public void cancel() {

                }
            };
            dialog.setArgs(title, message);
            super.showDialogFragment(dialog);
        }
}

DialogNew 类如下所示:

public abstract class DialogNew extends DialogFragment {

    private View rootView;

    private String title;
    private String message;

    public void setArgs(String title, String message) {
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("message", message);
        setArguments(args);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(STYLE_NO_TITLE, 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        rootView = inflater.inflate(R.layout.fragment_new_dialog, container, false);

        init();
        setListeners();

        return rootView;
    }

    public abstract void success(boolean isLandscape);

    public abstract void cancel();
}

PS:相同的代码适用于较旧的支持存储库。

android android-fragments android-support-library android-dialogfragmentfragment fragmentmanager

评论

1赞 Vucko 9/2/2016
为什么是抽象的?不能实例化抽象类。DialogNew
0赞 Enzokie 9/16/2016
@Vucko没关系。在做这种事情时,是的,你是对的,你不能实例化一个抽象,相反,它将初始化一个扩展该抽象类的匿名类。简而言之,这没有问题。
0赞 Meet Vora 9/22/2016
在支持库版本 24.2.1 中遇到相同的错误
0赞 Ahmed Eltaher 4/6/2017
然后添加 !,解决方案是什么!,我拿到了旧代码,并尝试更新支持库,结果崩溃了,我们该怎么办?
0赞 silentsudo 5/5/2020
你需要显式定义一个公共的无参数构造函数,Android 不是一个普通的 java!

答:

32赞 CommonsWare 9/2/2016 #1

这个错误并不是特别奇怪。如果您以前没有收到此错误,那就太奇怪了。

Android 会在配置更改(例如屏幕旋转)过程中销毁并重新创建片段,并在需要时作为重新构建任务的一部分(例如,用户切换到另一个应用,应用的进程在后台终止,然后用户尝试返回您的应用,所有这些都在 30 分钟左右)。Android 无法重新创建 的匿名子类。DialogNew

因此,创建一个常规的 Java 类(或嵌套类)来扩展并具有您的业务逻辑,替换您当前使用的匿名子类。publicpublicstaticDialogNewDialogNew

评论

0赞 Falling Into Infinity 9/2/2016
感谢您的回复!我同意你的逻辑。现在我的 DialogNew 已经扩展了 DialogFragment,所以我无法在 MainActivity 中扩展它,因为 MainActivity 本身扩展了 Activity。我不知道如何,但相同的代码在较旧的支持存储库中正常工作。我只想将对话框和活动类分开。
0赞 Adam Johns 5/4/2018
如果您的子类是 Android 库的一部分,并且您不想在公共 API 中公开它,该怎么办?DialogFragment
1赞 CommonsWare 5/4/2018
@AdamJohns:不幸的是,考虑到 Java 的工作方式,如果将“公共 API”定义为“标记为”的东西“,那么你们的目标就相互不兼容了。在文档中,您可以指出此片段不适合库的使用者使用。您甚至可以在库中放置自定义 Lint 检查以主动警告开发人员,尽管此过程记录不足且目前处于不断变化的状态。但是,从技术角度来看,鉴于它们实现片段的方式,您需要它们可以通过零参数构造函数进行实例化。publicpublic
1赞 Tim Rae 2/25/2017 #2

编辑:你可能不想这样做......请参阅评论。

代码示例看起来与我在这里建议的相似,而且我最近还发现我在那里的解决方案不再有效。我已经更新了 Java7 的答案,但如果你有 Java8,解决方案非常简单:

(我还没有测试过)

public class DialogNew extends DialogFragment {
    private View rootView;
    private String title;
    private String message;

    // Do nothing by default
    private Consumer mSuccess = (boolean b) -> {};
    private Runnable mCancel = () -> {};

    public void setArgs(String title, String message) {
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("message", message);
        setArguments(args);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(STYLE_NO_TITLE, 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.fragment_new_dialog, container, false);
        // use mSuccess.accept(boolean) when needed
        init();
        setListeners();
        return rootView;
    }

    public void setSuccess(Consumer success) {
        mSuccess = success;
    }

    public void setCancel(Runnable cancel) {
        mCancel = cancel;
    }
}

然后在“主要”活动中:

public class MainActivity extends BaseActivity {
        public void showNewDialog(int type, String title, String message) {
            final DialogNew dialog = new DialogNew();
            dialog.setArgs(title, message);
            dialog.setSuccess((boolean isLandscape) -> {
                //....
            });
            super.showDialogFragment(dialog);
        }
}

评论

3赞 Avi Cherry 8/11/2017
由于几个原因,这是行不通的。首先,并且不会在被包裹的碎片中幸存下来。如果你保留了这个 Fragment,你就会遇到一个更严重的问题:当你调用 dialog.setSuccess 并向它传递一个 lambda 表达式时,你实际上已经创建了一个 MainActivity 的内部类。在配置更改时重新创建 Activity 时,将发生内存泄漏,并且回调将尝试对已销毁的 Activity 调用方法。mSuccessmCancel
4赞 L.Charly 5/5/2020 #3

我从头开始重新创建了我的片段,它为我解决了问题。

新建 -> Fragment -> Fragment (Blank),然后在确认之前取消选中第二个框。

1赞 Kushal Prajapati 8/8/2020 #4

从新的 >Fragment> 空白片段创建片段

它对我♥♥♥有用

2赞 exbuddha 3/12/2021 #5

此错误的原因在 Android 开发人员指南中得到了很好的解释。

当系统发出配置更改时,它需要能够创建 Fragment 的新实例。为此,它依赖于片段的默认构造函数,该构造函数不带任何参数,因此不能有任何依赖关系。如果 Fragment 类不是静态公共类,则系统无法以反射方式找到此默认构造函数,并且错误仅指示这一点。

要解决此问题,您必须覆盖 FragmentManager 实例的 FragmentFactory 的默认实现,该实现将处理 fragment 的创建。这由我提供的链接中的代码解释。

0赞 Mehdi 7/9/2022 #6

发生此错误的原因是在创建 fragment 实例时使用了虚拟方法。

必须从声明中删除虚拟方法,并且应使用处理程序类来侦听 DialogNew 类事件。

public class MainActivity extends BaseActivity {

   @SuppressLint("ValidFragment")
   public void showNewDialog(int type, String title, String message) {

       final DialogNew dialog = new DialogNew(
            // use DialogHandler for manage success or cancel click
            new DialogHandler() {
                @Override
                public void success(boolean isLandscape) {
                    
                }

                @Override
                public void cancel() {

                }
            }
        );
       dialog.setArgs(title, message);
       super.showDialogFragment(dialog);
   }
}