碎片必须是公共静态类,才能从实例状态中正确地重新创建。

21

更新到最新的支持库之后,

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类中,我创建了一个可重用的片段,可以在扩展BaseActivity的活动类中使用它。

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,我使用了以下方式来使用fragment:

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();
}

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


1
为什么DialogNew是抽象的?因为你不能实例化一个抽象类。 - Vucko
@Vucko 没问题。在进行这种操作时,您是正确的,不能实例化抽象类,而是会初始化一个扩展该抽象类的匿名类。简而言之,这没有问题。 - Enzokie
在支持库版本24.2.1中遇到了相同的错误。 - Meet Vora
添加叹号!,有什么解决方案!我有旧代码,尝试更新支持库,但由于某种原因崩溃了,我们该怎么办? - Ahmed Eltaher
你需要明确定义一个公共无参构造函数,Android不是纯粹的Java! - silentsudo
6个回答

32

这个错误并不是特别奇怪。如果您以前没有遇到过这个错误,那才是奇怪的。

在Android中,当发生配置更改(例如屏幕旋转)或需要重新构建任务(例如用户切换到另一个应用程序时,您的应用程序进程在后台结束,然后用户尝试返回您的应用程序,所有这些都在大约30分钟内完成)时,Android会销毁和重建片段。Android无法重建DialogNew的匿名子类。

因此,请创建一个常规的public Java类(或一个publicstatic嵌套类),该类继承DialogNew并包含您的业务逻辑,以替换您目前正在使用的DialogNew的匿名子类。


谢谢回复!我同意你的逻辑。现在我的DialogNew已经扩展了DialogFragment,所以我不能在MainActivity中扩展它,因为MainActivity本身扩展了Activity。我不知道怎么做,但是相同的代码在旧的支持库中可以正常工作。我只想保持对话框和活动类分开。 - Falling Into Infinity
如果您的 DialogFragment 子类是 Android 库的一部分,而您不希望将其暴露在公共 API 中,该怎么办? - Adam Johns
1
@AdamJohns:不幸的是,考虑到Java的工作方式,如果“公共API”被定义为“标记为public的内容”,则您的目标是相互冲突的。在文档中,您可以指出此片段不适用于库的使用者。您甚至可以在库中放置自定义Lint检查以提前警告开发人员,尽管这个过程文档不足且处于流动状态。但是,从技术角度来看,鉴于他们实现片段的方式,您需要通过public零参数构造函数来实例化它们。 - CommonsWare

4

我从头重新创建了碎片,这解决了我的问题。

新建 -> 碎片 -> 碎片(空白),并在确认之前取消选中第二个框。


2
这个错误的原因在Android Developers指南中有很好的解释。
当系统发生配置更改时,它需要能够创建您的片段的新实例。为了这样做,它依赖于一个不带参数的片段的默认构造函数,因此不能有任何依赖关系。如果您的Fragment类不是静态公共类,则系统无法反射地找到此默认构造函数,并且该错误就会指出这一点。
为了解决这个问题,您将不得不覆盖FragmentManager实例的FragmentFactory的默认实现,该实现将处理创建您的片段。代码在我提供的链接中有解释。

1

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

对我有效 ♥♥♥


1
编辑:你可能不想这样做...请查看评论。
代码示例与我在这里提出的建议相似,我最近还发现我在那里提供的解决方案已经不能使用了。我已经更新了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
由于以下几个原因,这种方法行不通。首先,mSuccessmCancel在Fragment进行打包时无法存活。如果你将此Fragment保留,你将面临更严重的问题:当你调用dialog.setSuccess并传入一个 lambda 表达式时,实际上你已经创建了 MainActivity 的内部类。当活动在配置更改时重新创建时,会出现内存泄漏,并且回调将尝试在已销毁的 Activity 上调用方法。 - Avi Cherry

0

这个错误是因为在创建片段实例时使用了虚方法。

必须从声明中删除虚方法,并使用处理程序类来监听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);
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接