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之前都是不会出问题的,哦了。