在显示对话框时,我得到"在onSaveInstanceState之后无法执行此操作"

chr*_*ine 110 android dialog fragment

有些用户报告,如果他们使用通知栏中的快速操作,他们就会收到一个强制关闭.

我在通知中显示了一个快速操作,它调用了"TestDialog"类.在按下"贪睡"按钮后的TestDialog类中,我将显示SnoozeDialog.

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}
Run Code Online (Sandbox Code Playgroud)

错误是 *IllegalStateException: Can not perform this action after onSaveInstanceState*.

IllegarStateException被触发的代码行是:

snoozeDialog.show(fm, "snooze_dialog");
Run Code Online (Sandbox Code Playgroud)

该类正在扩展"FragmentActivity",而"SnoozeDialog"类正在扩展"DialogFragment".

以下是错误的完整堆栈跟踪:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)
Run Code Online (Sandbox Code Playgroud)

我无法重现此错误,但我收到了很多错误报告.

任何人都可以帮助我如何解决这个错误?

Raf*_*ael 57

这是常见问题.我们通过重写show()并在DialogFragment扩展类中处理异常来解决这个问题

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,应用此方法不会更改DialogFragment.class的内部字段:

boolean mDismissed;
boolean mShownByMe;
Run Code Online (Sandbox Code Playgroud)

在某些情况下,这可能会导致意外结果.更好地使用commitAllowingStateLoss()而不是commit()

  • 此时,您可以调用commitAllowingStateLoss()而不是commit().不会提出异常. (18认同)
  • 你刚刚添加了一个 try-catch 块,这不是问题的解决方案,你只是在掩盖一个错误。 (5认同)
  • 但为什么会出现这个问题呢?可以忽略错误吗?你做什么会发生什么?毕竟,当点击时,这意味着该活动是现场的,并且很好......无论如何,我在这里报告了这个,因为我认为这是一个错误:https://code.google.com/p/android/issues/细节?ID = 207269 (3认同)
  • 最好在try-catch子句中调用super.show(manager,tag)。DialogFragment拥有的标志可以通过这种方式保持安全 (2认同)

Pon*_*pat 24

这意味着你commit()(show()在DialogFragment的情况下)片段之后onSaveInstanceState().

Android将保存您的片段状态onSaveInstanceState().因此,如果片段状态commit()onSaveInstanceState()碎片将丢失.

因此,如果Activity被杀死并稍后重新创建,则片段将不会添加到用户体验不佳的活动.这就是为什么Android不会不惜一切代价让国家损失的原因.

简单的解决方案是检查状态是否已保存.

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:onResumeFragments()将在片段恢复时调用.

  • 如果我想在另一个片段中显示 DialogFragment 怎么办? (2认同)
  • 我使用单个调用发布(runnable).关于getFragmentManager,它取决于.如果要与另一个活动共享该对话框,则应使用getFragmentManager,但是,如果该对话框仅存在片段,则getChildFragmentManager似乎是更好的选择. (2认同)

小智 19

使用 Activity-KTX 的新生命周期范围就像以下代码示例一样简单:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}
Run Code Online (Sandbox Code Playgroud)

此方法可以在 onStop() 之后直接调用,并且在返回时调用 onResume() 后将成功显示对话框。

  • 感谢您分享这个。该 API 现在对该方法有一个警告:“注意:不建议使用此 API,因为它在某些情况下可能会导致资源浪费。请改用 Lifecycle.repeatOnLifecycle API。此 API 将在未来版本中删除。 (4认同)

huu*_*duy 15

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}
Run Code Online (Sandbox Code Playgroud)

参考:链接


Den*_*ski 10

几天后,我想分享我的解决方案如何,我已经修复它,以示DialogFragment你应该重写show()它的方法和调用commitAllowingStateLoss()Transaction对象.这是Kotlin的例子:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }
Run Code Online (Sandbox Code Playgroud)

  • 为了使开发人员不必继承“DialogFragment”,您可以将其更改为具有以下签名的 Kotlin 扩展函数:“fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String)”。另外,try-catch 不是必需的,因为您调用的是 `commitAllowingStateLoss()` 方法而不是 `commit()` 方法。 (4认同)

Fra*_*ank 9

如果对话框不是很重要(可以在应用程序关闭/不再查看时不显示它),请使用:

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}
Run Code Online (Sandbox Code Playgroud)

并且只在我们运行时打开您的对话框(片段):

if (running) {
    yourDialog.show(...);
}
Run Code Online (Sandbox Code Playgroud)

编辑,可能更好的解决方案:

在生命周期中调用onSaveInstanceState是不可预测的,我认为更好的解决方案是检查isSavedInstanceStateDone(),如下所示:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

@Override
protected void onResume() {
    super.onResume();

    savedInstanceStateDone = false;
}

@Override
protected void onStart() {
    super.onStart();

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}
Run Code Online (Sandbox Code Playgroud)


Mạn*_*ynh 7

如果您重写 show() 函数,请不要这样做:

override fun show(manager: FragmentManager, tag: String?) {
    // mDismissed = false; is removed -> lead to wrong state
    // mShownByMe = true; is removed -> lead to wrong state
    val ft = manager.beginTransaction()
    ft.add(this, tag)
    ft.commitAllowingStateLoss()
}
Run Code Online (Sandbox Code Playgroud)

它可能会导致错误的对话框状态

做就是了:

override fun show(manager: FragmentManager, tag: String?) {
    try {
        super.show(manager, tag)
    } catch (e: Exception) {
        val ft = manager.beginTransaction()
        ft.add(this, tag)
        ft.commitAllowingStateLoss()
    }
}
Run Code Online (Sandbox Code Playgroud)


swo*_*oby 6

我已经遇到这个问题很多年了。
互联网上散布着成百上千(数百?数千?)关于此的讨论,并且其中的困惑和虚假信息似乎很多。
更糟糕的是,本着xkcd“ 14 standard”漫画的精神,我将自己的答案扔进了电话里。
xkcd 14标准

cancelPendingInputEvents()commitAllowingStateLoss()catch (IllegalStateException e),和类似的解决方案都显得残酷。

希望以下内容可以轻松地显示如何重现和解决问题:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢那些没有任何解释就投反对票的人。而不是 * 只是 * 否决投票,如果他们解释我的解决方案是如何有缺陷的,也许会更好?我可以否决否决选民的否决票吗? (3认同)
  • 我认为不赞成投票可能是嵌入式XKCD的结果,答案实际上并不是发表社会评论的地方(无论多么有趣和/或真实)。 (2认同)

RIJ*_* RV 5

请尝试使用FragmentTransaction而不是FragmentManager.我认为以下代码将解决您的问题.如果没有,请告诉我.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");
Run Code Online (Sandbox Code Playgroud)

编辑:

片段交易

请检查此链接.我认为它会解决你的疑问.

  • 关于使用FragmentTransaction解决问题的原因的任何解释都会很棒. (4认同)
  • Dialog#show(FragmentManager,tag)做同样的事情.这不是解决方案. (3认同)
  • 这个答案不是解决方案.DialogFragment #show(ft)和show(fm)完全相同. (3认同)

小智 5

使您的对话框片段对象成为全局对象并在 onPause() 方法中调用dismissAllowingStateLoss()

@Override
protected void onPause() {
    super.onPause();

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}
Run Code Online (Sandbox Code Playgroud)

不要忘记在片段中分配值并在按钮单击或任何地方调用 show() 。


Dal*_*ngh 1

虽然没有在任何地方正式提及,但我遇到过这个问题几次。根据我的经验,旧平台上支持片段的兼容性库存在问题,导致此问题。您可以使用普通的片段管理器 API 来测试它。如果没有任何效果,那么您可以使用普通对话框而不是对话框片段。