отримання винятку "IllegalStateException: Неможливо виконати цю дію після onSaveInstanceState"


355

У мене є додаток Live Android, і з ринку я отримав наступний слід стека, і я не маю уявлення, чому це відбувається, як це відбувається не в коді програми, а його отримання викликається тією чи іншою подією з програми (припущення)

Я не використовую фрагменти, все ще є посилання на FragmentManager. Якщо будь-яке тіло може кинути трохи світла на деякі приховані факти, щоб уникнути подібних питань:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)  

Ви знайшли рішення ще? Маючи таку ж проблему тут: stackoverflow.com/questions/7575921 / ...
nhaarman


2
@phlebas Ні, ти цього не зробив. Ваше стосується діалогів, а це не так. Верхня лінія відповідності трасування стека недостатня. Решта дуже різні. Я говорю це тому, що я просто переглянув ваше питання, і це мені, на жаль, не допомагає.
themightyjon

Чи використовуєте Ви нитку чи AsynTask у цій діяльності?
Хосе Кастро

21
Я обговорюю цю помилку в своєму дописі в блозі ... ви повинні її прочитати. :)
Алекс Локвуд

Відповіді:


455

Це найглупіша помилка, з якою я стикався досі. У мене була Fragmentпрограма, яка працює на API <11 та Force Closingна API> 11 .

Я дійсно не міг зрозуміти, що вони змінили всередині Activityжиттєвого циклу в заклику saveInstance, але я ось як вирішив це:

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super(). Bug on API Level > 11.
}

Я просто не телефоную .super()і все чудово працює. Сподіваюся, це заощадить вам якийсь час.

EDIT: після додаткових досліджень це відома помилка в пакеті підтримки.

Якщо вам потрібно зберегти екземпляр і додати щось до свого, outState Bundleви можете скористатися наступним:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

EDIT2: це також може статися, якщо ви намагаєтесь здійснити транзакцію після того, як ваша Activityпішла у фоновому режимі. Щоб цього уникнути, слід скористатисяcommitAllowingStateLoss()

EDIT3: Вищезазначені рішення виправляли проблеми в ранніх бібліотеках support.v4, наскільки я пам'ятаю. Але якщо у вас все-таки виникають проблеми з цим, ОБОВ'ЯЗКОВО також прочитайте блог @AlexLockwood : фрагменти транзакцій та втрата стану активності

Підсумок із публікації в блозі (але настійно рекомендую прочитати):

  • НІКОЛИ не здійснюйте commit() транзакцій після onPause()попередньої стільникової та onStop()після постної стільникової роботи
  • Будьте уважні, здійснюючи трансакції всередині Activityметодів життєвого циклу. Використання onCreate() , onResumeFragments()іonPostResume()
  • Уникайте здійснення транзакцій всередині асинхронних методів зворотного виклику
  • Використовуйте commitAllowingStateLoss()лише в крайньому випадку

97
Ви повинні використовувати commitAllowingStateLoss () замість того , щоб зробити ()
Хутро

7
Таким чином, якщо не викликати супер в onSaveInstanceState, це зупинить можливість FragmentManager зберегти стан усіх фрагментів і відновити їх. Це може спричинити проблеми з обертанням. Крім того, я щойно спробував інше, що стосується того, як класти мотлох в комплект, і це не має для мене ніякого значення. Не впевнений, як це буде - помилка, на яку ви посилаєтесь у пакеті підтримки, є NullPointerException, і не дуже схожа на цю IllegalStateException ...
themightyjon

56
@meh commitAllowingStateLoss()уникає виключення. Це не захищає вашу програму від випадкових втрат держави. Дивіться цю публікацію в блозі .
Алекс Локвуд

2
@AlexLockwood, тому з цього повідомлення в блозі ми можемо дізнатися, що нам слід робити всі наші мережеві дзвінки всередині фрагмента (і відображати деякий тимчасовий прогрес ui, якщо це потрібно), і це єдиний спосіб, коли ми можемо уникнути отримання цього винятку, коли це, мабуть, трапляється, тому що виконується викликається після деякого виклику асинхронного методу.
meh

1
Я не розумію першого пункту: "НІКОЛИ не здійснюйте () транзакції після ... onStop () на пост-стільниці". Що робити, якщо мені потрібна кнопка для запуску фрагмента, який слід замінити іншим? Чи слід ставити булевий файл, який перевіряє, чи закінчилась діяльність onStop, і якщо вона є, викликати натомість commitAllowingStateLoss? Крім того, що якщо у мене є фрагмент у фрагменті, який мені потрібно замінити після натискання кнопки?
андроїд розробник

76

Переглядаючи вихідний код Android про те, що викликає цю проблему, дає прапор mStateSaved у FragmentManagerImplкласі (екземпляр, доступний в Activity), має значення true. Встановлено значення true, коли резервний стек буде збережено (saveAllState) при виклику Activity#onSaveInstanceState. Після цього дзвінки від ActivityThread не скидають цей прапор, використовуючи доступні методи скидання з FragmentManagerImpl#noteStateNotSaved()і dispatch().

Як я це бачу, є деякі доступні виправлення, залежно від того, що робить і використовує ваш додаток:

Хороші способи

Перш за все: я б рекламував статтю Алекса Локвуда . Потім, з того, що я робив до цього часу:

  1. Для фрагментів та заходів, для яких не потрібно зберігати інформацію про стан, зателефонуйте на комісію commitAllowStateLoss . Взяте з документації:

    Дозволяє виконувати комісію після збереження стану активності. Це небезпечно, оскільки фіксація може бути втрачена, якщо активність потрібно буде пізніше відновити зі свого стану, тому це слід використовувати лише у випадках, коли стан користувальницького інтерфейсу несподівано зміниться користувачем несподівано ". Я думаю, що це добре, якщо фрагмент містить інформацію лише для читання. Або навіть якщо вони показують редаговану інформацію, використовуйте методи зворотного виклику, щоб зберегти відредаговану інформацію.

  2. Одразу після здійснення транзакції (ви щойно зателефонували commit()), зателефонуйте FragmentManager.executePendingTransactions().

Не рекомендовані способи:

  1. Як згадував вище Овідіу Латку, не телефонуйте super.onSaveInstanceState(). Але це означає, що ви втратите весь стан своєї діяльності разом із фрагментами.

  2. Переосмислити onBackPressedі там лише зателефонувати finish(). Це повинно бути добре, якщо ваша програма не використовує API фрагментів; так як super.onBackPressedтам є заклик до FragmentManager#popBackStackImmediate().

  3. Якщо ви використовуєте API фрагментів, і стан вашої активності важливий / життєво важливий, тоді ви можете спробувати зателефонувати за допомогою API відображення FragmentManagerImpl#noteStateNotSaved(). Але це злом, або можна сказати, що це обхід. Мені це не подобається, але в моєму випадку це цілком прийнятно, оскільки у мене є код із застарілого додатка, який використовує застарілий код ( TabActivityі неявно LocalActivityManager).

Нижче наведено код, який використовує відображення:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    invokeFragmentManagerNoteStateNotSaved();
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
    /**
     * For post-Honeycomb devices
     */
    if (Build.VERSION.SDK_INT < 11) {
        return;
    }
    try {
        Class cls = getClass();
        do {
            cls = cls.getSuperclass();
        } while (!"Activity".equals(cls.getSimpleName()));
        Field fragmentMgrField = cls.getDeclaredField("mFragments");
        fragmentMgrField.setAccessible(true);

        Object fragmentMgr = fragmentMgrField.get(this);
        cls = fragmentMgr.getClass();

        Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
        noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
        Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
    } catch (Exception ex) {
        Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
    }
}

Ура!


Схоже, це трапляється і з ActionBarSherlock, під Пряником, тому випадок перевірки Build Id здається суперечливим: :(
t0mm13b

Також слід зазначити - що не підходить і для використання АБС :)
t0mm13b

@ t0mm13b: Код, наведений вище, відповідає моєму проекту, оскільки він не використовує ні фрагментів, ні підтримки.FragmentActivity. Він працює на android.app.Activity і оскільки непослідовність, яка викликає Виняток, викликана FragmentManager (API рівня 11 вгору), тому перевірка ... Якщо ви вірите, що джерело зла те саме для вас теж, не соромтеся зняти чек. ABS - це вже інша історія, оскільки вона працює над пакетом сумісності та реалізацією підтримки.FragmentActivity може використовувати ту саму реалізацію FragmentManager та voila: та сама проблема.
гунар

@ t0mm13b: щоб додати більше, ніж 600 символів недостатньо, потрібно спочатку дослідити, що насправді спричинює це для вас. Ви також повинні усвідомити, що вище - це некрасивий злом, і я не несу відповідальності, якщо він працює чи ні (для мене це було найкращим рішенням з огляду на обставини). Якщо вам доведеться його використовувати, двічі перевірте у вихідному коді compat імена змінних, оскільки вони можуть відрізнятися від стандартного пакета. Я би сподівався, що цю проблему вирішать наступні версії пакету сумісності, але з досвіду Android мало шансів на те, щоб статися ...
gunar

Так, це саме те саме питання, що і в цьому звіті про помилки, в чому полягає питання цього ОП. Я стою проти мого коментаря - ви мали би чітко вказати відмову і сказати, що це не гарантується, а також слід було б заявити, що ви не використовували фрагменти - інакше навіщо також турбувати публікацію цієї відповіді! :) Просто кажу ...
t0mm13b

35

Такий виняток трапиться, якщо ви спробуєте виконати перехід фрагмента після того, як onSaveInstanceState()буде викликана діяльність вашої фрагмента .

Одна з причин цього може статися, якщо ви залишите AsyncTask(або Thread) працювати, коли діяльність припиняється.

Будь-які переходи після onSaveInstanceState()виклику потенційно можуть загубитися, якщо система відновлює активність для ресурсів і відтворить її пізніше.


2
Ей, Функ, у мене тут питання, як прийти onBackPress, щоб викликати цю активність або фрагмент, якщо ця діяльність чи фрагмент припинено. Вищеописаний виняток, здається, генерується від якогось події інтерфейсу (тобто натискання клавіші BACK), але я не в змозі з’ясувати співвідношення між завданнями Async та клавішею Назад.
dcool

Оскільки ви можете зберегти переходи фрагментів у зворотний стан, натискання кнопки "назад" може призвести до зворотного переходу збереженого переходу (тому старі фрагменти повертаються). onSaveInstanceState викликається перед знищенням вашої діяльності для відновлення ресурсів у системі, не завжди після виклику onStop. Вибачте, це було не дуже зрозуміло у моїй відповіді.
FunkTheMonk

Функція, але я не використовую жодних фрагментів у своїй програмі. Це можуть бути фрагменти, що використовуються в нативному коді. раніше я думав, що ти говориш про те саме.
dcool

1
У мене був AsyncTask із посиланням на фрагмент. Проблема вирішена після вилучення виклику super () з onSaveInstanceState та заміни посилання з мого AsyncTask на WeakReference <Fragment>.
Буффало

2
@Buffalo Це точно не вирішення проблеми. Ви завжди повинні телефонувати super.onSaveInstanceState().
Алекс Локвуд

27

Просто зателефонуйте до super.onPostResume () перед тим, як показати свій фрагмент або перемістити код у методі onPostResume () після виклику super.onPostResume (). Це вирішує проблему!


6
Виклик onPostResume () гарантує, що onResumeFragments () викликає, і для мене це ідеальне рішення.
j2emanue

20

Це також може статися під час виклику dismiss()фрагмента діалогу після блокування екрана \ пробілу та збереження стану екземпляра діалогу Діяльність +. Щоб обійти цей дзвінок:

dismissAllowingStateLoss()

Буквально кожен раз, коли я відхиляю діалог, я все одно не переймаюся його станом, так що це нормально - ви насправді не втрачаєте жодного стану.


2
Це була моя точна проблема! Ви, пан, геніальний!
Таш Пемхіва

17

Коротке та робоче рішення:

Дотримуйтесь простих кроків:

Крок 1 : Переопределить стан onSaveInstanceState у відповідному фрагменті. І видаліть із нього супер метод.

@Override
public void onSaveInstanceState(Bundle outState) {
};

Крок 2 : Використовуйте CommitAllowingStateLoss (); замість commit (); при цьому фрагмент операцій.

fragmentTransaction.commitAllowingStateLoss();

1
Видалення супер методу зробило трюк, чи можете ви пояснити, чому все ж? Чи безпечно це видалити?
Брюс

7
видаляти super () не безпечно, ви втратите інші конфіденційні дані після цього!
мертвий риб

12

Я думаю, що стан життєвого циклу може допомогти запобігти подібні збої, починаючи з Android support lib v26.1.0. Ви можете виконати наступну перевірку:

if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
  // Do fragment's transaction commit
}

або ви можете спробувати:

Fragment.isStateSaved()

більше інформації тут https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved ()


7

це працювало для мене ... я дізнався це самостійно ... сподіваюся, це допоможе тобі!

1) НЕ мають глобальної "статичної" FragmentManager / FragmentTransaction.

2) onCreate, ЗАВЖДИ знову ініціалізуйте FragmentManager!

зразок нижче: -

public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSavedInstanceState = savedInstanceState;
    setDefaultFragments();
}

protected void setDefaultFragments() {
    fragmentManager = getSupportFragmentManager();
    //check if on orientation change.. do not re-add fragments!
    if(mSavedInstanceState == null) {
        //instantiate the fragment manager

        fragmentTransaction = fragmentManager.beginTransaction();

        //the navigation fragments
        NavigationFragment navFrag = new NavigationFragment();
        ToolbarFragment toolFrag = new ToolbarFragment();

        fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
        fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
        fragmentTransaction.commitAllowingStateLoss();

        //add own fragment to the nav (abstract method)
        setOwnFragment();
    }
}

6

Я завжди отримував це, коли намагався показати фрагмент у методі onActivityForResult (), тому проблема була наступною:

  1. Моя активність призупинена і припинена, а це означає, що onSaveInstanceState () вже був викликаний (як для пристроїв перед сотом, так і для пост-стільників).
  2. У разі будь-якого результату я здійснив транзакцію, щоб показати / приховати фрагмент, що спричиняє цю незаконну дію.

Я зробив наступне:

  1. Додане значення для визначення того, чи потрібно виконати потрібну дію (наприклад, зробити фотографії з camere - isPhotoTaken) - це може бути булевим або цілим значенням залежно від кількості різних транзакцій.
  2. У переосмисленому методі onResumeFragments () я перевірив своє значення і після зроблених транзакцій з фрагментами мені знадобився. У цьому випадку commit () не було виконано після onSaveInstanceState, оскільки стан було повернуто методом onResumeFragments ().

5

Я вирішив проблему за допомогою налаштування, змінивши. Хитрість полягає в тому, що відповідно до життєвого циклу активності Android, коли ви явно викликали наміри (намір камери чи будь-який інший); активність призупинена і збережена. У цьому випадку викликається інстанція. При обертанні пристрою в інше положення, відмінне від того, під час якого діяла активність; виконання операцій з фрагментами, таких як фрагмент фіксації, викликає незаконне виключення стану. На це є багато скарг. Це щось про управління життєвим циклом діяльності Android та правильні виклики методів. Щоб вирішити це, я зробив це так: 1-Переозначте метод onsavedInstance вашої діяльності та визначте поточну орієнтацію екрана (портрет або пейзаж), а потім встановіть орієнтацію екрана на нього до того, як ваша діяльність буде призупинена. таким чином ви активуєте блокування обертання екрана для своєї діяльності, якщо воно було повернене іншим. 2-то, перейдіть на поновлюваний метод активності і встановіть свій режим орієнтації тепер на сенсор, щоб після виклику збереженого методу він ще раз зателефонує на конфігурацію, щоб правильно впоратися з обертанням.

Ви можете скопіювати / вставити цей код у свою діяльність, щоб боротися з ним:

@Override
protected void onSaveInstanceState(Bundle outState) {       
    super.onSaveInstanceState(outState);

    Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
    int orientation =this.getDisplayOrientation();
    //Lock the screen orientation to the current display orientation : Landscape or Potrait
    this.setRequestedOrientation(orientation);
}

//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device 
public int getDisplayOrientation() {
    Display getOrient = getWindowManager().getDefaultDisplay();

    int orientation = getOrient.getOrientation();

    // Sometimes you may get undefined orientation Value is 0
    // simple logic solves the problem compare the screen
    // X,Y Co-ordinates and determine the Orientation in such cases
    if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        Configuration config = getResources().getConfiguration();
        orientation = config.orientation;

        if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        // if height and widht of screen are equal then
        // it is square orientation
            if (getOrient.getWidth() == getOrient.getHeight()) {
                orientation = Configuration.ORIENTATION_SQUARE;
            } else { //if widht is less than height than it is portrait
                if (getOrient.getWidth() < getOrient.getHeight()) {
                    orientation = Configuration.ORIENTATION_PORTRAIT;
                } else { // if it is not any of the above it will defineitly be landscape
                    orientation = Configuration.ORIENTATION_LANDSCAPE;
                }
            }
        }
    }
    return orientation; // return value 1 is portrait and 2 is Landscape Mode
}

@Override
public void onResume() {
    super.onResume();
    Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
} 

4

У мене була така ж проблема, як отримати IllegalStateException, але заміна всіх моїх викликів на виконання () на commitAllowingStateLoss () не допомогла.

Винуватцем став дзвінок на DialogFragment.show ().

Я оточую його

try {
    dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
    return;
}

і це вдалося. Гаразд, я не можу показувати діалогове вікно, але в цьому випадку це було добре.

Це було єдине місце в моєму додатку, де я вперше зателефонував FragmentManager.beginTransaction (), але ніколи не викликав commit (), тому я не знайшов його, коли шукав "commit ()".

Найцікавіше, що користувач ніколи не залишає додаток. Натомість вбивцею було проміжне оголошення AdMob.


3
Те ж саме. Я вирішив переосмислити метод 'show (менеджер FragmentManager, тег String)', замінивши "commit" на "commitAllowingStateLoss"; щось втрачаю, тому що я не можу встановити два приватних атрибута діалогу: mDismissed та mShownByMe. Але це, здається, спрацьовує кожен раз :)
Франческо Дітрані

Я прийняв альтернативне рішення для DialogFragment, яке може уникнути цього винятку: github.com/AndroidDeveloperLB/DialogShard
андроїд розробник

4

Моє рішення цієї проблеми було

У фрагмент додайте методи:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
    guideMap = guideMapFragment.getMap();
    ...
}

@Override
public void onDestroyView() {
    SherlockFragmentActivity a = getSherlockActivity();
    if (a != null && guideMapFragment != null) {
        try {
            Log.i(LOGTAG, "Removing map fragment");
            a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
            guideMapFragment = null;
        } catch(IllegalStateException e) {
            Log.i(LOGTAG, "IllegalStateException on exit");
        }
    }
    super.onDestroyView();
}

Може бути погано, але нічого кращого не вдалося знайти.


Вважає, що вилучення виключення може уникнути аварії програми, але проблеми з поведінкою такі фрагменти залишаються на екрані або не додаються.
Маркос Васконцелос

4
продовжуйте прокручувати. правда там
аніль

4

У мене виникла ця проблема. Але я думаю, що ця проблема не пов’язана з програмою "віддавати" та "виконувати" AllowStateLoss

Наступне повідомлення про відстеження стека та виключення стосується commit ().

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

Але цей виняток спричинив OnBackPress ()

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)

Всі вони були викликані checkStateLoss ()

private void checkStateLoss() {
    if (mStateSaved) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }

mStateSaved буде істинним після onSaveInstanceState.

Ця проблема трапляється рідко. Я ніколи не стикався з цією проблемою. Я не можу повторити цю проблему.

Я знайшов випуск 25517

Це могло статися за таких обставин

  1. Клавіша "Назад" викликається після OnSaveInstanceState, але перед початком нової діяльності.

  2. використовувати onStop () у коді

Я не впевнений, у чому полягає корінь проблеми. Тому я використовував некрасивий спосіб.

@Override
public void onBackPressed() {

    try{
        super.onBackPressed();
    }catch (IllegalStateException e){
        // can output some information here
        finish();
    }
}

Я дійсно не вирішив проблему, але ця проблема не пов’язана з програмою "commit" і "commitAllowStateLoss".
oO_ox

4

У мене є те саме питання у моєму додатку. Я вирішив цю проблему, просто зателефонувавши super.onBackPressed();на попередній клас та зателефонувавши commitAllowingStateLoss()на поточний клас із цим фрагментом.


2
Дякую. Це рішення вирішило питання uisng commitAllowingStateLoss()замістьcommit()
Chintak Patel

Уникайте використання commitAllowingStateLoss () medium.com/@elye.project/…
swooby

3

onSaveInstance викликається, якщо користувач поверне екран, щоб він міг завантажувати ресурси, пов'язані з новою орієнтацією.

Можливо, що цей користувач повернув екран із наступним натисканням кнопки "назад" (оскільки також можливо, що цей користувач махнув їх телефоном під час використання вашого додатка)


2
Хоча зміни конфігурації (наприклад, зміни орієнтації) можуть призвести до цього винятку, вони не є першопричиною.
Алекс Локвуд


2

Те саме питання від мене, і після щоденного аналізу всіх статей, блогу та stackoverflow я знайшов просте рішення. Ніколи не використовуйте saveInstanceState, це умова з одним рядком коду. Про код фрагмента:

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

2

Це відбувається щоразу, коли ви намагаєтеся завантажити фрагмент, але активність змінила його стан на onPause (). Це відбувається, наприклад, коли ви намагаєтеся отримати дані та завантажити їх у діяльність, але до моменту, коли користувач натиснув якусь кнопку і перейшов до наступної діяльності.

Вирішити це можна двома способами

Ви можете використовуватиaction.commitAllowingStateLoss () замістьaction.commit () для завантаження фрагмента, але ви можете втратити операцію, що виконується.

або

Переконайтеся, що активність відновлюється, і не збирається призупиняти стан під час завантаження фрагмента. Створіть булевий і перевірте, чи активність не переходить у станPause ().

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

тоді під час завантаження фрагмента перевіряйте, чи активність присутня, і завантажуйте лише тоді, коли активність на передньому плані.

if(mIsResumed){
 //load the fragment
}

1

Дякую @gunar, але я думаю, що є кращий спосіб.

За даними док .:

 * If you are committing a single transaction that does not modify the
 * fragment back stack, strongly consider using
 * {@link FragmentTransaction#commitNow()} instead. This can help avoid
 * unwanted side effects when other code in your app has pending committed
 * transactions that expect different timing.
 *
 * @return Returns true if there were any pending transactions to be
 * executed.
 */
public abstract boolean executePendingTransactions();

Тож використовуйте commitNowдля заміни:

fragmentTransaction.commit();
FragmentManager.executePendingTransactions()

0

Ну, після спроби всіх перерахованих вище рішень без успіху (адже в основному я не маю транзакцій).

У моєму випадку я використовував AlertDialogs та ProgressDialog як фрагменти, які, іноді, під час обертання, коли запитували FragmentManager, помилка зростає.

Я знайшов вирішення змішування кількох подібних публікацій:

Це 3-ступінкове рішення, все зроблене на вашій FragmentActivity (в даному випадку вона називається GenericActivity):

private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    //To avoid bug for fragments: Step 2 of 3
    activity = new WeakReference<GenericActivity>(this);
}

@Override
public FragmentManager getSupportFragmentManager(){
    //To avoid bug for fragments: Step 3 of 3
    if (this == activity.get()) {
        return super.getSupportFragmentManager();
    }
    return activity.get().getSupportFragmentManager();
}

0

Коли я буду використовувати стартактивність в одному фрагменті, я отримаю цей виняток;

Коли я переходжу до використання startactivityforresult, виняток пропадає :)

Тож найпростіший спосіб виправити це - використовувати startActivityForResult api :)


0

Цей виняток я отримував, коли натискав кнопку "назад", щоб скасувати вибір вибору наміру на активності фрагмента карти. Я вирішив це, замінивши код onResume () (де я ініціалізував фрагмент і здійснив транзакцію) на onStart (), і додаток зараз працює нормально. Сподіваюся, це допомагає.


0

Це зафіксовано в Android 4.2, а також у джерелі бібліотеки підтримки. [*]

Докладніше про причину (і робочі місця) див. У звіті про помилку Google: http://code.google.com/p/android/isissue/detail?id=19917

Якщо ви використовуєте бібліотеку підтримки, вам не доведеться турбуватися про цю помилку (надовго) [*]. Однак якщо ви використовуєте API безпосередньо (тобто не використовуєте FragmentManager бібліотеки підтримки) і орієнтуєтесь на API нижче Android 4.2, вам потрібно буде спробувати один із робочих напрямків.

[*] На момент написання диспетчера Android SDK менеджер все ще поширює стару версію, в якій виявляється ця помилка.

Редагувати Я додам тут трохи пояснень, тому що я, очевидно, якимось чином переплутав того, хто внизу проголосував за цю відповідь.

Існує кілька різних (але пов'язаних з ними) обставин, які можуть спричинити викид цього винятку . Моя відповідь вище стосується конкретного екземпляра, про який йдеться у запитанні, тобто помилку в Android, яку згодом було виправлено. Якщо ви отримуєте це виняток з іншої причини, це тому, що ви додаєте / видаляєте фрагменти, коли цього не слід (після збереження фрагментів). Якщо ви перебуваєте в такій ситуації, то, можливо, вам можуть бути корисні "Вкладені фрагменти - IllegalStateException" Неможливо виконати цю дію після onSaveInstanceState " ".



0

Мій випадок використання: я використовував слухач у фрагменті, щоб сповістити активність про те, що якась річ сталася. Я зробив новий фрагмент фіксації за методом зворотного виклику. Це прекрасно працює в перший раз. Але при зміні орієнтації активність відтворюється із збереженим станом екземпляра. У цьому випадку фрагмент не створюється знову, це означає, що фрагмент має слухача, який є старою знищеною активністю. Будь-який спосіб викликає зворотний виклик на дію. Це переходить до знищеної діяльності, яка викликає проблему. Рішення полягає в тому, щоб скинути слухача в фрагменті з поточною активною активністю. Це вирішує проблему.


0

Що я з’ясував, це те, що якщо інша програма діалогового типу та дозволяє надсилати дотики до фонового додатку, то майже будь-який фоновий додаток вийде з ладу з цією помилкою. Я думаю, що нам потрібно перевіряти кожен раз, коли виконується транзакція, чи збережений або відновлений екземпляр.


0

У моєму випадку, за тим самим винятком помилок, я ставлю "onBackPress ()" у виконанні (ви можете використовувати будь-який свій погляд):

myView.post(new Runnable() {
                    @Override
                    public void run() {
                        onBackPressed()
                    }
                });

Не розумію чому, але це працює!


Публікація у поданні буде запущена лише після того, як подання буде належним чином викладено та виведено на екран; це часто означає, що сама діяльність повністю відновилася, тому немає жодних питань
Mercato

0

Ви можете викликати fragmentManager.popBackStackImmediate (); коли діяльність призупинена. Діяльність не закінчена, але призупинена і не на передньому плані. Потрібно перевірити, призупинено чи ні перед popBackStackImmediate ().


0

Я помітив щось дуже цікаве. У своєму додатку є можливість відкрити галерею телефону, і пристрій запитує, який додаток використовувати, там я клацну по сірій області від діалогового вікна і побачив цю проблему. Я помітив, як моя діяльність переходить від onPause, onSaveInstanceState назад до onResume, не буває, щоб відвідувати onCreateView. Я роблю транзакції в onResume. Тож, що я в кінцевому підсумку робив, - це встановити прапор, який буде відменено на Pauuse, але є істинним onCreateView. якщо прапор є істинним onResume, тоді зробіть onCommit, інакше присвойтеAllowingStateLoss. Я міг би продовжувати і витрачати стільки часу, але хотів перевірити життєвий цикл. У мене є пристрій sdkversion 23, і я не отримую цього питання, але у мене є ще один, який 21, і там я його бачу.


-1

ви можете використовувати FragmentActivity.onStart перед popBackStackImmediate

подобається це:

public void backStackFragment() {
    this.start();
    getFragmentManager().popBackStackImmediate();
}

public void start(){
    FragmentActivity a = getActivity();
    if(a instanceof DepositPlanPadActivity){
      ((DepositPlanPadActivity)a).onStart();
    }
    if(a instanceof SmallChangePlanPad){
            ((SmallChangePlanPad)a).onStart();
        }
        if(a instanceof UserCenterActivity){
            ((UserCenterActivity)a).onStart();
        }
    }

http://jorryliu.blogspot.com/2014/09/illegalstateexception-can-not-perform.html

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.