从FragmentPagerAdapter管理Fragment生命周期及可见性

从FragmentPagerAdapter管理Fragment生命周期及可见性

使用ViewPager和Fragment相结合需要用到FragmentPagerAdapter适配器。那么我们先来看下FragmentPagerAdapter适配器带来的问题

问题1:如果Fragment有网络加载,那么我们会发现除了当前的Fragment的网络启动之外,别的Fragment的网络加载也启动了
问题2:显示上没问题,左右滑动也都没事,但是越用越卡
问题3:如何利用Fragment的生命周期让我们的页面顺滑起来

问题1:如果Fragment有网络加载,那么我们会发现除了当前的Fragment的网络启动之外,别的Fragment的网络加载也启动了!这里要说的是FragmentPagerAdapter的加载机制,为了让客户有更好的体验,当我们使用ViewPager显示一个Fragment的时候,FragmentPagerAdapter会自动加载其两侧的Fragment,进而让客户在滑动的时候感觉没有明显的卡顿和数据加载。但是如果我们写的网络加载里面有Toast的话,就会发生一些乌龙时间,好比第一页是有数据的,但是我在无数据页给了一个无数据提示,就会发生在有数据的页接到了无数据提示。而且更主要的是,同时开启两个数据加载连接必然产生两个请求数据返回的时间加长!这样对应当前想使用的页面体验又是不好的。再一个我们缓存两页,如果图片过多,又没有写好数据回收的工作,那么刚开始就是卡顿,再然后就是内存溢出。

问题2:就是Fragment左右加载的内存没有被释放导致的,这里建议使用Fresco去加载图片,因为Fresco的内存处理机制实在是太棒了!使用Fresco的缓存策略,只要图片不在当前页面显示那么图片的内存就被回收。

问题3:当我们滑动ViewPager的时候,在FragmentPagerAdapter下的Fragment生命周期回调。从Activity进入一个Fragment,其流程是

显示出来的页:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume
之后两边的页:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume

打印Log的时候我们可以看见同样的生命周期会输出三遍信息,这就是说当前的Fragment和左右的Fragment都被加载了。所以当我们将网络加载的代码放到onCreateView里面的时候,开启一个Fragment那么其实同时开启了3个网络加载线程!这里给出的解决策略是在setUserVisibleHint()方法中进行数据加载,当我们的Fragment显示的时候,启动数据加载单元。

当我们继续滑动Fragment的时候会看见那些生命周期呢?

被隐藏的页面:onPause->onStop->onDestroyView
新建的页面:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume

这里有一个重点,就是Fragment的生命周期到onDestroyView就结束了,而不是onDestory,这是很重要的,这就说明只有和Fragment.RootView相关的View被销毁了,而其他的数据还在。如果需要下来刷新和上拉加载,那么就需要一个全局的Adapter,在Fragment#onDestroyView的时候,Fragment的数据还在,那么就可以结合之前说的在setUserVisibleHint()中根据Adapter是否被初始化来决定是否再次进行网络加载!进而起到减少网络加载,和在使用时按需加载的需求。

这里继续为大家补充一些关于setUserVisibleHint()的知识:

  • setUserVisibleHint()在Fragment创建时会先被调用一次,传入isVisibleToUser = false
  • 如果当前Fragment可见,那么setUserVisibleHint()会再次被调用一次,传入isVisibleToUser = true
  • 如果Fragment从可见->不可见,那么setUserVisibleHint()也会被调用,传入isVisibleToUser = false

总结:setUserVisibleHint()除了Fragment的可见状态发生变化时会被回调外,在new Fragment()时也会被回调。如果我们需要在 Fragment 可见与不可见时干点事,直接使用setUserVisibleHint()就会发生多余的回调,那么就需要重新封装一下。

public abstract class LazyFragment extends Fragment {

    private static final String TAG = LazyFragment.class.getSimpleName();

    private boolean isFragmentVisible;
    private boolean isReuseView;
    private boolean isFirstVisible;
    private View rootView;

    //setUserVisibleHint()在Fragment创建时会先被调用一次,传入isVisibleToUser = false
    //如果当前Fragment可见,那么setUserVisibleHint()会再次被调用一次,传入isVisibleToUser = true
    //如果Fragment从可见->不可见,那么setUserVisibleHint()也会被调用,传入isVisibleToUser = false
    //总结:setUserVisibleHint()除了Fragment的可见状态发生变化时会被回调外,在new Fragment()时也会被回调
    //如果我们需要在 Fragment 可见与不可见时干点事,直接使用`setUserVisibleHint()`就会发生多余的回调
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        //setUserVisibleHint()有可能在fragment的生命周期外被调用
        if (rootView == null) {
            return;
        }
        if (isFirstVisible && isVisibleToUser) {
            onFragmentFirstVisible();
            isFirstVisible = false;
        }
        if (isVisibleToUser) {
            onFragmentVisibleChanged(true);
            isFragmentVisible = true;
            return;
        }
        if (isFragmentVisible) {
            isFragmentVisible = false;
            onFragmentVisibleChanged(false);
        }
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initVariable();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        //如果setUserVisibleHint()在rootView创建前调用时,那么就等到rootView创建完后才回调onFragmentVisibleChanged(true)
        //保证onFragmentVisibleChanged()的回调发生在rootView创建完成之后,以便支持UI操作
        if (rootView == null) {
            rootView = view;
            if (getUserVisibleHint()) {
                if (isFirstVisible) {
                    onFragmentFirstVisible();
                    isFirstVisible = false;
                }
                onFragmentVisibleChanged(true);
                isFragmentVisible = true;
            }
        }
        super.onViewCreated(isReuseView ? rootView : view, savedInstanceState);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        initVariable();
    }

    private void initVariable() {
        isFirstVisible = true;
        isFragmentVisible = false;
        rootView = null;
        isReuseView = true;
    }

    /**
     * 设置是否使用 View 的复用,默认开启
     * View 的复用是指,ViewPager 在销毁和重建 Fragment 时会不断调用 onCreateView() -> onDestroyView()
     * 之间的生命函数,这样可能会出现重复创建 View 的情况,导致界面上显示多个相同的 Fragment
     * View 的复用其实就是指保存第一次创建的 View,后面再 onCreateView() 时直接返回第一次创建的 View
     *
     * @param isReuse
     */
    protected void reuseView(boolean isReuse) {
        isReuseView = isReuse;
    }

    /**
     * 去除setUserVisibleHint()多余的回调场景,保证只有当fragment可见状态发生变化时才回调
     * 回调时机在View创建完后,所以支持UI操作,解决在setUserVisibleHint()里进行UI操作有可能报null异常的问题
     * 可在该回调方法里进行一些UI显示与隐藏,比如加载框的显示和隐藏
     *
     * @param isVisible true  不可见 -> 可见
     *                  false 可见   -> 不可见
     */
    protected void onFragmentVisibleChanged(boolean isVisible) {

    }

    /**
     * 在Fragment首次可见时回调,可在这里进行加载数据,保证只在第一次打开Fragment时才会加载数据,这样就可以防止每次进入都重复加载数据
     * 该方法会在 onFragmentVisibleChanged() 之前调用,所以第一次打开时,可以用一个全局变量表示数据下载状态,然后在该方法内将状态设置为下载状态,接着去执行下载的任务
     * 最后在 onFragmentVisibleChanged() 里根据数据下载状态来控制下载进度UI控件的显示与隐藏
     */
    protected void onFragmentFirstVisible() {

    }

    protected boolean isFragmentVisible() {
        return isFragmentVisible;
    }
}

FragmentStatePagerAdapter和FragmentPagerAdapter的区别

  1. FragmentPagerAdapter适用于Fragment比较少的情况,因为FragmentPagerAdapter会把每一个Fragment保存在内存中,不用每次切换的时候去保存现场,切换回来再重新创建,所以用户体验比较好。没有onDestored()
  2. 而对于Fragment比较多的情况,我们需要切换的时候销毁以前的Fragment以释放内存,就可以使用FragmentStatePagerAdapter.onDestored()

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/02/26/manage-fragment-lifecycle-and-visibility-from-fragmentpageradapter/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
从FragmentPagerAdapter管理Fragment生命周期及可见性
从FragmentPagerAdapter管理Fragment生命周期及可见性 使用ViewPager和Fragment相结合需要用到FragmentPagerAdapter适配器。那么我们先来看下FragmentPagerAda……
<<上一篇
下一篇>>
文章目录
关闭
目 录