解決 IllegalStateException: Can not perform this action after onSaveInstanceState

今天在修復外網崩潰時,發現有這個錯誤 IllegalStateException: Can not perform this action after onSaveInstanceState,詳細堆棧信息以下:java

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager:1377) at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager:504) at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity:178) at android.app.Activity.onKeyUp(Activity.java:2207) at android.view.KeyEvent.dispatch(KeyEvent.java:2664) at android.app.Activity.dispatchKeyEvent(Activity.java:2437) at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1975) at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:4013) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3987) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3553) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3603) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3572) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3679) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3580) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3736) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3553) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3603) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3572) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3580) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3553) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3603) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3572) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3712) at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:3877) at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2027) at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:1721) at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:1712) at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2004) at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:138) at android.os.Looper.loop(Looper.java:123) at android.app.ActivityThread.main(ActivityThread.java:5028) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:788) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:604) at dalvik.system.NativeStart.main(Native Method) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 經過堆棧信息能夠看出,異常是發生在onBackPressed時,發現onSaveInstanceState 方法被調用了。瞭解異常如何產生,首先必須弄明白onSaveInstanceState方法的調用時機,onSaveInstanceState api 介紹以下: onSaveInstanceState apiandroid

歸納的講,onSaveInstanceState 這個方法會在activity 將要被kill以前被調用以保存每一個實例的狀態,以保證在未來的某個時刻回來時能夠恢復到原來的狀態,但和activity 的生命週期方法onStop 和 onPause 不同,與二者並無絕對的前後調用順序,或者說並不是全部場景都會調用onSaveInstanceState 方法。那麼onSaveInstanceState 方法什麼時候會被調用呢,或者這麼問,何時activity 會被系統kill 掉呢?有如下幾種比較常見的場景: (1)用戶主動按下home 鍵,系統不能確認activity 是否會被銷燬,實際上此刻系統也沒法預測未來的場景,好比說內存佔用,應用運行狀況等,因此係統會調用onSaveInstanceState保存activity狀態 ; (2)activity位於前臺,按下電源鍵,直接鎖屏; (3)橫豎屏切換; (4)activity B啓動後位於activity A以前,在某個時刻activity A由於系統回收資源的問題要被kill掉,A經過onSaveInstanceState保存狀態。api

那麼,爲何會拋出異常呢?緣由在於咱們的activity在某種場景下處於被kill 掉的邊緣,系統就調用了onSaveInstanceState 方法,這個方法裏面會調用 FragmentManager saveAllState 方法,將fragment 的狀態保存,在狀態保存後用戶又主動調了 onBackPressed ,而這個方法的超類super.onBackPressed 方法會判斷FragmentManager 是否保存了狀態,若是已經保存就會拋出IllegalStateException 的異常 。app

此外,還有一個比較相似的異常堆棧信息:異步

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) 1 2 3 4 5 這一類異常緣由是在activity 調用onSaveInstanceState後,調用了fragmenttransaction 的commit 方法所致,若是說onBackPressed 的異常是出如今用戶按back 後,那麼在何處調用commit會致使IllegalStateException異常呢?實際上在api 11 (Honeycomb)以前,若是onSaveInstanceState 方法被調用,那麼確定是在onPause 生命週期方法前,但api11 之後,卻只能保證在onStop 生命週期方法前,和onPause 方法並無明確的前後調用順序,正是因爲此處生命週期的微小變化,致使api11 後,若是在onPause 和 onStop 之間調用commit ,將有可能拋出一個IllegalStateException異常告知狀態丟失。 關於這類崩潰問題能夠參考:@AlexLockwood ‘s blog : Fragment Transactions & Activity State Losside

最後,談談如何避免這一類的崩潰問題。 一、關於commit 方法的調用異常處理方法 (1)在activity生命週期函數內謹慎使用commit 方法 ,通常狀況下若是能在onCreate 中調用,基本不會出現問題,可是若是在onResume onStart 等方法中調用就須要格外注意,好比說FragmetActivity 的onResume 方法 ,在某些場景下onResume 方法被調用以前,可能依然保存着以前的狀態致使異常 。函數

Dispatch onResume() to fragments. Note that for better inter-operation with older versions of the platform, at the point of this call the fragments attached to the activity are not resumed. This means that in some cases the previous state may still be saved, not allowing fragment transactions that modify the state. 1 2 3 (2)儘量避免在一些和生命週期函數異步的方法中調用commit,如AsyncTask 等。 (3)實在無法肯定調用時機時,能夠用commitAllowingStateLoss 代替 commit ,commitAllowingStateLoss 在狀態丟失時不會拋出任何異常,但也正由於如此在一些必須確保狀態被保存的場合,最好不要使用 commitAllowingStateLoss 方法。oop

二、針對onbackpress 致使的異常,也有幾種解決手段 (1)在api11 以上 不調用super 的onSaveInstanceState 方法ui

protected void onSaveInstanceState(Bundle outState) { //No call for super(). Bug on API Level > 11. } 1 2 3 這樣作的後果會致使activity 和 fragment 的全部狀態,在activity 被系統殺死掉後沒法保存,因此若是有保存狀態的須要,這個方法是不適用的。this

(2)重寫 onBackPressed 方法,不調用super.onBackPressed ,直接調用finish 。緣由很簡單,super.onBackPressed 裏面會調用FragmentManager popBackStackImmediate() 方法,若是直接掉finish 就不會觸發異常,但這種狀況只建議在沒有使用 Fragments api時調用。

(3)經過反射手段在onSaveInstanceState 方法裏調用 FragmentManagerImpl noteStateNotSaved方法將 mStateSaved 變量置爲false ,這樣既不會致使activity狀態丟失,也能確保退出時不會拋出異常,算是比較優雅的處理途徑,代碼以下:

private Method noteStateNotSavedMethod;
private Object fragmentMgr;
private String[] activityClassName = {"Activity", "FragmentActivity"};

[@Override](https://my.oschina.net/u/1162528)
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    invokeFragmentManagerNoteStateNotSaved();
}

private void invokeFragmentManagerNoteStateNotSaved() {
    //java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        return;
    }
    try {
        if (noteStateNotSavedMethod != null && fragmentMgr != null) {
            noteStateNotSavedMethod.invoke(fragmentMgr);                
            return;
        }
        Class cls = getClass();
        do {
            cls = cls.getSuperclass();
        } while (!(activityClassName[0].equals(cls.getSimpleName())
                || activityClassName[1].equals(cls.getSimpleName())));

        Field fragmentMgrField = prepareField(cls, "mFragments");
        if (fragmentMgrField != null) {
            fragmentMgr = fragmentMgrField.get(this);
            noteStateNotSavedMethod = getDeclaredMethod(fragmentMgr, "noteStateNotSaved");
            if (noteStateNotSavedMethod != null) {
                noteStateNotSavedMethod.invoke(fragmentMgr);                    
            }
        }

    } catch (Exception ex) {            
    }
}

private Field prepareField(Class<?> c, String fieldName) throws NoSuchFieldException {
    while (c != null) {
        try {
            Field f = c.getDeclaredField(fieldName);
            f.setAccessible(true);
            return f;
        } finally {
            c = c.getSuperclass();
        }
    }
    throw new NoSuchFieldException();
}

private Method getDeclaredMethod(Object object, String methodName, Class<?>... parameterTypes) {
    Method method = null;
    for (Class<?> clazz = object.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
        try {
            method = clazz.getDeclaredMethod(methodName, parameterTypes);
            return method;
        } catch (Exception e) {
        }
    }
    return null;
}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 到這裏已經介紹完了,若是對於文章內容有興趣的話,能夠多多交流。

相關文章
相關標籤/搜索