在显示对话框我得到“不能执行此操作后onSaveInstanceState”

有些用户正在报告,如果他们使用通知栏中的快速操作,他们正在接近力量。

我在调用“TestDialog”类的通知中显示一个快速操作。 在按下“snooze”button后的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:在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) 

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

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

这是常见的问题 。 我们通过重写show()和处理DialogFragment扩展类中的exception来解决这个问题

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

}

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

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

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

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

 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()将在分段恢复时调用。

如果对话框不是非常重要(当应用程序closures/不再显示时没有显示它),请使用:

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

只有在运行时才打开对话框(片段):

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

编辑,可能更好的解决scheme:

在生命周期中调用onSaveInstanceState的地方是不可预知的,我认为更好的解决scheme是检查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; } 

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

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

编辑:

片段交易

请检查这个链接。 我想这会解决你的疑问。

 private void showSnoozeDialog() { FragmentManager fm = getSupportFragmentManager(); SnoozeDialog snoozeDialog = new SnoozeDialog(); // snoozeDialog.show(fm, "snooze_dialog"); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.add(dialog, null); ft.commitAllowingStateLoss(); } 

ref: 链接

几天后,我想分享我的解决scheme,我已经修复了它,显示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) { } } 

虽然没有任何地方正式提到,但我偶然遇到过这个问题。 根据我的经验,在旧版平台上支持片段的兼容性库会导致这个问题。 您可以使用普通的片段pipe理器API来使用testing。 如果没有任何作品,那么你可以使用普通的对话框而不是对话框片段

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

 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, eg Message, Icon, etc. ).create(); } } 

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

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

整个方法看起来有点复杂,但是一旦创build了活动和对话框的两个基类,它就很容易使用,并且完美地工作。 它可以用于其他基于Fragment的操作,这可能会受到相同问题的影响。

  1. 将这个类添加到你的项目中:(必须在android.support.v4.app包中)
包android.support.v4.app;


 / **
  *由Gil在8/16/2017创build。
  * /

公共类StatelessDialogFragment扩展DialogFragment {
     / **
      *显示对话框,使用现有的事务添加片段,然后提交
      *交易,同时允许国家损失。 
* *我build议大部分时间使用{@link #show(FragmentTransaction,String)} *这是你真正不关心的对话。 (debugging/跟踪/广告等) * * @param交易 *添加片段的现有事务。 * @参数标签 *这个片段的标签,按照 * {@link FragmentTransaction#add(Fragment,String)FragmentTransaction.add}。 * @return按照每个返回已提交事务的标识符 * {@link FragmentTransaction#commit()FragmentTransaction.commit()}。 * @see StatelessDialogFragment#showAllowingStateLoss(FragmentManager,String) * / public int showAllowingStateLoss(FragmentTransaction事务,String标记) { mDismissed = false; mShownByMe = true; transaction.add(this,tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss(); 返回mBackStackId; } / ** *显示对话框,将片段添加到给定的FragmentManager。 这是一个方便 *为显式创build一个事务,添加片段给它与给定的标签,和 *承诺它不关心状态。 这不会将交易添加到 *返回堆栈。 当片段被解散时,将执行一个新的事务来移除它 *来自活动。
* *我build议你在大部分时间使用{@link #show(FragmentManager,String)},但这是 *对于你真正不关心的对话。 (debugging/跟踪/广告等) * * * @param经理 *片段pipe理器这个片段将被添加到。 * @参数标签 *这个片段的标签,按照 * {@link FragmentTransaction#add(Fragment,String)FragmentTransaction.add}。 * @see StatelessDialogFragment#showAllowingStateLoss(FragmentTransaction,String) * / 公共无效showAllowingStateLoss(FragmentManager经理,string标签) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this,tag); ft.commitAllowingStateLoss(); } }
  1. 扩展StatelessDialogFragment而不是DialogFragment
  2. 使用方法showAllowingStateLoss而不是show

  3. 请享用 ;)