ViewPager中Fragment的懒加载和可见状态监听

一. 前言

在Android开发中常常会使用到ViewPager, ViewPager若是和Fragment一块儿使用的话, 就要考虑懒加载和预加载的问题. ViewPager有个方法setOffscreenPageLimit 这个方法能够配置缓存数量. 那是否是直接设置0就能够实现懒加载了呢? 不是的, 查看源码:git

public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {  //DEFAULT_OFFSCREEN_PAGES为1
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }
复制代码

设置0后会无效的, limit 仍是被设置成默认的1github

懒加载就是当用户滑动到当前的frament才能去加载数据, 这样避免加载了数据可是没有使用到, 形成了浪费.
预加载是为了提早加载数据, 让用户减小等待时间. 懒加载和预加载应该根据具体的业务要求去使用. 没有谁好谁坏之分. 但二者的前提都是要搞清楚Fragment在ViewPager中的生命周期, 下面先来弄清楚生命周期的调用.缓存

二. Fragment在ViewPager中的生命周期

首先写一个简单的Activity里面有ViewPager 代码以下:bash

public class MainActivity extends AppCompatActivity {

    private ViewPager viewById;
    private List<Fragment> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewById = findViewById(R.id.vp);
        list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            SimpleFragment simpleFragment = SimpleFragment.newInstance(i);
            list.add(simpleFragment);
        }
        MineAdapter mineAdapter = new MineAdapter(getSupportFragmentManager(),list);
        viewById.setOffscreenPageLimit(1);
        viewById.setAdapter(mineAdapter);
    }
}
复制代码

关于这个SimpleFragment代码就不用贴出来了 很简单就是在各个生命周期中加入log. 这个MineAdapter 很简单的. 以下:网络

public class MineAdapter extends FragmentPagerAdapter {
    List<Fragment>  mPages;
    public MineAdapter(FragmentManager fm, List<Fragment> pages) {
        super(fm);
        mPages=pages;
    }
    @Override
    public Fragment getItem(int i) {
        return mPages.get(i);
    }
    @Override
    public int getCount() {
        return mPages.size();
    }
}
复制代码

运行程序 来看看Fragment生命周期 日志以下: 为了方便叙述第0个fragement简称为0号. 这时候实际上是缓存数量为1ide

image.png
经过日志能够看到首次进入这个activity页面的时候. 首先加载的是0号和1号, 并且不是说等0号(当前显示界面)加载完 再加载1号. 而是0号和1号生命周期交错进行. 而且都一直走到了onResume方法. 滑动到1号 这时候 1号为当前展现界面. 0号和2号为缓存. 继续看日志
image.png
这时候1号只走了一个方法setUserVisibleHint true . 而后2号生命周期从onAttach到onResume. 再滑动一下, 这时候2号为当前展现界面. 这时候会缓存1号和3号, 0号进入销毁.
image.png

三. 总结生命周期中的规律

经过以上的日志 能够总结关键几点.ui

  1. setUserVisibleHint 方法都是比较先走的. 首次进入的时候同一个Fragment的setUserVisibleHint 要走两次 一次true, 一次false.
  2. 被预加载的Fragment的生命周期 除了setUserVisibleHint true没走以外 其余的生命周期也走了.
  3. 预加载的Fragment 到显示的时候 其实只走了 setUserVisibleHint true.
  4. Fragment 销毁的时候 只走到了onDestroyView方法 并无走onDestroy onDetach方法. 这点对于 执行一些回收操做很是有必要了解.

四. 懒加载的实现

懒加载是滑动到当前Fragment的时候才去调用的方法. 通常在实际业务中就是滑到了要展现的页面去调接口获取数据.
写一个基础的BaseLazyFragment , 继承这个BaseLazyFragment 重写lazyInit()方法. 这个方法里写你须要执行的懒加载操做.spa

public class BaseLazyFragment extends Fragment {
    private boolean isViewPrepared; // 标识fragment视图已经初始化完毕
    private boolean hasFetchData; // 标识已经触发过懒加载数据

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        lazyFetchDataIfPrepared(); //通过了预加载页面, 而后展现 
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isViewPrepared = true;
        lazyFetchDataIfPrepared(); //首次进入, 没有预加载直接加载数据
    }

    /**
     * 懒加载方法,获取数据什么的放到这边来使用,在切换到这个界面时才进行网络请求
     */
    private void lazyFetchDataIfPrepared() {
        // 用户可见fragment && 没有加载过数据 && 视图已经准备完毕
        if (getUserVisibleHint() && !hasFetchData && isViewPrepared) {
            hasFetchData = true; //已加载过数据
            lazyInit();
        }
    }

    /**
     * 执行须要懒加载的方法
     */
    protected void lazyInit() {
        Log.i("zmin........." + getArguments().getInt("key"), ".............加载完成数据");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        hasFetchData = false;
        isViewPrepared = false;
        Log.i("zmin........." + getArguments().getInt("key"), ".............onDestroyView");
    }
}
复制代码

五. 预加载

1. 预加载在viewpager中

viewpager其实对预加载有很好的支持. 能够直接调用方法setOffscreenPageLimit来设置缓存的数量.3d

2. 监听可见和不可见的状态

在实际业务中, 可能存在这样一种需求. 虽然是须要预加载的, 可是要监听Fragment的可见状态. 好比Fragment中有视频播放. 若是Fragment可见的话就要播放. 不可见的时候就须要暂停. 这时候还须要考虑的是Fragment可能会跳转到其余界面. Fragment虽然可见和不可见有个生命周期方法setUserVisibleHint回调, 可是没法直接得知当前状态是一直不可见的,仍是由可见转为不可见的 . 下面来实现这个功能 :日志

public class BaseAppearFragment extends Fragment {

    private boolean isViewPrepared; // 标识fragment视图已经初始化完毕
    private boolean hasAppear; //标识界面当前可见

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        //当前fragment转为可见状态
        if (isVisibleToUser && isViewPrepared && !hasAppear) {
            onFragmentAppear();
            hasAppear = true;
        }
        //当前fragment转为不可见状态
        if (!isVisibleToUser && hasAppear) {
            onFragmentDismiss();
            hasAppear = false;
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.i("zmin........." + getArguments().getInt("key"), ".............onCreateView");
        return inflater.inflate(R.layout.activity_fragment, null);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isViewPrepared = true;
        TextView tv = getView().findViewById(R.id.tv);
        tv.setText(String.valueOf(getArguments().getInt("key")));
        Log.i("zmin........." + getArguments().getInt("key"), ".............onViewCreated");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i("zmin........." + getArguments().getInt("key"), ".............onResume(");
        if (getUserVisibleHint()) {
            onFragmentAppear();
            hasAppear = true;
        }
    }

    @Override
    public void onDestroyView() {
        isViewPrepared = true;
        super.onDestroyView();
        Log.i("zmin........." + getArguments().getInt("key"), ".............onDestroyView");
    }

    /**
     * 界面可见
     */
    public void onFragmentAppear() {
        Log.i("zmin........." + getArguments().getInt("key"), "......界面可见..onFragmentAppear");
    }

    /**
     * 界面由可见转为不可见
     */
    public void onFragmentDismiss() {
        Log.i("zmin........." + getArguments().getInt("key"), "...由可见转为不可见.........onFragmentDismiss");
    }
}

复制代码

能够看到主要在在setUserVisibleHint和onResume方法中作判断. 由于Fragment切换的时候, 不少生命周期方法是不走的.

  1. 当0号滑动到1号的时候, 这时候1号只走setUserVisibleHint方法. onResume方法是不走的.
  2. 当1号跳转到其余界面再返回的时候 会执行onResume可是不执行setUserVisibleHint .
  3. 而首次进入的时候setUserVisibleHint 和onResume都执行.

六 总结

经过详细的日志 分析了Fragment生命周期的执行. 从而实现懒加载和预加载中对可见状态监听. 不少业务场景下须要用到. 若是要懒加载能够直接继承BaseLazyFragment 类便可. 若是要监听可见隐藏状态则能够继承 BaseAppearFragment . 若是还想本身去看看打印的日志. 能够clone代码, github地址 github.com/zmin666/Zmi… 但愿这些总结对你有帮助.

相关文章
相关标签/搜索