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

Posted

技术标签:

【中文标题】片段必须是公共静态类才能从实例状态正确重新创建【英文标题】:Fragment must be a public static class to be properly recreated from instance state 【发布时间】:2017-01-10 18:00:44 【问题描述】:

更新到最新的支持库后,

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

的 Activity 类
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:相同的代码适用于较旧的支持存储库。

【问题讨论】:

为什么DialogNew 是抽象的?你不能实例化一个抽象类。 @Vucko 没关系。当做这种事情时,是的,你是对的,你不能实例化一个抽象,而是会初始化一个扩展该抽象类的匿名类。总之没有问题。 在支持库版本 24.2.1 中面临同样的错误 然后添加! , 解决办法是什么 ! ,我得到了旧代码,并尝试更新支持库,它崩溃的原因,我们该怎么办? 你需要明确定义一个公共的无参数构造函数,Android不是一个普通的java! 【参考方案1】:

这个错误并不是特别奇怪。如果您之前没有收到此错误,很奇怪。

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

所以,创建一个常规的 public Java 类(或 public static 嵌套类),扩展 DialogNew 并具有您的业务逻辑,替换您目前使用的 DialogNew 的匿名子类。

【讨论】:

感谢您的回复!我同意你的逻辑。现在我的 DialogNew 已经扩展了 DialogFragment,所以我不能在 MainActivity 中扩展它,因为 MainActivity 本身扩展了 Activity。我不知道如何,但相同的代码在较旧的支持 repo 上正常工作。我只想保持对话框和活动类分开。 如果您的 DialogFragment 子类是 Android 库的一部分并且您不想在公共 API 中公开它怎么办? @AdamJohns:不幸的是,鉴于 Java 的工作方式,如果“公共 API”被定义为“标记为 public 的事物”,那么您的目标是相互不兼容的。在文档中,您可以指出此片段不适合库的使用者使用。你甚至可以在你的库中放置一个自定义的 Lint 检查来主动警告开发人员,尽管这个过程记录不足并且目前处于不断变化的状态。但是,从技术角度来看,考虑到它们实现片段的方式,您需要它们可以通过 public 零参数构造函数进行实例化。【参考方案2】:

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

New -> Fragment -> Fragment (Blank) 并且在确认之前取消选中第二个框。

【讨论】:

【参考方案3】:

编辑:您可能不想这样做...查看 cmets。

代码示例看起来与我在 here 上建议的类似,而且我最近还发现我在那里的解决方案不再有效。我已经为 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);
        

【讨论】:

这不起作用有几个原因。首先,mSuccessmCancel 将无法在被分割的 Fragment 中存活。如果你让这个 Fragment 被保留,你有一个更糟糕的问题:当你调用 dialog.setSuccess 并传递一个 lambda 表达式时,你实际上已经创建了一个 MainActivity 的内部类。当在配置更改时重新创建 Activity 时,您将发生内存泄漏,并且回调将尝试调用已销毁 Activity 上的方法。【参考方案4】:

Android Developers 指南中很好地解释了此错误的原因。

当系统发布配置更改时,它需要能够创建片段的新实例。为此,它依赖于片段的默认构造函数,该构造函数不接受任何参数,因此不能有任何依赖关系。如果您的 Fragment 类不是静态公共类,则系统无法反射性地找到此默认构造函数,错误就表明了这一点。

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

【讨论】:

【参考方案5】:

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

它对我有用♥♥♥

【讨论】:

以上是关于片段必须是公共静态类才能从实例状态正确重新创建的主要内容,如果未能解决你的问题,请参考以下文章

在android studio中升级repo v9后,片段必须是公共静态类崩溃错误

课程作业02

为啥 C# 公共静态变量不需要实例化?

静态变量和实例变量的区别?

从单独的类文件访问公共静态类的状态

c#静态变量和非静态变量的区别