# 1. The following page structure is often encountered:

TabLayout+ViewPager+FragmentStatePagerAdapter+Fragment

  1. How to determine the system was recovered?
  2. Why doesn’t the content show up?
  3. The solution

# 2. Solve problems

How to determine the system was recovered

Check whether the adapter is null. If not, check whether the fragment state is null.

/ * * * @returnPublic Boolean Whether fragment was sent to detach by system or destroyedisFragmentsDetachedOrDestroyed() {
        if(getCount() > 0 && fragmentList ! = null && fragmentList.size() > 0) {for (int i = 0; i < fragmentList.size(); i++) {
                if(fragmentList.get(i) == null || fragmentList.get(i).isDetached() || ! fragmentList.get(i).isAdded()) {return true; }}}else {
            return true;
        }
        return false;
    }
Copy the code

Just make sure one of them doesn’t exist anymore. Of course, there’s another way. As we know, the system will save the corresponding data in the destruction of the non-user normal exit and call onSaveInstanceState, and restore the page when it is displayed again and call onRestoreInstanceState. Both methods can also be seen in the ViewPager source code.

 @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.position = mCurItem;
        if(mAdapter ! = null) { ss.adapterState = mAdapter.saveState(); }return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if(! (state instanceof SavedState)) { super.onRestoreInstanceState(state);return;
        }

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        if(mAdapter ! = null) { mAdapter.restoreState(ss.adapterState, ss.loader);setCurrentItemInternal(ss.position, false.true);
        } else{ mRestoredCurItem = ss.position; mRestoredAdapterState = ss.adapterState; mRestoredClassLoader = ss.loader; }}Copy the code

MRestoredCurItem, mRestoredAdapterState, and mRestoredClassLoader are not default values. (Not expanded here)

Why doesn’t the content show up?

In the case of this article, a new Adapter is reassigned to the ViewPager each time it is judged to have been reclaimed because the requirements require it.

mAdapter = new CustomPagerAdapter(getActivity(), getChildFragmentManager(), list);
viewPager.setAdapter(mAdapter);
viewPager.setOffscreenPageLimit(size);
Copy the code

It is common sense to think that a new Adapter object should point to a new reference, so the corresponding Fragment should be recreated and displayed, but it is not. Look at the writing custom Adapter (extends FragmentStatePagerAdapter) :

@NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        LogUtil.d(TAG, "instantiateItem->" + position);
        return super.instantiateItem(container, position);
    }

  @Override
    public Fragment getItem(int position) {
        LogUtil.d(TAG, "getItem->" + position);
        return fragmentList.get(position);
    }
Copy the code

Why are no new fragments generated? From the printed log, we can see that the instantiateItem is called, but getItem is not called and no Fragment is returned from there. Writing in the track FragmentStatePagerAdapter:

 @NonNull
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        Fragment fragment;
        if (this.mFragments.size() > position) {
            fragment = (Fragment)this.mFragments.get(position);
            if(fragment ! = null) {returnfragment; }}if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }

        fragment = this.getItem(position);
        if (this.mSavedState.size() > position) {
            SavedState fss = (SavedState)this.mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }

        while(this.mFragments.size() <= position) {
            this.mFragments.add((Object)null);
        }

        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        this.mFragments.set(position, fragment);
        this.mCurTransaction.add(container.getId(), fragment);
        return fragment;
    }
Copy the code

As you can see, when mFragments can find corresponding to the position of the fragments, is not to call the getItem method and using FragmentStatePagerAdapter mediator or attribute value. So why is the new Adapter still not empty? Trace its assignment:

public void restoreState(Parcelable state, ClassLoader loader) {
        if(state ! = null) { Bundle bundle = (Bundle)state; bundle.setClassLoader(loader); Parcelable[] fss = bundle.getParcelableArray("states");
            this.mSavedState.clear();
            this.mFragments.clear();
            if(fss ! = null) {for(int i = 0; i < fss.length; ++i) {
                    this.mSavedState.add((SavedState)fss[i]);
                }
            }

            Iterable<String> keys = bundle.keySet();
            Iterator var6 = keys.iterator();

            while(true) {
                while(true) {
                    String key;
                    do {
                        if(! var6.hasNext()) {return;
                        }

                        key = (String)var6.next();
                    } while(! key.startsWith("f"));

                    int index = Integer.parseInt(key.substring(1));
                   Fragment f = this.mFragmentManager.getFragment(bundle, key);
                    if(f ! = null) {while(this.mFragments.size() <= index) {
                            this.mFragments.add((Object)null);
                        }

                        f.setMenuVisibility(false);
                        this.mFragments.set(index, f);
                    } else {
                        Log.w("FragmentStatePagerAdapt"."Bad fragment at key " + key);
                    }
                }
            }
        }
    }
Copy the code

System destruction will call the above method, we could see this sentence fragments f = this. MFragmentManager. GetFragment (bundle, key); Fragments are retrieved from the FragmentManager and added to the Adapter mFragments. The entire restoreState call is in two places:

  1. viewPager.setAdapter
  2. viewPager.onRestoreInstanceState

Since the FragmentManager is the same object every time a new Adapter is generated (this cannot be changed to another), when setAdapter is generated, The Fragment list value previously saved in the same FragmentManager will still be assigned to the mFragments in the newly generated Adapter, which will then have a value when judged on the instantiateItem by mFragments. Therefore, the getItem method is not called.

There are many solutions, two of which are provided below; 1) rewrite FragmentStatePagerAdapter, commented out with fragments from the mFragments return code block.

 @NonNull
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        Fragment fragment;
//        if (this.mFragments.size() > position) {
//          fragment = (Fragment)this.mFragments.get(position);
//          if(fragment ! = null) { //returnfragment; //} //Copy the code

② In the customized AdapterinstantiateItemMethod to remove data such as mFragments

@NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        LogUtil.d(TAG, "instantiateItem->" + position);
        try {
            Field mFragments = getClass().getSuperclass().getDeclaredField("mFragments");
            mFragments.setAccessible(true);
            ((ArrayList) mFragments.get(this)).clear();

            Field mSavedState = getClass().getSuperclass().getDeclaredField("mSavedState");
            mSavedState.setAccessible(true);
            ((ArrayList) mSavedState.get(this)).clear();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return super.instantiateItem(container, position);
    }
Copy the code

There are other solutions to the problems mentioned in this article, such as reassigning values directly from the system. If you use the FragmentPagerAdapter, the solution follows a similar pattern.