ViewPager with Fragments 引發的崩潰和一些問題

參考:java

  1. android 中的狀態保存與恢復流程
  2. 對Fragment的狀態保存恢復機制原理的分析android

  3. Fragment 之addToBackStack的理解

 

崩潰1: 數組

throw new IllegalStateException("Fragment does not have a view");

崩潰分析:app

  • 從崩潰棧咱們能夠發現這個崩潰是在Fragment,initChildFragmentManager 中的回掉中發生的。(mContainer.onFindViewById 有兩個實現,一個是在FragmnetActivity 中,這個不會致使這個崩潰。)
  • 崩潰時機:ParentFragment 尚未調用 onCreateView 的時候。

崩潰代碼調用位置:(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 的 保存與恢復函數

  • child fragment 的狀態保存時機:
    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);
            }
        }
    }
  • child fragment 的恢復時機:
  • Fragment.java
    @CallSuper
    public void onCreate(@Nullable Bundle savedInstanceState) {
        mCalled = true;
        restoreChildFragmentState(savedInstanceState);
        if (mChildFragmentManager != null
                && !mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) {
            mChildFragmentManager.dispatchCreate();
        }
    }
  • Fragment 狀態恢復的時機:
  • FragmentActivity.onCreate();
    ...
    mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
    ...
    mFragments.dispatchCreate();
  • Fragment onCreate 調用的時機:
  • (mv to sate : )Fragment.INITIALIZING in FragmentManager
  • Fragment.CRREATED: 狀態執行,是FragmentManager add fragment 後經過commit 觸發, 或者在界面恢復的時候經過mFragmentManager.dispatchCreate();觸發。

崩潰緣由:ui

  • Parante Fragment 在界面恢復的時候,也恢復了childFragments。
  • childFragment 在恢復的時候,進入了  fragment crated 狀態。崩潰在childFragment 中。
  • Parent Fragment 的initChildFragmentManager在Parent 的onCreateView 以前被調用到。從而致使child fragment 去查找本身的容器添加本身視圖的時候,崩潰。

解決方案: (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)

崩潰緣由:

  • 界面恢復。恢復保存的Fragment。全部的一級fragemnt在恢復的時候,若是有child fragment ,也會恢復。
  • FragmentMangerImpl.mActive is null ; 在ParentFragment恢復childFragmentManager的時候,其中activie 爲空。
  • ParentFragment 已經OnViewCreated了, 而且在開始執行f.restoreViewSate.
  • 使用了FragmentStatePagerAdapter(getSurpportedFragmentManager()) , 在銷燬的時候,會保存當前pager fragment 中的狀態。
  • 當界面從新建立的時候,會愛OnCreate 的時候,恢復Fragments 。當從新復這個ViewPager 的時候,恢復數據致使崩潰。

崩潰代碼位置:

解決辦法: 使用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.  相同點: 構造函數都須要傳入FragmentManager。 都在內部維護了fragments 列表信息。
  2. 不一樣點:FragmentStatePagerAdapter 會在destroy 的時候saveState 。 執行remove 。而FragmentPagerAdapter 執行detach。

 

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。

  • 恢復Fragment 的時機: FragmentActivity OnCreate 的時候,先調用super 。而後再使用FragmentController 執行Fragment 的恢復工做。會前後恢復active ,added , backStack 中的數據。

6, fragment 的addToBackStack 操做,只添加了transaction 事件到棧中。 在pop 的時候,執行對應的逆操做。所以界面中在執行某些添加與某些不添加到棧中這樣的操做的時候,就極可能致使問題。參考文章3;

相關文章
相關標籤/搜索