FragmentStatePagerAdapter保存、康复及改写Fragment的数据

一、前语

本篇文章聚焦在“怎样运用FragmentStatePagerAdapter来保存Fragment的数据、在内存中加载出保存后的数据再填充到Fragment中、以及在下拉改写之后改写Fragment的最新数据”。关于 ViewPage、PagerAdapter、FragmentPagerAdapter、ViewPager2、FragmentStateAdapter 的怎样运用及差异并不在本篇文章内讨论。

假如关于新开发的功用,建议运用 ViewPager2 + FragmentStateAdapter 来构建; 关于需求维护的项目,且项目中运用到 ViewPage + FragmentStatePagerAdapter,仍是需求学习下 FragmentStatePagerAdapter 的一些用法的。

FragmentStatePagerAdapter,适用于多个Fragment的场景,默许情况下会在内存中保留3个Fragment,当时Fragment、左面及右侧Frgment,在现已都拜访一遍的情况下,其他的Fragment会被销毁掉(即走了onDestory)

FragmentStatePagerAdapter保存、康复的GIF图:

FragmentStatePagerAdapter保存、恢复、下拉刷新Fragment的内存数据

FragmentStatePagerAdapter 遇到下拉改写时,清空内存缓存数据,重新恳求最新的网络数据的GIF图:

FragmentStatePagerAdapter保存、恢复、下拉刷新Fragment的内存数据

二、FragmentStatePagerAdapter保存、康复及改写数据的作用

1、FragmentStatePagerAdapter保存、康复

如第一个GIF图所示,将Fragment的网络数据保存到内存中,然后下次Fragment再次构建时运用内存中数据。

首先有五个Fragment,分别是Fragment0 – Fragment4。

(1)第一步:进入到该首页后,从左到右滑动到最后一个Fragment,加载网络数据并填充到对应的Fragment中。

(2)第二步:从右到左依次滑动到第一个Fragment,此刻能够看到 Fragment3 及 Fragment4 展现的是网络数据,而Fragment0 到 Fragment3 展现的内存数据。

这是由于在第一步时,当界面展现最后一个Fragment的时分,这个时分仅有Fragment3 及 Frgment4 是存活的,其他Fragment均被销毁掉了。在毁掉Fragment之前干了一件事,将数据保存到内存中去,当销毁掉的Fragment再次被构建时,将之前保存下来的数据填充到新构建的Fragment中展现。

(3)第三步:再次从左到右滑动到最后一个Fragment,此刻能够看到一切的Fragment运用的都是内存数据了。道理同上

仅在Fragment中做两件工作即可:

(1)保存Fragment数据到内存中, 重写onSaveInstanceState办法

(2)从内存中康复Fragment数据,在onCreateView办法中的判断参数savedInstanceState不为null,取其参数中的数据

2、FragmentStatePagerAdapter 遇到下拉改写时,清空内存缓存数据,重新恳求最新的网络数据

如第二个GIF图所示,下拉改写后,恳求最新的网络数据填充到Fragment中

(1)在当时Fragment4页面下,进行下拉改写

(2)下拉改写的数据回来后,改写当时页面,展现最新的网络数据

(3)从右滑动到左时,能够看到一切的Fragment页面均是恳求了最新的数据

这儿面有几个留意的点:

(1)下拉改写后,仅在当时页面显现时才会进行该页面的网络恳求,而不是将一切Fragment页面都进行网络恳求

(2)对应Fragment4而言,由于下拉改写后,Fragment4 及 Fragment3 是存活状态,因而需求单独地处理它们的改写(其实这儿需求考虑地是当时Fragment及其左右的Fragment)

(3)而对应非Fragment4、Fragment3的Fragment们,由于现已被销毁掉了,所以只需求在下拉改写后,将它们的内存数据清除去就能够了,这样下次构建的时分,没有内存数据,则会重新恳求网络数据展现的。

关于下拉改写的能够参考这篇文章Android:对现有布局添加自定义的下拉改写布局(阻尼滑动、悬停、回弹动画作用)

三、具体实现

1、怎样保存Fragment的数据到内存中

FragmentStatePagerAdapter在毁掉Fragment的时分,会调用destoryItem办法,从而间接地调用了Fragment的onSaveInstanceState办法来保存数据。

public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    // ...
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null)
    // ...
}

因而,仅需求在Fragment中重写onSaveInstanceState办法

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    // 这儿能够根据需求保存数据
    outState.putString(TAG, DATA_STR);
    super.onSaveInstanceState(outState);
}

2、怎样康复内存中的数据填充到Fragment中

在构建Fragment的时分,能够充分地运用onCreateView办法中的Bundle类型的savedInstanceState参数:

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    mContext = getActivity();
    View view = inflater.inflate(R.layout.demo_fragment_layout, null, false);
    initView(view);
    // savedInstanceState不为空的时分,能够从内存中取出数据
    if (savedInstanceState != null) {
        mContentTv.setText(((String) savedInstanceState.get(TAG)) + ", 从内存中获得数据");
        mContentTv.setTextColor(mContext.getResources().getColor(R.color.black));
    } else {
       loadNetData();
    }
    return view;
}

3、怎样在下拉改写后,每个Fragment恳求最新的网络数据并改写展现

下拉改写,是将一切Fragment的数据都改写成最新的网络数据,因而在下拉改写后,要做下面几件事:

(1)想办法改写当时还活着的Fragment

如第二个GIF图所示,当在Fragment4的时分,下拉改写,则此刻活着的Fragment就只剩下Fragment4及Fragment3了,那么怎样改写Fragment4及Fragment3呢?

1)改写Fragment4

由于是在Fragment4页面进行下拉改写的,所以能够在下拉改写回调事件中调用:

@Override
public void onComplete() {
    Log.d(TAG, "onComplete... ");
    mMyFragmentAdapter.clearSavedState();
    // 当下拉改写后,分别将当时页面进行改写、左右页面在页面挑选后再进行改写
    // 改写当时页面
    int currentPosition = mViewPager.getCurrentItem();
    refreshUI(currentPosition);
    // 记载左面方位,待mViewPage切换的机遇进行改写
    mLeftRefresh = currentPosition - 1 >= 0 ? currentPosition - 1 : -1;
    // 记载右边方位,待mViewPage切换的机遇进行改写
    mRightRefresh = currentPosition + 1 <= mTabItemList.size() ? currentPosition + 1 : -1;
}
// 改写当时页面
private void refreshUI(int position) {
    Fragment currentFragment = mMyFragmentAdapter.getCurrentFragment(position);
    if (currentFragment instanceof DemoFragment) {
        ((DemoFragment) currentFragment).loadNetData();
    }
}

在回调中,改写当时页面的时分需求获取当时的Fragment,而Fragment都是由Adapter办理的,所以怎样获取到呢?能够运用反射去取:参考这个getCurrentFragment办法

public class MyFragmentAdapter extends FragmentStatePagerAdapter {
    private static final String TAG = "MyFragmentAdapter";
    public static final String POSITION = "POSITION";
    private List<TabItem> mTabItemList = new ArrayList<>();
    public MyFragmentAdapter(FragmentManager fm) {
        super(fm);
    }
    public void setTabItemList(List<TabItem> tabItemList) {
        mTabItemList = tabItemList;
    }
    @NonNull
    @Override
    public Fragment getItem(int position) {
        Bundle bundle = new Bundle();
        bundle.putInt(POSITION, position);
        Fragment demoFragment = new DemoFragment();
        demoFragment.setArguments(bundle);
        return demoFragment;
    }
    @Override
    public int getCount() {
        return mTabItemList.size();
    }
    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return mTabItemList.get(position).getName();
    }
    public void clearSavedState() {
        try {
            Class clazz = getClass().getSuperclass();
            Field savedStateField = clazz.getDeclaredField("mSavedState");
            savedStateField.setAccessible(true);
            Object object = savedStateField.get(this);
            ArrayList<Fragment.SavedState> savedStates = (ArrayList<Fragment.SavedState>) object;
            savedStates.clear();
        } catch (Exception e) {
            Log.e(TAG, "clear saved state is error");
        }
    }
   // 获取当时的Fragment
    @Nullable
    public Fragment getCurrentFragment(int position) {
        try {
            Class clazz = getClass().getSuperclass();
            Field mFragmentsField = clazz.getDeclaredField("mFragments");
            mFragmentsField.setAccessible(true);
            Object object = mFragmentsField.get(this);
            ArrayList<Fragment> fragmentArrayList = (ArrayList<Fragment>) object;
            return fragmentArrayList.get(position);
        } catch (Exception e) {
            Log.e(TAG, "getCurrentFragment is error");
            return null;
        }
    }
}

2)改写Fragment3

由于Fragment3此刻并没显现,能够做一个position标记,记载下当切换到Fragment3的时分,进行网络恳求并改写。因而,在回调中,看到的是记载左面方位(假如左面方位不存在,则赋值-1)

// 记载左面方位,待mViewPage切换的机遇进行改写
mLeftRefresh = currentPosition - 1 >= 0 ? currentPosition - 1 : -1;

在ViewPager中的页面切换回调中,拿到position和mLeftRefresh对比,假如相同则恳求网络数据并改写:

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }
    @Override
    public void onPageSelected(int position) {
        if (position == mLeftRefresh) {
            refreshUI(position);
            mLeftRefresh = -1;
        } else if (position == mRightRefresh) {
            refreshUI(position);
            mRightRefresh = -1;
        }
    }
    @Override
    public void onPageScrollStateChanged(int state) {
    }
});

(2)想办法改写现已销毁掉的Fragment

现已销毁掉的Fragment,其内存数据保存在mSavedState列表中

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();

因而,需求做的就是在下拉改写之后,将这个mSavedState列表数据进行清空即可。

到这个,你可能有思路了,就是反射取FragmentStatePagerAdapter的这个mSaveState私有属性做clear即可

// 下拉改写的回调
@Override
public void onComplete() {
    Log.d(TAG, "onComplete... ");
    // 清除内存数据
    mMyFragmentAdapter.clearSavedState();
}
// MyFragmentAdapter
public void clearSavedState() {
    try {
        Class clazz = getClass().getSuperclass();
        Field savedStateField = clazz.getDeclaredField("mSavedState");
        savedStateField.setAccessible(true);
        Object object = savedStateField.get(this);
        ArrayList<Fragment.SavedState> savedStates = (ArrayList<Fragment.SavedState>) object;
        savedStates.clear();
    } catch (Exception e) {
        Log.e(TAG, "clear saved state is error");
    }
}