有些用户报告,如果他们在通知栏中使用快速操作,他们就会收到一个强制关闭。
我在通知中显示了一个快速操作,该操作调用" 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"和#34; SnoozeDialog" class正在扩展" 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)
我无法重现此错误,但我收到了大量错误报告。
有人可以帮我解决这个错误吗?
答案 0 :(得分:47)
这是常见的issue。 我们通过重写show()并在DialogFragment扩展类
中处理异常来解决这个问题var pieDictionary = TerminalDictionary
.GroupBy(x => x.Value.Manufacturer)
.ToDictionary(g => g.Sum(v => v.Value.Freq));
请注意,应用此方法不会更改DialogFragment.class的内部字段:
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);
}
}
}
在某些情况下,这可能会导致意外结果。最好使用commitAllowingStateLoss()而不是commit()
答案 1 :(得分:21)
这意味着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");
}
}
注意:onResumeFragments()将在片段恢复时调用。
答案 2 :(得分:12)
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();
}
参考:link
答案 3 :(得分:11)
使用Activity-KTX的新生命周期作用域就像以下代码示例一样简单:
lifecycleScope.launchWhenResumed {
showErrorDialog(...)
}
此方法可以在onStop()之后直接调用,并且一旦返回就调用onResume(),将成功显示该对话框。
答案 4 :(得分:9)
如果对话框不是很重要(可以在应用程序关闭/不再显示时不显示),请使用:
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;
}
答案 5 :(得分:9)
几天之后我想分享我的解决方案我是如何修复它的,为了显示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) {
}
}
答案 6 :(得分:5)
请尝试使用FragmentTransaction而不是FragmentManager。我认为以下代码将解决您的问题。如果没有,请告诉我。
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");
编辑:
请检查此链接。我认为这将解决您的疑问。
答案 7 :(得分:5)
我已经遇到这个问题多年了
互联网上散布着数十(数千??)的讨论,其中的混乱和虚假信息似乎很多。
为了使情况变得更糟,并且本着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);
}
};
答案 8 :(得分:1)
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(); } }
使用 showAllowingStateLoss 方法代替show
享受;)
答案 9 :(得分:1)
虽然在任何地方都没有正式提及,但我几次遇到这个问题。根据我的经验,兼容性库中支持旧平台上的片段会导致此问题。您可以使用普通的片段管理器API来测试它。如果没有任何作用,那么您可以使用普通对话框而不是对话框片段。
答案 10 :(得分:0)
许多视图将高级事件(例如点击处理程序)发布到事件队列以运行延迟。所以问题是&#34; onSaveInstanceState&#34;已经调用了Activity,但事件队列包含deferred&#34; click事件&#34;。因此,将此事件分派给您的处理程序
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();
}
}
答案 11 :(得分:0)
此错误似乎正在发生,因为在调用onSaveInstanceState
后输入事件(例如按键或onclick事件)将被传递。
解决方案是覆盖您的活动中的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();
}
}
}
答案 12 :(得分:0)
使您的对话框片段对象成为全局对象,并在onPause()方法中调用dismissAllowingStateLoss()
@Override
protected void onPause() {
super.onPause();
if (dialogFragment != null) {
dialogFragment.dismissAllowingStateLoss();
}
}
不要忘记在片段中分配值,并在单击按钮或任何地方调用show()。
答案 13 :(得分:0)
使用此代码
FragmentTransaction ft = fm.beginTransaction();
ft.add(yourFragment, "fragment_tag");
ft.commitAllowingStateLoss();
代替
yourFragment.show(fm, "fragment_tag");
答案 14 :(得分:0)
我已经通过使用反射找到了解决该问题的好方法。 上述所有解决方案的问题在于, mDismissed 和 mShownByMe 字段不会更改其状态。
只需在您自己的自定义底部工作表对话框片段中覆盖方法“显示”,如下面的示例(科特琳)
override fun show(manager: FragmentManager, tag: String?) {
val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
mDismissedField.isAccessible = true
mDismissedField.setBoolean(this, false)
val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
mShownByMeField.isAccessible = true
mShownByMeField.setBoolean(this, true)
manager.beginTransaction()
.add(this, tag)
.commitAllowingStateLoss()
}
答案 15 :(得分:0)
以下实现可用于解决在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)
。{/ p>中拨打XAppCompatActivity
如果要创建包含参数的更通用的对话框类,可以使用Bundle
方法中的参数将它们保存在show()
中,并使用getArguments()
中的onCreateDialog()
进行检索。 1}}。
整个方法看起来有点复杂,但是一旦为活动和对话框创建了两个基类,它就非常容易使用并且非常有效。它可以用于可能受同一问题影响的其他基于Fragment
的操作。
答案 16 :(得分:0)
如果您覆盖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()
}
这可能导致对话状态错误
只需:
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()
}
}