標籤:getc current resume ack framework roi err been form
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 content11 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 just19 // selecting the tab.20 mTabWidget.focusCurrentTab(mCurrentTab);21 22 // tab content23 mCurrentView = spec.mContentStrategy.getContentView();24 25 if (mCurrentView.getParent() == null) {26 mTabContent27 .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 shot37 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 items46 * in this list changes47 * @param l48 * The callback that will run49 */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 @Override61 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之前都是不會出問題的,哦了。
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState