參考:java
崩潰1: 數組
throw new IllegalStateException("Fragment does not have a view");
崩潰分析:app
崩潰代碼調用位置:(state : fragment crated :)container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);框架
case Fragment.CREATED: // This is outside the if statement below on purpose; we want this to run // even if we do a moveToState from CREATED => *, CREATED => CREATED, and // * => CREATED as part of the case fallthrough above. ensureInflatedFragmentView(f); if (newState > Fragment.CREATED) { if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { if (f.mContainerId == View.NO_ID) { throwException(new IllegalArgumentException( "Cannot create fragment " + f + " for a container view with no id")); } container = (ViewGroup) mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { String resName; try { resName = f.getResources().getResourceName(f.mContainerId); } catch (NotFoundException e) { resName = "unknown"; } throwException(new IllegalArgumentException( "No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + resName + ") for fragment " + f)); } } f.mContainer = container; f.mView = f.performCreateView(f.performGetLayoutInflater( f.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView != null) { f.mInnerView = f.mView; f.mView.setSaveFromParentEnabled(false); if (container != null) { container.addView(f.mView); } if (f.mHidden) { f.mView.setVisibility(View.GONE); } f.onViewCreated(f.mView, f.mSavedFragmentState); dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false); // Only animate the view if it is visible. This is done after // dispatchOnFragmentViewCreated in case visibility is changed f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE) && f.mContainer != null; } else { f.mInnerView = null; } } f.performActivityCreated(f.mSavedFragmentState); dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false); if (f.mView != null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; } // fall through
界面構成信息: Activity 中存在Fragment , Fragment 中使用了ViewPager , ViedwPager 中嵌套了ViewPager with fragmnets ;ide
相關知識:關於Fragment state 的 保存與恢復:函數
Fragment.java called in FragmentManager. when performSaveInstanceState;
void performSaveInstanceState(Bundle outState) { onSaveInstanceState(outState); if (mChildFragmentManager != null) { Parcelable p = mChildFragmentManager.saveAllState(); if (p != null) { outState.putParcelable(FragmentActivity.FRAGMENTS_TAG, p); } } }
Fragment.java @CallSuper public void onCreate(@Nullable Bundle savedInstanceState) { mCalled = true; restoreChildFragmentState(savedInstanceState); if (mChildFragmentManager != null && !mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) { mChildFragmentManager.dispatchCreate(); } }
FragmentActivity.onCreate(); ...
mFragments.restoreAllState(p, nc != null ? nc.fragments : null); ...
mFragments.dispatchCreate();
(mv to sate : )Fragment.INITIALIZING in FragmentManager
崩潰緣由:ui
解決方案: (work around)this
在ParentFragment onCreate 的時候, 手動移除全部的child fragment;spa
崩潰場景2: 當爲程序設置不保留後臺活動的狀況下。 從新回到 ViewPager + fragments 的界面中程序崩潰。
崩潰日誌:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.util.SparseArray.get(int)' on a null object reference at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:902) at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:216) at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1453) at android.view.View.dispatchRestoreInstanceState(View.java:15784) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3274) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3282) at android.view.View.restoreHierarchyState(View.java:15762) at android.support.v4.app.Fragment.restoreViewState(Fragment.java:510) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1445) at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809) at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799) at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580) at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367) at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:700) at android.os.Handler.handleCallback(Handler.java:761)
界面構成信息: Activity 中存在Fragment , Fragment 中使用了ViewPager , ViedwPager 中嵌套了ViewPager with fragmnets 。而FragmentViewPager 的Adapter 則使用了StateFragmentAdapter。
public Fragment getFragment(Bundle bundle, String key) { int index = bundle.getInt(key, -1); if (index == -1) { return null; } else { Fragment f = (Fragment)this.mActive.get(index); if (f == null) { this.throwException(new IllegalStateException("Fragment no longer exists for key " + key + ": index " + index)); } return f; } }
mActive的恢復代碼:在FragmentManager中。對Activity 的FRM 在ActivityOnCreate 的時候就調用恢復了。對於childFragmentManager。在Fragment的onCreate中調用mChildFragmentManager.restoreAllState(p, mChildNonConfig);
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) { // If there is no saved state at all, then there can not be // any nonConfig fragments either, so that is that. if (state == null) return; FragmentManagerState fms = (FragmentManagerState)state; if (fms.mActive == null) return; List<FragmentManagerNonConfig> childNonConfigs = null; // First re-attach any non-config instances we are retaining back // to their saved state, so we don't try to instantiate them again. ...... // Build the full list of active fragments, instantiating them from // their saved state. mActive = new SparseArray<>(fms.mActive.length); for (int i=0; i<fms.mActive.length; i++) { FragmentState fs = fms.mActive[i]; if (fs != null) { FragmentManagerNonConfig childNonConfig = null; if (childNonConfigs != null && i < childNonConfigs.size()) { childNonConfig = childNonConfigs.get(i); } Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig); if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f); mActive.put(f.mIndex, f); // Now that the fragment is instantiated (or came from being // retained above), clear mInstance in case we end up re-restoring // from this FragmentState again. fs.mInstance = null; } } // Update the target of all retained fragments. ... // Build the list of currently added fragments. mAdded.clear(); if (fms.mAdded != null) { for (int i=0; i<fms.mAdded.length; i++) { Fragment f = mActive.get(fms.mAdded[i]); if (f == null) { throwException(new IllegalStateException( "No instantiated fragment for index #" + fms.mAdded[i])); } f.mAdded = true; if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f); if (mAdded.contains(f)) { throw new IllegalStateException("Already added!"); } synchronized (mAdded) { mAdded.add(f); } } } .... this.mNextFragmentIndex = fms.mNextFragmentIndex; }
崩潰分析:由下面兩行能夠肯定是在Fragment ,case Fragment.CREATED, 執行視圖恢復的時候崩潰的。
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:510) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1445)
崩潰緣由:
崩潰代碼位置:
解決辦法: 使用FragmentPagerAdapter 替代FragmentStatePagerAdapter。 FragmentPagerAdapter 不會保留狀態。可是又引起了刷新問題。
後續問題: 頁面不刷新。換成FragmentStatePagerAdapter,是能夠刷新的。
問題緣由: 歷史代碼中對adatper 設置新的fragment 數組後,再要求刷新。由於使用的是PagerFragmentAdapter。 在切換頁面的時候,使用的是detach方式。而fragmentTag 又和 posotion 相關,致使notify 後,fragment 其實使用的是以前的fragment。(Fragment 複用了) 所以界面不刷新。
解決方案:自定義新的Fragment Pager Adapter。 在destroy view 的時候,採用 remove 而不是 detach 方式。固然這樣作存在效率問題。
2, 知識點: FragmentStatePagerAdapter 與 FragmentPagerAdapter 的區別:
1) FragmentStatePagerAdapter : destroy 的時候, 先移除全部的adapter 而且保存狀態。執行的是remove 在initiate 的時候,執行的addFragment 操做。
instantiateItem:的時候
Fragment fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); if (mSavedState.size() > position) { Fragment.SavedState fss = mSavedState.get(position); if (fss != null) { fragment.setInitialSavedState(fss); } } while (mFragments.size() <= position) { mFragments.add(null); } fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); mFragments.set(position, fragment); mCurTransaction.add(container.getId(), fragment);
destroy 的時候:
@Override public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment) object; if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, fragment.isAdded() ? mFragmentManager.saveFragmentInstanceState(fragment) : null); mFragments.set(position, null); mCurTransaction.remove(fragment); }
而 FragmentPagerAdapter 執行的只是detach attach 操做。 所以界面是不會刷新的(與Adapter 的具體實現相關)。
@Override public void destroyItem(ViewGroup container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment)object).getView()); mCurTransaction.detach((Fragment)object); }
3, commitNowAllowLossState 與 commitAllowLossState區別。
commitNowAllowLossState 若是前面調用了addToBackStack 就會致使崩潰。見源碼。
4, onSaveInstanceState (View 中也有這個方法,用於保存視圖狀態) onRestoreInstanceState.
調用順序: onSaveInstanceState 未必會調用。 先調用onSaveInstanceState 在onStop 前執行。
若是咱們沒有覆寫onSaveInstanceState()方法, 此方法的默認實現會自動保存activity中的某些狀態數據, 好比activity中各類UI控件的狀態.。android應用框架中定義的幾乎全部UI控件都恰當的實現了onSaveInstanceState()方法,所以當activity被摧毀和重建時, 這些UI控件會自動保存和恢復狀態數據. 好比EditText控件會自動保存和恢復輸入的數據,而CheckBox控件會自動保存和恢復選中狀態.開發者只須要爲這些控件指定一個惟一的ID(經過設置android:id屬性便可), 剩餘的事情就能夠自動完成了.若是沒有爲控件指定ID, 則這個控件就不會進行自動的數據保存和恢復操做。
5, fragment 恢復的時機:
當Activity 被之外銷燬後,系統會保留當前界面中的Fragment 數據。 在Activity 被系統從新創建的時候, 重新恢復Activity。
6, fragment 的addToBackStack 操做,只添加了transaction 事件到棧中。 在pop 的時候,執行對應的逆操做。所以界面中在執行某些添加與某些不添加到棧中這樣的操做的時候,就極可能致使問題。參考文章3;