Android development often uses the ViewPager+Fragment+Adapter scenario. Generally, each Fragment controls its own refresh, but what if you want to refresh the entire ViewPager? Or if you want to rebuild the cached Fragent, how do you do that? Before met a problem when I do business, ViewPage in second setAdapter if using FragmentPager will not lead to a page refresh, but using FragementStatePagerAdapter will refresh? Couldn’t help but be curious, with the tracking of part of the source code, simple arrangement is as follows:

ViewPager+FragmentPagerAdapter Why can’t setAdapter refresh the entire page

The second time you set up the PagerAdapter, you clean up the original Fragment and then call populate() to rebuild it.

public void setAdapter(PagerAdapter adapter) { if (mAdapter ! = null) { ... for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems.get(i); <! Destroy --> madapter.destroyItem (this, ii.position, ii.object); } mAdapter.finishUpdate(this); <! --> mitems.clear (); removeNonDecorViews(); <! MCurItem = 0; scrollTo(0, 0); }... if (! wasFirstLayout) { <! -- Rebuild Fragment--> populate(); }... }Copy the code

As mentioned earlier, the second time you set the FragmentAdapter of a ViewPager using a setAdapter it doesn’t refresh immediately, but if you swipe back a few screens it does, right? The Fragent cached by the FragmentManager will not be created or refreshed on the second setAdapter because the FragmentAdapter calls Destroy using detach. No Fragment is actually destroyed, just the View is intended to be destroyed. This will result in the FragmentManager still having a cache of positive fragments:

@Override public void destroyItem(ViewGroup container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } // just detach mCurTransaction. Detach ((Fragment)object); }Copy the code

Detach will eventually call the detachFragment function of FragmentManager to detach the Fragment from the current Activity

public void detachFragment(Fragment fragment, int transition, int transitionStyle) { if (! fragment.mDetached) { <! Detach --> fragment. MDetached = true; if (fragment.mAdded) { <! If added is removed from the added list --> if (mAdded! = null) { mAdded.remove(fragment); }... fragment.mAdded = false; <! Create --> moveToState(Fragment, fragment.created, transition, transitionStyle, false); }}}Copy the code

FragmentManager does not call makeInactive to clean up fragment. CREATED fragments.

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { ... case Fragment.CREATED: if (newState < Fragment.CREATED) { ... if (! keepActive) { if (! f.mRetaining) { makeInactive(f); } else { f.mActivity = null; f.mParentFragment = null; f.mFragmentManager = null; }...Copy the code

Because only makeInactive cleans references to fragments like this:

void makeInactive(Fragment f) { if (f.mIndex < 0) { return; } <! Mactive.set (f.index, null) -- null mactive.set (f.index, null); if (mAvailIndices == null) { mAvailIndices = new ArrayList<Integer>(); } mAvailIndices.add(f.mIndex); mActivity.invalidateFragment(f.mWho); f.initState(); }Copy the code

As you can see, the Fragment cache remains in the FragmentManager. Once the new FragmentPagerAdapter is set, it will retrieve the Fragment via the instantiateItem function, which will first retrieve the Fragment from the FragmentManager’s cache. The retrieved Fragment is actually the Fragment that was not destroyed before, which is why it is not refreshed:

@Override public Object instantiateItem(ViewGroup container, int position) { <! - create a new transaction - > if (mCurTransaction = = null) {mCurTransaction = mFragmentManager. BeginTransaction (); } final long itemId = getItemId(position); <! Name --> String name = makeFragmentName(container.getid (), itemId); <! - according to the name lookup cache fragments in the Activity of FragmentManager - > fragments fragments = mFragmentManager. FindFragmentByTag (name); <! -- Use the current Fragment if you find it --> if (Fragment! = null) { mCurTransaction.attach(fragment); } else { <! --> fragment = getItem(position); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment ! = mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; }Copy the code

From the code above you can see, in the new Fragment object, the first is through mFragmentManager. FindFragmentByTag (name); Check whether there is already a Fragment cache. On the second setting of the Adapter, part of the Fragment has already been added to the FragmentManager cache. New Adapter can still through mFragmentManager. FindFragmentByTag find cache fragments (name), to prevent fragments new, so there will be no overall effect of refresh. So what if you want to refresh as a whole? Can use FragementStatePagerAdapter, both for the Fragment cache management is different.

ViewPager + FragementStatePagerAdapter can be done through setAdapter overall refresh

Also see FragementStatePagerAdapter destroyItem function first, when FragementStatePagerAdapter destroyItem used as a way to remove, This destroys not only the view but also the Fragment for operations that are not added to the rollback stack.

@Override public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); <! - FragementStatePagerAdapter to clean up their own cache - > mFragments. Set (position, null); <! Remove (fragment) --> mCurTransaction. Remove (fragment); }Copy the code

Visible FragementStatePagerAdapter will first through mFragments. Set (position, null) to clean up their own cache, and then, through the Transaction. Remove clear the cache in FragmentManager, Transaction.remove will eventually call the removeFragment function of FragmentManager:

public void removeFragment(Fragment fragment, int transition, int transitionStyle) { <! Final Boolean inactive =! --> final Boolean inactive =! fragment.isInBackStack(); if (! fragment.mDetached || inactive) { if (mAdded ! = null) { mAdded.remove(fragment); }... fragment.mAdded = false; fragment.mRemoving = true; <! -- Set the state to fragment. CREATED or fragment. INITIALIZING--> moveToState(Fragment, inactive? Fragment.INITIALIZING : Fragment.CREATED, transition, transitionStyle, false); }}Copy the code

Fragments of FragementStatePagerAdapter when adding addToBackStack, so moveToState sets the status to fragments. The INITIALIZING,

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { ... case Fragment.CREATED: if (newState < Fragment.CREATED) { ... if (! keepActive) { if (! f.mRetaining) { makeInactive(f); } else { f.mActivity = null; f.mParentFragment = null; f.mFragmentManager = null; }...Copy the code

Fragment.INITIALIZING < fragment. CREATED, where the function makeInactive is called to clean up references to the Fragment, actually destroying the Fragment’s cache in the FragmentManager.

ViewPager through the populate so again when new FragementStatePagerAdapter instantiateItem will new fragments, because before the Fragment has been cleared, If the Fragment cannot be found in its own Fragment cache list, create a new one. Look at the following code:

@Override public Object instantiateItem(ViewGroup container, int position) { <! - to see if the FragementStatePagerAdapter have cache fragments, Fragments.size() > position) {Fragment f = mfragments.get (position); Fragment f = mfragments.get (position); if (f ! = null) { return f; }}... <! Key points - If the FragementStatePagerAdapter find, new directly, don't care about whether there is -- -- > in the FragmentManager fragments fragments = the getItem (position); <! --> if (msavedstate.size () > position) {fragment.savedState FSS = msavedState.get (position); if (fss ! = null) { fragment.setInitialSavedState(fss); }}... mFragments.set(position, fragment); mCurTransaction.add(container.getId(), fragment); return fragment; }Copy the code

Can be seen from the above code also, FragementStatePagerAdapter when new fragments, won’t go to fetch in FragmentMangerImpl, but FragementStatePagerAdapter directly in the cache, if less than, Directly new fragments, if through setting a new FragementStatePagerAdapter setAdapter, all will be new fragments, can achieve the result of whole refresh.

How does FragmentPagerAdapter refresh ViewPager with notifyDataSetChanged

When the data in the FragmentPagerAdapter changes, it is often necessary to re-set the data to the Fragment or create a new Fragment. For ViewPager that uses the FragmentPagerAdapter, It is not enough to just use the notifyDataSetChanged; tracing the source code shows that the notifyDataSetChanged eventually calls the dataSetChanged in ViewPager:

void dataSetChanged() { ... for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems.get(i); final int newPos = mAdapter.getItemPosition(ii.object); if (newPos == PagerAdapter.POSITION_UNCHANGED) { continue; } if (newPos == PagerAdapter.POSITION_NONE) { mItems.remove(i); i--; . mAdapter.destroyItem(this, ii.position, ii.object); needPopulate = true; . continue; }... if (needPopulate) { final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (! lp.isDecor) { lp.widthFactor = 0.f; } } setCurrentItemInternal(newCurrItem, false, true); requestLayout(); }}Copy the code

By default, getItemPosition in FragmentPagerAdapter returns pagerAdapter.position_Unchanged, so no destroyItem is used. Even after setting pagerAdapter.position_None and calling destroyItem, detach will not rebuild the View after destroying it. You will have to manually change the parameters. The FragmentAdapter getItem function is called the first time a Fragment needs to be created. If you need to pass parameters to the Fragment, you can set this on FragmentAdapter getItem (), but only when getItem is new. Once a Fragment is created, it will be cached by the FragmentManager. If not released, getItem will not be called again for the Fragment in its current location, for reasons explained in the instantiateItem function above. It’s going to go to the cache first. So at this point, how do you update? Fragment.setArguments cannot be called again, because attached fragments cannot be set again through setArguments or else an exception will be thrown

public void setArguments(Bundle args) {
    if (mIndex >= 0) {
        throw new IllegalStateException("Fragment already active");
    }
    mArguments = args;
}
Copy the code

Set getItemPosition to POSITION_NONE on instantiateItem (‘ View ‘, ‘View’);

public int getItemPosition(Object object) {
    return POSITION_NONE;
}
Copy the code

How to set the parameters? The user needs to manually provide interface change parameters, override the instantiateItem with the custom FragmentAdapter, retrieve the cached Fragment manually, reset the parameters before attaching it, and after that, Fragment gets new parameters when it goes through the onCreateView process.

@Override
public Object instantiateItem(ViewGroup container, int position) {

    String name = makeFragmentName(container.getId(), position);
    Fragment fragment =((FragmentActivity) container.getContext()).getSupportFragmentManager().findFragmentByTag(name);

    if(fragment instanceof MyFragment){
        Bundle bundle=new Bundle();
        bundle.putString("msg",""+System.currentTimeMillis());
        ( (MyFragment) fragment).resetArgument(bundle);
    }

    return super.instantiateItem(container, position);
}

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}
Copy the code

This completes the refresh of the Fragment in the FragmentPagerAdapter. And we also know that for the FragmentPagerAdapter, the user doesn’t need to cache the Fragment at all, they just cache the View, because the FragmentPagerAdapter doesn’t destroy the Fragment, It also does not destroy cached fragments in FragmentManager, and whether cached views are refreshed or not may depend on your specific business needs.

How FragmentStatePagerAdapter through notifyDataSetChanged refresh ViewPager page

For FragmentStatePagerAdapter relatively easy, if you don’t need to consider the efficiency, to rebuild all fragments can, just need to copy the getItemPosition function

public int getItemPosition(Object object) {
    return POSITION_NONE;
}
Copy the code

Because the FragmentStatePagerAdapter real remove fragments, to achieve the effect of completely rebuilt.

Meaning of Fragmentmanager Transaction stack

Finally, take a look at the Transaction stack in Fragmentmanager. What exactly does the Transaction stack in Fragmentmanager do? FragmentManager operates on fragments in batches. There are multiple Add, remove, and attach operations in a Transaction. Android has a return key. Android Fragment management introduces a Transaction stack to facilitate rollback. In fact, it flips all operations on a Transaction: add, delete, attach, detach, and vice versa. For every Transaction that is pushed onto the stack, it needs to be removed from the stack, and each operation has a context, such as an entry and exit animation. When the operation needs to be flipped, i.e. when the back key is clicked, it needs to know how to flip, i.e. record the current scene. For remove, if there is no push operation, Note You can delete the context without recording it. The ViewPager when using FragmentPagerAdapter/FragmentStatePagerAdapter won’t addToBackStack, which is why the detach a consistent or inconsistent with the remove sometimes performance reasons. If you take a quick look at the unstack operation, you’re actually doing a reverse of the original operation, but not exactly copying it, depending on the current Fragment.

public void popFromBackStack(boolean doStateMove) { Op op = mTail; while (op ! = null) { switch (op.cmd) { case OP_ADD: { Fragment f = op.fragment; f.mNextAnim = op.popExitAnim; mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; case OP_REPLACE: { Fragment f = op.fragment; if (f ! = null) { f.mNextAnim = op.popExitAnim; mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } if (op.removed ! = null) { for (int i=0; i<op.removed.size(); i++) { Fragment old = op.removed.get(i); old.mNextAnim = op.popEnterAnim; mManager.addFragment(old, false); } } } break; .Copy the code

FragmentManager Cache management of fragments

FragmentManager maintains three important lists, an mActive FragmentList, an mAdded FragmentList, and a BackStackRecord rollback stack

ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
ArrayList<BackStackRecord> mBackStack;
Copy the code

The mAdded list is currently added to the Container, while mActive is a collection of all participating fragments. As long as the Fragment is not removed, it will exist uniformly. It can be considered that all fragments added are alive. MActive fragments can be executed and null, only makeInactive functions do this.

void makeInactive(Fragment f) {
    if (f.mIndex < 0) {
        return;
    }
    mActive.set(f.mIndex, null);
    if (mAvailIndices == null) {
        mAvailIndices = new ArrayList<Integer>();
    }
    mAvailIndices.add(f.mIndex);
    mActivity.invalidateFragment(f.mWho);
    f.initState();
}
Copy the code

It is from these two lists that the FragmentPagerAdapter gets the Fragment it is trying to get.

public Fragment findFragmentByTag(String tag) { if (mAdded ! = null && tag ! = null) { for (int i=mAdded.size()-1; i>=0; i--) { Fragment f = mAdded.get(i); if (f ! = null && tag.equals(f.mTag)) { return f; } } } if (mActive ! = null && tag ! = null) { for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.get(i); if (f ! = null && tag.equals(f.mTag)) { return f; } } } return null; }Copy the code

conclusion

This article simply analyzes the ViewPager with FragmentStatePagerAdapter encounter problems in using FrgmentPagerAdapter, principle, and the solution of the problem.

Author: read the little snail The original link: ViewPager refresh principle of problem analysis and solution (FragmentPagerAdapter + FragementStatePagerAdapter)

For reference only, welcome correction