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

一. 前言

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

  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 还是被设置成默认的1

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

二. Fragment在ViewPager中的生命周期

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

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号. 这时候其实是缓存数量为1
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

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

通过以上的日志 可以总结关键几点.

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

四. 懒加载的实现

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

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来设置缓存的数量.

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地址 https://github.com/zmin666/ZminDemo
希望这些总结对你有帮助.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容