在显示的对话框我得到“后的onSaveInstanceState无法执行此操作”(On showin

2019-08-18 09:29发布

某些用户报告,如果他们使用的快速行动的通知栏,他们获得一个强制关闭。

我表现出谁所说的“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");
}

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

其中IllegarStateException被炒鱿鱼的代码行是:

snoozeDialog.show(fm, "snooze_dialog");

该类扩展“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)

我不能重现这个错误,但我得到了很多的错误报告。

任何人可以帮助我如何解决这个问题?

Answer 1:

这是常见的问题 。 我们通过覆盖显示()和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);
        }
    }
}

需要注意的是应用这种方法不会改变DialogFragment.class的内部字段:

boolean mDismissed;
boolean mShownByMe;

这可能导致在某些情况下意外的结果。 更好地利用commitAllowingStateLoss(),而不是提交()



Answer 2:

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

Android将保存在您的片段状态onSaveInstanceState() 所以,如果你commit()之后片段onSaveInstanceState()片段的状态将丢失。

其结果是,如果活动被杀掉并重新创建后的片段将不会添加到活动这是不好的用户体验。 这就是为什么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");
    }
}

注:onResumeFragments()将调用时碎片恢复。



Answer 3:

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: 链接



Answer 4:

如果对话框并不重要(它是好的,不,显示它在应用程序关闭/不再考虑),使用:

boolean running = false;

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

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

和开放的,只有当我们正在运行的对话(片段):

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

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

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


Answer 5:

几天后,我想分享我的解决方案如何,我已经修复它,以示DialogFragment你应该重写show()的方法就可以了,并调用commitAllowingStateLoss()上的Transaction对象。 下面是例子科特林:

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

        }

    }


Answer 6:

请尽量使用FragmentTransaction而不是FragmentManager。 我想下面的代码将解决你的问题。 如果没有,请让我知道。

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

编辑:

片段交易

请检查该链接。 我认为这将解决您的查询。



Answer 7:

我已经在这个问题持续了多年。
该因特网和与得分散落(几百?几千?)讨论过这个问题,混乱和虚假信息在他们看来层层叠叠。
为了使情况变得更糟,并在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);
    }
};


Answer 8:

虽然它没有正式提及任何地方,但我面对这个问题几次。 根据我的经验有什么不对的兼容性库上导致此问题老式的平台支持片段。 您可以使用通过使用正常的片段管理器API进行测试。 实在不行,那么你可以使用正常的对话,而不是对话片段。



Answer 9:

  1. 这个类添加到您的项目:(必须在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. 扩展StatelessDialogFragment代替DialogFragment
  2. 使用该方法,而不是showAllowingStateLoss

  3. 请享用 ;)



Answer 10:

可以用下面的实现来解决过程中进行安全状态的变化问题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);
    }
}

然后,使用这样的类:

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

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

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

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

如果你想创建一个带参数的更通用对话框类,你可以在拯救他们Bundle在该参数show()方法,并与检索它们getArguments()onCreateDialog()

整个方法可能看起来有点复杂,但是一旦你已经创建活动和对话的两个基类,这是很容易使用,并且是完全的工作。 它可以用于其他Fragment可能由同一个问题的影响基于操作。



Answer 11:

许多意见后高层次的事件,如点击处理事件队列来运行推迟。 所以问题是,“的onSaveInstanceState”已经呼吁活动,但事件队列包含延期“点击事件”。 因此,当这一事件被分派到您的处理

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

和你的代码不show的则抛出IllegalStateException。

最简单的办法是清理事件队列中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();
        }
}


Answer 12:

出现此错误正在发生,因为输入事件(如按键或向下的onclick事件)越来越交付后onSaveInstanceState被调用。

该解决方案是重写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();
        }
    }
}


Answer 13:

让你的对话片段对象全局和调用dismissAllowingStateLoss()中的onPause()方法

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

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

不要忘了在片段和调用显示()上按一下按钮或者其它任何分配值。



文章来源: On showing dialog i get “Can not perform this action after onSaveInstanceState”