解決 IllegalStateException: Can not perform this action after onSaveInstanceState

轉載請標明出處:http://blog.csdn.net/EdisonChang/article/details/49873669
今天在修復外網崩潰時,發現有這個錯誤 IllegalStateException: Can not perform this action after onSaveInstanceState。

異常是發生在onBackPressed時,發現onSaveInstanceState 方法被調用了。瞭解異常如何產生,首先必須弄明白onSaveInstanceState方法的調用時機,onSaveInstanceState api 介紹如下:
onSaveInstanceState api

概括的講,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保存狀態。

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

還有其他情況會導致這種異常,比如在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 Loss

最後,談談如何避免這一類的崩潰問題。
1、關於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. 

(2)儘可能避免在一些和生命週期函數異步的方法中調用commit,如AsyncTask 等。
(3)實在沒法確定調用時機時,可以用commitAllowingStateLoss 代替 commit ,commitAllowingStateLoss 在狀態丟失時不會拋出任何異常,但也正因爲如此在一些必須確保狀態被保存的場合,最好不要使用 commitAllowingStateLoss 方法。

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

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

這樣做的後果會導致activity 和 fragment 的所有狀態,在activity 被系統殺死掉後無法保存,所以如果有保存狀態的需要,這個方法是不適用的。

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

到這裏已經介紹完了,如果對於文章內容有興趣的話,可以多多交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章