При отображении диалога я получаю "Не могу выполнить это действие после onSaveInstanceState"
Некоторые пользователи сообщают, что если они используют быстрое действие в панели уведомлений, они приближаются.
Я показываю быстрое действие в уведомлении, которое вызывает класс "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)
Я не могу воспроизвести эту ошибку, но я получаю много сообщений об ошибках.
Кто-нибудь может помочь, как я могу исправить эту ошибку?
Ответы
Ответ 1
Это распространенная проблема.
Мы решили эту проблему, переопределив 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);
}
}
}
Обратите внимание, что применение этого метода не изменит внутренние поля DialogFragment.class:
boolean mDismissed;
boolean mShownByMe;
В некоторых случаях это может привести к неожиданным результатам. Лучше использовать commitAllowingStateLoss() вместо commit()
Ответ 2
Это означает, что после onSaveInstanceState()
фрагмента commit()
(show()
в случае диалога DialogFragment).
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() вызывается, когда фрагменты возобновляются.
Ответ 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();
}
ссылка: ссылка
Ответ 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;
}
Ответ 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) {
}
}
Ответ 6
Я сталкивался с этой проблемой в течение многих лет.
Интернет заполонен десятками (сотнями? Тысячами) дискуссий по этому поводу, и путаница и дезинформация в них кажутся обильными.
Чтобы сделать ситуацию еще хуже, и в духе комикса xkcd "14 стандартов" я добавляю свой ответ на ринг.
![xkcd 14 standards]()
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);
}
};
Ответ 7
попробуйте использовать FragmentTransaction вместо FragmentManager. Я думаю, что приведенный ниже код решит вашу проблему. Если нет, пожалуйста, дайте мне знать.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");
EDIT:
Фрагментная транзакция
Пожалуйста, проверьте эту ссылку. Я думаю, что это решит ваши запросы.
Ответ 8
Многие представления отправляют события высокого уровня, такие как обработчики кликов, в очередь событий для запуска отложенных. Поэтому проблема заключается в том, что "onSaveInstanceState" уже вызван для Activity, но очередь событий содержит отложенное событие "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)
и ваш код делает 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();
}
}
Ответ 9
Хотя это официально не упоминается нигде, но я столкнулся с этой проблемой пару раз. По моему опыту в библиотеке совместимости есть что-то неправильное, поддерживающее фрагменты на старых платформах, что вызывает эту проблему. Вы используете тест, используя обычный API-интерфейс менеджера фрагментов. Если ничего не работает, вы можете использовать обычный диалог вместо фрагмента диалога.
Ответ 10
- Добавьте этот класс в свой проект: (должен быть в пакете 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();
}
}
- Расширьте StatelessDialogFragment вместо DialogFragment
-
Используйте метод showAllowingStateLoss вместо show
-
Наслаждайтесь;)
Ответ 11
Сделайте объект вашего диалогового фрагмента глобальным и вызовите dismissAllowingStateLoss() в методе onPause()
@Override
protected void onPause() {
super.onPause();
if (dialogFragment != null) {
dialogFragment.dismissAllowingStateLoss();
}
}
Не забудьте назначить значение во фрагменте и вызвать show() по нажатию кнопки или где-либо еще.
Ответ 12
Следующая реализация может быть использована для решения проблемы выполнения безопасных изменений состояния в течение жизненного цикла 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
, на которые может повлиять одна и та же проблема.
Ответ 13
Эта ошибка возникает из-за того, что входные события (такие как нажатия клавиш или события 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();
}
}
}
Ответ 14
используйте этот код
FragmentTransaction ft = fm.beginTransaction();
ft.add(yourFragment, "fragment_tag");
ft.commitAllowingStateLoss();
вместо
yourFragment.show(fm, "fragment_tag");