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

时间:2022-08-26 22:01:11

Some user are reporting, if they use the quick action in the notification bar, they are getting a force close.

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

I show a quick action in the notification who calls the "TestDialog" class. In the TestDialog class after pressing the button "snooze", I will show the SnoozeDialog.

我在通知中显示了一个快速操作,它调用了“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");
}

The error is IllegalStateException: Can not perform this action after onSaveInstanceState.

错误是IllegalStateException:在onSaveInstanceState之后无法执行此操作。

The code line where the IllegarStateException gets fired is:

IllegarStateException被触发的代码行是:

snoozeDialog.show(fm, "snooze_dialog");

The class is extending "FragmentActivity" and the "SnoozeDialog" class is extending "DialogFragment".

该类正在扩展“FragmentActivity”,而“SnoozeDialog”类正在扩展“DialogFragment”。

Here is the complete stack trace of the error:

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

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)

I can't reproduce this error, but I am getting a lot of error reports.

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

Can anybody help that how can I fix this error?

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

12 个解决方案

#1


34  

This is common issue. We solved this issue by overriding show() and handling exception in DialogFragment extended class

这是常见问题。我们通过重写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);
        }
    }
}

#2


19  

That mean you commit() (show() in case of DialogFragment) fragment after onSaveInstanceState().

这意味着你在onSaveInstanceState()之后提交()(在DialogFragment情况下为show())片段。

Android will save your fragment state at onSaveInstanceState(). So, if you commit() fragment after onSaveInstanceState() fragment state will be lost.

Android会将你的片段状态保存在onSaveInstanceState()中。因此,如果在onSaveInstanceState()之后提交()片段,则片段状态将丢失。

As a result, if Activity get killed and recreate later the fragment will not add to activity which is bad user experience. That's why Android does not allow state loss at all costs.

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

The easy solution is to check whether state already saved.

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

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

Note: onResumeFragments() will call when fragments resumed.

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

#3


9  

If the dialog is not really important (it is okay to not-show it when the app closed/is no longer in view), use:

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

boolean running = false;

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

@Override
public void onStop() {
    running = false;
    super.onStop();
}

And open your dialog(fragment) only when we're running:

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

if (running) {
    yourDialog.show(...);
}

EDIT, PROBABLY BETTER SOLUTION:

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

Where onSaveInstanceState is called in the lifecycle is unpredictable, I think a better solution is to check on isSavedInstanceStateDone() like this:

在生命周期中调用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;
}

#4


8  

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

ref: link

ref:链接

#5


6  

After few days I want share my solution how I've fixed it, to show DialogFragment you should to override show() method of it and call commitAllowingStateLoss() on Transaction object. Here is example in Kotlin:

几天之后我想分享我的解决方案我是如何修复它的,为了显示DialogFragment你应该覆盖它的show()方法并在Transaction对象上调用commitAllowingStateLoss()。这是Kotlin的例子:

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

        }

    }

#6


4  

please try to use FragmentTransaction instead of FragmentManager. I think the below code will solve your problem. If not, Please let me know.

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

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

EDIT:

编辑:

Fragment Transaction

片段交易

Please check this link. I think it will solve you queries.

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

#7


1  

Though it's not officially mentioned anywhere but I faced this problem couple of times. In my experience there is something wrong in compatibility library supporting fragments on older platforms which causes this problem. You use test this by using normal fragment manager API. If nothing works then you can use the normal dialog instead of dialog fragment.

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

#8


1  

  1. Add this class to your project: (must be in android.support.v4.app package)
  2. 将此类添加到项目中:(必须在android.support.v4.app包中)
package android.support.v4.app;


/**
 * Created by Gil on 8/16/2017.
 */

public class StatelessDialogFragment extends DialogFragment {
    /**
     * Display the dialog, adding the fragment using an existing transaction and then committing the
     * transaction whilst allowing state loss.
* * I would recommend you use {@link #show(FragmentTransaction, String)} most of the time but * this is for dialogs you reallly don't care about. (Debug/Tracking/Adverts etc.) * * @param transaction * An existing transaction in which to add the fragment. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @return Returns the identifier of the committed transaction, as per * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentManager, String) */ public int showAllowingStateLoss(FragmentTransaction transaction, String tag) { mDismissed = false; mShownByMe = true; transaction.add(this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss(); return mBackStackId; } /** * Display the dialog, adding the fragment to the given FragmentManager. This is a convenience * for explicitly creating a transaction, adding the fragment to it with the given tag, and * committing it without careing about state. This does not add the transaction to the * back stack. When the fragment is dismissed, a new transaction will be executed to remove it * from the activity.
* * I would recommend you use {@link #show(FragmentManager, String)} most of the time but this is * for dialogs you reallly don't care about. (Debug/Tracking/Adverts etc.) * * * @param manager * The FragmentManager this fragment will be added to. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentTransaction, String) */ public void showAllowingStateLoss(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commitAllowingStateLoss(); } }
  1. Extend StatelessDialogFragment instead of DialogFragment
  2. 扩展StatelessDialogFragment而不是DialogFragment
  3. Use the method showAllowingStateLoss instead of show

    使用方法showAllowingStateLoss而不是show

  4. Enjoy ;)

    请享用 ;)

#9


1  

I have run in to this problem for years.
The Internets are littered with scores (hundreds? thousands?) of discussions about this, and confusion and disinformation in them seems aplenty.
To make the situation worse, and in the spirit of the xkcd "14 standards" comic, I am throwing in my answer in to the ring.
在显示对话框时,我得到“在onSaveInstanceState之后无法执行此操作”

多年来我一直遇到这个问题。互联网上散布着数十(数千??)的讨论,其中的混乱和虚假信息似乎很多。为了使情况变得更糟,并且本着xkcd“14标准”漫画的精神,我正在把我的答案扔进戒指。

The cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), and similar solutions all seem atrocious.

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

Hopefully the following easily shows how to reproduce and fix the problem:

希望以下内容能够轻松地显示如何重现和修复问题:

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

#10


0  

The following implementation can be used to solve the problem of performing safely state changes during the Activity lifecycle, in particular for showing dialogs: if the instance state has already been saved (e.g. due to a configuration change), it postpones them until the resumed state has been performed.

以下实现可用于解决在Activity生命周期中执行安全状态更改的问题,特别是用于显示对话框:如果实例状态已被保存(例如,由于配置更改),则将它们推迟到恢复状态已经完成了。

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

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

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

Then using a class like this:

然后使用这样的类:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

You can safely show dialogs without worrying about the app state:

您可以安全地显示对话框而无需担心应用程序状态:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

and then call TestDialog.show(this) from within your XAppCompatActivity.

然后从XAppCompatActivity中调用TestDialog.show(this)。

If you want to create a more generic dialog class with parameters, you can save them in a Bundle with the arguments in the show() method and retrieve them with getArguments() in onCreateDialog().

如果要使用参数创建更通用的对话框类,可以使用show()方法中的参数将它们保存在Bundle中,并使用onCreateDialog()中的getArguments()检索它们。

The whole approach could seem a bit complex, but once you have created the two base classes for activities and dialogs, it is quite easy to use and is perfectly working. It can be used for other Fragment based operations which could be affected by the same problem.

整个方法看起来有点复杂,但是一旦为活动和对话框创建了两个基类,它就非常容易使用并且非常有效。它可以用于其他可能受同一问题影响的基于Fragment的操作。

#11


0  

Many views post high-level events such as click handlers to the event queue to run deferred. So the problem is that "onSaveInstanceState" has already been called for the Activity but the event queue contains deferred "click event". Hence when this event is dispatched to your handler

许多视图将高级事件(例如点击处理程序)发布到事件队列以运行延迟。所以问题是已经为Activity调用了“onSaveInstanceState”,但事件队列包含延迟的“click事件”。因此,当此事件被分派给您的处理程序时

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

and your code does show the IllegalStateException is thrown.

并且您的代码确实显示抛出了IllegalStateException。

The simplest solution is to clean event queue, in onSaveInstanceState

最简单的解决方案是在onSaveInstanceState中清除事件队列

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}

#12


0  

This error appears to be occurring because input events (such as key down or onclick events) are getting delivered after onSaveInstanceState is called.

此错误似乎正在发生,因为在调用onSaveInstanceState之后会传递输入事件(例如按键或onclick事件)。

The solution is to override onSaveInstanceState in your Activity and cancel any pending events.

解决方案是覆盖Activity中的onSaveInstanceState并取消所有挂起的事件。

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}

#1


34  

This is common issue. We solved this issue by overriding show() and handling exception in DialogFragment extended class

这是常见问题。我们通过重写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);
        }
    }
}

#2


19  

That mean you commit() (show() in case of DialogFragment) fragment after onSaveInstanceState().

这意味着你在onSaveInstanceState()之后提交()(在DialogFragment情况下为show())片段。

Android will save your fragment state at onSaveInstanceState(). So, if you commit() fragment after onSaveInstanceState() fragment state will be lost.

Android会将你的片段状态保存在onSaveInstanceState()中。因此,如果在onSaveInstanceState()之后提交()片段,则片段状态将丢失。

As a result, if Activity get killed and recreate later the fragment will not add to activity which is bad user experience. That's why Android does not allow state loss at all costs.

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

The easy solution is to check whether state already saved.

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

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

Note: onResumeFragments() will call when fragments resumed.

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

#3


9  

If the dialog is not really important (it is okay to not-show it when the app closed/is no longer in view), use:

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

boolean running = false;

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

@Override
public void onStop() {
    running = false;
    super.onStop();
}

And open your dialog(fragment) only when we're running:

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

if (running) {
    yourDialog.show(...);
}

EDIT, PROBABLY BETTER SOLUTION:

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

Where onSaveInstanceState is called in the lifecycle is unpredictable, I think a better solution is to check on isSavedInstanceStateDone() like this:

在生命周期中调用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;
}

#4


8  

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

ref: link

ref:链接

#5


6  

After few days I want share my solution how I've fixed it, to show DialogFragment you should to override show() method of it and call commitAllowingStateLoss() on Transaction object. Here is example in Kotlin:

几天之后我想分享我的解决方案我是如何修复它的,为了显示DialogFragment你应该覆盖它的show()方法并在Transaction对象上调用commitAllowingStateLoss()。这是Kotlin的例子:

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

        }

    }

#6


4  

please try to use FragmentTransaction instead of FragmentManager. I think the below code will solve your problem. If not, Please let me know.

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

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

EDIT:

编辑:

Fragment Transaction

片段交易

Please check this link. I think it will solve you queries.

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

#7


1  

Though it's not officially mentioned anywhere but I faced this problem couple of times. In my experience there is something wrong in compatibility library supporting fragments on older platforms which causes this problem. You use test this by using normal fragment manager API. If nothing works then you can use the normal dialog instead of dialog fragment.

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

#8


1  

  1. Add this class to your project: (must be in android.support.v4.app package)
  2. 将此类添加到项目中:(必须在android.support.v4.app包中)
package android.support.v4.app;


/**
 * Created by Gil on 8/16/2017.
 */

public class StatelessDialogFragment extends DialogFragment {
    /**
     * Display the dialog, adding the fragment using an existing transaction and then committing the
     * transaction whilst allowing state loss.
* * I would recommend you use {@link #show(FragmentTransaction, String)} most of the time but * this is for dialogs you reallly don't care about. (Debug/Tracking/Adverts etc.) * * @param transaction * An existing transaction in which to add the fragment. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @return Returns the identifier of the committed transaction, as per * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentManager, String) */ public int showAllowingStateLoss(FragmentTransaction transaction, String tag) { mDismissed = false; mShownByMe = true; transaction.add(this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss(); return mBackStackId; } /** * Display the dialog, adding the fragment to the given FragmentManager. This is a convenience * for explicitly creating a transaction, adding the fragment to it with the given tag, and * committing it without careing about state. This does not add the transaction to the * back stack. When the fragment is dismissed, a new transaction will be executed to remove it * from the activity.
* * I would recommend you use {@link #show(FragmentManager, String)} most of the time but this is * for dialogs you reallly don't care about. (Debug/Tracking/Adverts etc.) * * * @param manager * The FragmentManager this fragment will be added to. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentTransaction, String) */ public void showAllowingStateLoss(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commitAllowingStateLoss(); } }
  1. Extend StatelessDialogFragment instead of DialogFragment
  2. 扩展StatelessDialogFragment而不是DialogFragment
  3. Use the method showAllowingStateLoss instead of show

    使用方法showAllowingStateLoss而不是show

  4. Enjoy ;)

    请享用 ;)

#9


1  

I have run in to this problem for years.
The Internets are littered with scores (hundreds? thousands?) of discussions about this, and confusion and disinformation in them seems aplenty.
To make the situation worse, and in the spirit of the xkcd "14 standards" comic, I am throwing in my answer in to the ring.
在显示对话框时,我得到“在onSaveInstanceState之后无法执行此操作”

多年来我一直遇到这个问题。互联网上散布着数十(数千??)的讨论,其中的混乱和虚假信息似乎很多。为了使情况变得更糟,并且本着xkcd“14标准”漫画的精神,我正在把我的答案扔进戒指。

The cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), and similar solutions all seem atrocious.

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

Hopefully the following easily shows how to reproduce and fix the problem:

希望以下内容能够轻松地显示如何重现和修复问题:

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

#10


0  

The following implementation can be used to solve the problem of performing safely state changes during the Activity lifecycle, in particular for showing dialogs: if the instance state has already been saved (e.g. due to a configuration change), it postpones them until the resumed state has been performed.

以下实现可用于解决在Activity生命周期中执行安全状态更改的问题,特别是用于显示对话框:如果实例状态已被保存(例如,由于配置更改),则将它们推迟到恢复状态已经完成了。

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

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

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

Then using a class like this:

然后使用这样的类:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

You can safely show dialogs without worrying about the app state:

您可以安全地显示对话框而无需担心应用程序状态:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

and then call TestDialog.show(this) from within your XAppCompatActivity.

然后从XAppCompatActivity中调用TestDialog.show(this)。

If you want to create a more generic dialog class with parameters, you can save them in a Bundle with the arguments in the show() method and retrieve them with getArguments() in onCreateDialog().

如果要使用参数创建更通用的对话框类,可以使用show()方法中的参数将它们保存在Bundle中,并使用onCreateDialog()中的getArguments()检索它们。

The whole approach could seem a bit complex, but once you have created the two base classes for activities and dialogs, it is quite easy to use and is perfectly working. It can be used for other Fragment based operations which could be affected by the same problem.

整个方法看起来有点复杂,但是一旦为活动和对话框创建了两个基类,它就非常容易使用并且非常有效。它可以用于其他可能受同一问题影响的基于Fragment的操作。

#11


0  

Many views post high-level events such as click handlers to the event queue to run deferred. So the problem is that "onSaveInstanceState" has already been called for the Activity but the event queue contains deferred "click event". Hence when this event is dispatched to your handler

许多视图将高级事件(例如点击处理程序)发布到事件队列以运行延迟。所以问题是已经为Activity调用了“onSaveInstanceState”,但事件队列包含延迟的“click事件”。因此,当此事件被分派给您的处理程序时

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

and your code does show the IllegalStateException is thrown.

并且您的代码确实显示抛出了IllegalStateException。

The simplest solution is to clean event queue, in onSaveInstanceState

最简单的解决方案是在onSaveInstanceState中清除事件队列

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}

#12


0  

This error appears to be occurring because input events (such as key down or onclick events) are getting delivered after onSaveInstanceState is called.

此错误似乎正在发生,因为在调用onSaveInstanceState之后会传递输入事件(例如按键或onclick事件)。

The solution is to override onSaveInstanceState in your Activity and cancel any pending events.

解决方案是覆盖Activity中的onSaveInstanceState并取消所有挂起的事件。

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}