美文网首页Android基础知识
Android中Fragment的懒加载

Android中Fragment的懒加载

作者: dashingqi | 来源:发表于2019-06-19 21:01 被阅读34次

    简介

    • 所谓的Fragment懒加载就是当Fragment可见的时候我们再去请求数据显示数据。
    • Fragment的懒加载就是解决Fragment配合ViewPager使用时的预加载,预加载会造成不必要的网络请求,这样会消耗用户的流量。如果Fragment中有大量图片的加载,此时懒加载就很有必要了!在本文中也介绍一种不使用ViewPager造成的预加载的情况。

    预加载

    • 当Fragment加在ViewPager中的时候,在切换到第一个Fragment的时候,ViewPager会帮助我们将第二个Fragment提前加载好,虽然在切换的时候会提高一定的流畅度,如果预加载的Fragment中存在网络请求,这样会造成不必要的流量损耗。造成这种提前加载的问题是因为ViewPager内部的缓存机制,ViewPager为了在切换的时候防止出现卡顿的现象,提前将目标Fragment的左右Fragment都会加载好,这样切换就很丝滑了。

    • 还有一种情况就是不用ViewPager的情况下,一次性将多个Fragment加载好,通过hide和show方法控制Fragment的显示,这样也存在预加载的情况,也会出现不必要的流量损耗。

    • 接下来我们就通过分析这两种情况,来采取懒加载去控制这个预加载。

    懒加载分析

    现在市面上存在很多底部栏切换和顶部栏切换的操作,用户有时只是看其中一个,并不想提前加载其他页面的数据(其实加载了用户也不知道,但是作为敲代码的我们,怎能让这种情况出现呢?),所以就需要使用懒加载来控制网络的请求。

    • 在这里我们分析懒加载的时候,我们通过两个例子来讲解下怎么控制预加载,从而采用懒加载。
    Fragment+ViewPager+TabLayout
    • 说起懒加载就会想到Fragment中的setUserVisibleHint这个方法,这个方法在将Fragment放到ViewPager中会得到执行。而我们通常的懒加载都是通过此方法中的参数配合Fragment的生命周期方法来使用的。
    • 在分析setUserVisibleHint之前 我们看一个Log
    lazy_1.png

    这里我把FragmentOne和FragmentTwo 中的 setUserVisibleHint()和生命周期onAttach 到 onResume都给重写加了log,我们发现加在ViewPager中的Fragment,在第一次加载FragmentOne的时候会去执行FragmentTwo的生命周期方法同时执行了setUserVisibleHint方法。onResume在Fragment中表示当前Fragment针对用户来说是否可见,但是由于ViewPager的缓存机制,观察生命周期,此方法已经没有意义了。

    当把FragmentOne切换到FragmentTwo的时候我们在看下Log

    lzay_two.png

    我们观察到每次Fragment切换的时候都会将左右的Fragment加载好,同时生命周期方法执行到onResume,唯一变的就是setUserVisibleHint中的值,当Fragment对我们来说是真正可见的时候,isVisibleToUser为True。

    • setUserVisibleHint(boolean isVisibleToUser)

    该方法就是当前对用户来说Fragment可见了,就返回了true,如果对当前的用户不可见就返回false。其中的参数isVisibleToUser就表示当前Fragment的可见状态。该方法是在Fragment生命周期方法之前执行的,这跟ViewPager中的缓存机制有关系。

    • 懒加载的代码
      • 我们可以在onActivityCreated中通过isVisibleToUser来判断是否可以去请求数据
    
      public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            if (mIsVisibleToUser) {
                getData();
            }
        }
    
    • 这样看似解决了预加载的问题但是又会出现另外一个问题,当由FragmentOne切换到FregmentTwo的时候,
      没有去请求网络数据的入口了,应为ViewPager的缓存机制,FragmentTwo的onActivityCreated已经执行了,这时我们可以利用setVisibleToUser()方法在setVisibleToUser中加入请求数据的方法。
      @Override
        public void setUserVisibleHint(boolean isVisibleToUser) {
            super.setUserVisibleHint(isVisibleToUser);
            mIsVisibleToUser = isVisibleToUser;
            if (mIsVisibleToUser ) {
                getData();
            }
        }
    
    • 这时重新运行项目,mmp直接crash了,看了下报错的地方是空指针。看了眼错误日志,说是找不到控件,我一想这时请求数据的方法请求完后就去更新UI,这个时候布局都没有创建完,怎么更新完呢?所以经过这个空指针小插曲,我在onViewCreated方法中加入了一个标记为,作为View布局加载完成的标记。并更新了setVisibleToUser中的判断逻辑。


      lazy_3.png
    @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            mIsViewCreatetd = true;
        }
    @Override
        public void setUserVisibleHint(boolean isVisibleToUser) {
            Log.d(TAG, "setUserVisibleHint: " + isVisibleToUser);
            super.setUserVisibleHint(isVisibleToUser);
            mIsVisibleToUser = isVisibleToUser;
            if (mIsVisibleToUser&& mIsViewCreatetd  ) {
                getData();
            }
        }
    
    • 这时我们重新build项目,运行下 嗯 没有报错,切换到FragmentTwo中能去请求数据了,切换到FragmentThree中也能请求数据了。以为大功告成了,这就完美了?然而并没有,当由FragmentThree切换到FragmentTwo的时候,我们发现又去请求数据了?这很不好,当已经请求过的数据页面,再次切换回去(已经销毁的View,再次切换回去应该重新请求数据的。),应该由用户去触发请求,也就是下拉刷新啥的。所以这时就想,在每次请求数据后做个已经请求数据的标记,然后更新setVisibleTouser中的判断逻辑。
      @Override
        public void setUserVisibleHint(boolean isVisibleToUser) {
            Log.d(TAG, "setUserVisibleHint: " + isVisibleToUser);
            super.setUserVisibleHint(isVisibleToUser);
            mIsVisibleToUser = isVisibleToUser;
            if (mIsVisibleToUser && mIsViewCreatetd && !isLoadData) {
                getData();
                isLoadData = true;
            }
        }
    @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            if (mIsVisibleToUser) {
                getData();
                isLoadData = true;
            }
            Log.d(TAG, "onActivityCreated: ");
        }
    
    • 到这里,关于配合使用ViewPager造成的预加载预防,使用懒加载方式就介绍到这里,这应该是最通用的一种判断方式了。
    Fragment+BottomNavigationView

    BottomNavigationView+Fragment实现底部菜单栏

    • 同样我们运行项目 看下log


      lazy_4.png

    同样还是老问题,当第一次加载的时候,所有的Fragment都会被加载,那么Fragment的onActivityCreated都执行了,那么此时在该方法中执行请求网络数据都会触发,也就是预加载了。在这个项目中,是使用hide和show方法来控制Fragment的显示,使用这两个方法的时候不会去执行Fragment的生命周期方法,那么会执行什么方法呢? 当然是onHiddenChanhed()方法了呗

    • 项目中的主要代码
    private void initFragment() {
            fragments.add(new FragmentOne());
            fragments.add(new FragmentTwo());
            fragments.add(new FragmentThree());
            fragments.add(new FragmentFour());
            fragments.add(new FragmentFive());
            loadFragments();
        }
        //加载所有的Fragment
        private void loadFragments() {
            FragmentManager manager = getSupportFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            for (Fragment fragment : fragments) {
                transaction.add(R.id.mContainerView, fragment, fragment.getClass().getName());
                if (!(fragment instanceof FragmentOne)) {
                    transaction.hide(fragment);
                }
            }
            transaction.commitAllowingStateLoss();
        }
    //点击Item切换不同的Fragment
     private void initFragmentClick() {
            mBottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
                    switch (menuItem.getItemId()) {
                        case R.id.home:
                            showFragment(fragments.get(0), fragments.get(selectedId));
                            selectedId = 0;
                            break;
                        case R.id.wechat:
                            showFragment(fragments.get(1), fragments.get(selectedId));
                            selectedId = 1;
                            break;
                        case R.id.project:
                            showFragment(fragments.get(2), fragments.get(selectedId));
                            selectedId = 2;
                            break;
                        case R.id.system:
                            showFragment(fragments.get(3), fragments.get(selectedId));
                            selectedId = 3;
                            break;
                        case R.id.setting:
                            showFragment(fragments.get(4), fragments.get(selectedId));
                            selectedId = 4;
                            break;
                        default:
                            break;
                    }
                    return true;
                }
            });
        }
    //判断显示那个Fragment
    private void showFragment(Fragment show, Fragment hide) {
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            if (show != hide)
                transaction.hide(hide).show(show).commitAllowingStateLoss();
        }
    
    • 说下onHiddenChanged(boolean hidden)方法
      • 用FragmentTransaction的show和hide方法来控制Fragment,是不会走Fragment的生命周期方法的。
      • 参数hidden表示当前的Fragment是否不可见 true/false
      • 使用此种方式来管理Fragment是不会去执行setVisibleToUser方法的。
    • 所以总结以上,这种方式的懒加载可以实现如下
      • 重写onHiddenChange()方法,根据hidden判断当前Fragment是否处于隐藏。
      • 在onViewCreated中设置一个标记位
      • 每次调用完获取数据的方法,设置一个已请求数据的标志位。
    • 综上所述,可以写如下代码来控制预加载,继而使用懒加载的方式。
     @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            Log.d(TAG, "onActivityCreated: ");
            if (!isHidden) {
                getData();
                isLoadData = true;
            }
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            isViewCreated = true;
            Log.d(TAG, "onViewCreated: ");
        }
    
        @Override
        public void onHiddenChanged(boolean hidden) {
            super.onHiddenChanged(hidden);
            isHidden = hidden;
            Log.d(TAG, "onHiddenChanged: " + hidden);
            if (!hidden && isViewCreated && !isLoadData) {
                getData();
                isLoadData = true;
            }
        }
    
        public void getData() {
            Log.d(TAG, "getData: ");
        }
    

    结语

    至此关于Fragment的懒加载实现方法已经介绍完毕了,如有不对的方法,欢迎评论区讨论!

    相关文章

      网友评论

        本文标题:Android中Fragment的懒加载

        本文链接:https://www.haomeiwen.com/subject/bhvhqctx.html