java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

1. 场景:

  在开发过程中遇到这么一个需要,在主页点击按钮进入另一个Activity(ReadActivity),在该ReadActivity中点击一个按钮再返回主页并指定主页选中特定的Tab.主页是用FragmentTabHost + Fragment 实现。思路是通过startActivityForResult以及setResult() 以及requestCode作为标志位,是ReadActivity返回,因为还有其他的requestCode。再通过

FragmentTabHost的setCurrentTab(int index)方法切换Tab,我把该方法放置在了startActivityForResult()中,出现该问题java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState。

2.为什么?

  查阅源码,进入到FragmentManager的源码找到这么一段解释:

/**
     * Start a series of edit operations on the Fragments associated with
     * this FragmentManager.
     * 
     * <p>Note: A fragment transaction can only be created/committed prior
     * to an activity saving its state.  If you try to commit a transaction
     * after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()}
     * (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart}
     * or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error.
     * This is because the framework takes care of saving your current fragments
     * in the state, and if changes are made after the state is saved then they
     * will be lost.</p>
     */
    public abstract FragmentTransaction beginTransaction();

大意是:一个fragment事务只能在该依附的Activity正在保存状态时创建(created)或者提交(committed ),如果在调用onSaveInstanceState()之后再提交事务,就会导致问题发生—— Can not perform this action after onSaveInstanceState 。

3. 发生的时机:

  先看FragmentManager中这么一段代码(只展示相关代码):(static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;

 

Parcelable saveAllState() {
        // Make sure all pending operations have now been executed to get
        // our state update-to-date.
        execPendingActions();

        if (HONEYCOMB) {
            // As of Honeycomb, we save state after pausing.  Prior to that
            // it is before pausing.  With fragments this is an issue, since
            // there are many things you may do after pausing but before
            // stopping that change the fragment state.  For those older
            // devices, we will not at this point say that we have saved
            // the state, so we will allow them to continue doing fragment
            // transactions.  This retains the same semantics as Honeycomb,
            // though you do have the risk of losing the very most recent state
            // if the process is killed...  we'll live with that.
            mStateSaved = true;
        }
}

 

只看if中的注释:HONEYCOMB -> 大于等于Android3.0 版本的系统,Activity在不可见状态(pausing)后保存其状态,置mStateSaved状态为true。对于3.0更早的设备,则允许在不可见状态后执行fragment事务,所以保存状态就是不确定的了,但是肯定会在stop之前保存状态。

 

  mStateSaved(布尔值)标志是否保存状态:

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);
        }
    }
static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;

4. 思路整理:

  
从主页跳转到ReadActivity,MainActivity(首页)已不可见并且保存其状态,此时已经不能再对fragment执行commit操作。而FragmentTabHost的setCurrentTab(int index)中
调用了commit方法,导致问题出现。setCurrentTab() -> invokeOnTabChangeListener() -> onTabChanged() -> ft.commit(); 一目了然!!!
 1 public void setCurrentTab(int index) {
 2         if (index < 0 || index >= mTabSpecs.size()) {
 3             return;
 4         }
 5 
 6         if (index == mCurrentTab) {
 7             return;
 8         }
 9 
10         // notify old tab content
11         if (mCurrentTab != -1) {
12             mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
13         }
14 
15         mCurrentTab = index;
16         final TabHost.TabSpec spec = mTabSpecs.get(index);
17 
18         // Call the tab widget's focusCurrentTab(), instead of just
19         // selecting the tab.
20         mTabWidget.focusCurrentTab(mCurrentTab);
21 
22         // tab content
23         mCurrentView = spec.mContentStrategy.getContentView();
24 
25         if (mCurrentView.getParent() == null) {
26             mTabContent
27                     .addView(
28                             mCurrentView,
29                             new ViewGroup.LayoutParams(
30                                     ViewGroup.LayoutParams.MATCH_PARENT,
31                                     ViewGroup.LayoutParams.MATCH_PARENT));
32         }
33 
34         if (!mTabWidget.hasFocus()) {
35             // if the tab widget didn't take focus (likely because we're in touch mode)
36             // give the current tab content view a shot
37             mCurrentView.requestFocus();
38         }
39 
40         //mTabContent.requestFocus(View.FOCUS_FORWARD);
41         invokeOnTabChangeListener();
42     }
43 
44     /**
45      * Register a callback to be invoked when the selected state of any of the items
46      * in this list changes
47      * @param l
48      * The callback that will run
49      */
50     public void setOnTabChangedListener(OnTabChangeListener l) {
51         mOnTabChangeListener = l;
52     }
53 
54     private void invokeOnTabChangeListener() {
55         if (mOnTabChangeListener != null) {
56             mOnTabChangeListener.onTabChanged(getCurrentTabTag());
57         }
58     }
59 
60 @Override
61     public void onTabChanged(String tabId) {
62         if (mAttached) {
63             FragmentTransaction ft = doTabChanged(tabId, null);
64             if (ft != null) {
65                 ft.commit();
66             }
67         }
68         if (mOnTabChangeListener != null) {
69             mOnTabChangeListener.onTabChanged(tabId);
70         }
71     }

5. 解决:

  知道问题所在这就好说了,只要在Activity未保存状态之前执行setCurrentTab()方法,其关键就是执行commit方法。要么在当前未保存状态之前,要么在Activity再次可见并未onPause之前都是不会出问题的,哦了。

 
posted @ 2017-08-24 17:09  Spiderman.L  阅读(1808)  评论(0编辑  收藏  举报