Android Fragment 懒加载

Android Fragment 懒加载

一、为什么要进行懒加载

一般我们在使用add+show+hide去显示、隐藏fragment或者fragment嵌套使用、viewpager+fragment结合使用等场景下,如果不进行懒加载会导致多个fragment页面的生命周期被调用,每个页面都进行网络请求这样会产生很多无用的请求,因为实际显示的只是用户看到的那个页面,其他页面没有必要在这个时候去加载数据。

二、fragment懒加载

懒加载即在页面第一次可见时再进行数据请求以及加载的操作,因为版本的不同实现懒加载的方式也略有不同。

首先我们来看下旧版(support包)懒加载的实现方式,其实旧版主要是利用setUserVisibleHint和onHiddenChanged两个函数来实现懒加载的。

  • viewpager+fragment

viewpager+fragment结合的方式,一共有4个fragment分别是fragmentA、fragmentB、fragmentC、fragmentD。
ViewPager 预缓存 Fragment 的个数为1的情况下fragment的生命周期变化。

右划到fragmentB时的变化

返回fragmentA时的变化

由上面我们可以看出每次setUserVisibleHint函数都会比fragment的生命周期函数先回调,且仅当前显示页面的isVisibleToUser为true,其他页面isVisibleToUser均为false。进入fragmentA页面时同时加载了fragmentA和fragmentB且两者都回调了onresume生命周期函数,当划向fragmentB之后加载了fragmentC但是fragmentB未回调任何生命周期函数仅回调了setUserVisibleHint。

因此我们可以利用setUserVisibleHint来进行fragment的懒加载。下面上代码

public class LazyLoadFragment extends Fragment {
    //判断是否已进行过加载,避免重复加载
    private boolean isLoad=false;
    //判断当前fragment是否可见
    private boolean isVisibleToUser = false;
    //判断当前fragment是否回调了resume
    private boolean isResume = false;
    @Override
    public void onResume() {
        super.onResume();
        isResume=true;
        lazyLoad();
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.i(TAG, “setUserVisibleHint: “+isVisibleToUser+”  “+FragmentD.this);
        this.isVisibleToUser=isVisibleToUser;
        lazyLoad();
    }

    private void lazyLoad() {
        if (!isLoad&&isVisibleToUser&&isResume){
            //懒加载。。。
            isLoad=true;
        }
    }
  • add+show+hide
    Add fragmentA和fragmentB然后hide B show A,下面是两者相关函数回调结果

    然后 show B hide A

再show A hide B

右上边可以看出一开始add A和B时两者都回调了onresume,但是B还 回调了onHiddenChanged且为true,之后show B hide A 没有触发任何生命周期函数,两者仅回调了onHiddenChanged 且A为true B为false。

由此我们可以利用onHiddenChanged 来完成懒加载机制,下面是代码

public class FragmentD extends Fragment {
    //判断是否已进行过加载,避免重复加载
    private boolean isLoad=false;
    //判断当前fragment是否可见
    private boolean isHidden=true;

    @Override
    public void onResume() {
        super.onResume();
        lazyLoad();
    }
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        Log.i(TAG, “onHiddenChanged: “+hidden+” “+FragmentD.this);
        isHidden=hidden;
        lazyLoad();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isLoad=false; //注意当view销毁时需要把isLoad置为false
    }

    private void lazyLoad() {
        if (!isLoad&&!isHidden){
            //懒加载。。。
            isLoad=true;
        }
    }
}
  • 复杂嵌套的fragment
    当add+hide+show和viewpager+fragment 嵌套组合使用时上面的懒加载就需要做一些调整。
public class FragmentD extends Fragment {
    //判断是否已进行过加载,避免重复加载
    private boolean isLoad=false;
    //判断当前fragment是否可见
    private boolean isVisibleToUser = false;
    //判断当前fragment是否回调了resume
    private boolean isResume = false;
    private boolean isCallUserVisibleHint = false;

    @Override
    public void onResume() {
        super.onResume();
        isResume=true;
        if (!isCallUserVisibleHint) isVisibleToUser=!isHidden();
        lazyLoad();
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.i(TAG, “setUserVisibleHint: “+isVisibleToUser+”  “+FragmentD.this);
        this.isVisibleToUser=isVisibleToUser;
        isCallUserVisibleHint=true;
        lazyLoad();
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        Log.i(TAG, "onHiddenChanged: "+hidden+" "+FragmentD.this);
        isVisibleToUser=!hidden;
        lazyLoad();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isLoad=false;
        isCallUserVisibleHint=false;
        isVisibleToUser=false;
        isResume=false;
    }

    private void lazyLoad() {
        if (!isLoad&&isVisibleToUser&&isResume){
            //懒加载。。。
            isLoad=true;
        }
    }

看完旧版的我们来看下新版的(Androidx)fragment的懒加载应该如何实现。

Google 在 Androidx 在FragmentTransaction中增加了setMaxLifecycle方法来控制 Fragment 所能调用的最大的生命周期函数.该方法可以设置活跃状态下 Fragment 最大的状态,如果该 Fragment 超过了设置的最大状态,那么会强制将 Fragment 降级到正确的状态。

  • ViewPager+Fragment
    在 FragmentPagerAdapter 与 FragmentStatePagerAdapter 新增了含有behavior字段的构造函数
  public FragmentPagerAdapter(@NonNull FragmentManager fm,
            @Behavior int behavior) {
        mFragmentManager = fm;
        mBehavior = behavior;
    }

  public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
            @Behavior int behavior) {
        mFragmentManager = fm;
        mBehavior = behavior;
    }

如果 behavior 的值为 BEHAVIOR_SET_USER_VISIBLE_HINT,那么当 Fragment 对用户的可见状态发生改变时,setUserVisibleHint 方法会被调用。
如果 behavior 的值为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ,那么当前选中的 Fragment 在 Lifecycle.State RESUMED 状态 ,其他不可见的 Fragment 会被限制在 Lifecycle.State STARTED 状态。

使用了 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 后,确实只有当前可见的 Fragment 调用了 onResume 方法。而导致产生这种改变的原因,是因为 FragmentPagerAdapter 在其 setPrimaryItem 方法中调用了 setMaxLifecycle 方法

    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        //如果当前的fragment不是当前选中并可见的Fragment,那么就会调用
        // setMaxLifecycle 设置其最大生命周期为 Lifecycle.State.STARTED
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                    if (mCurTransaction == null) {
                        mCurTransaction = mFragmentManager.beginTransaction();
                    }
                    mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
                } else {
                    mCurrentPrimaryItem.setUserVisibleHint(false);
                }
            }
        //对于可见的Fragment,则设置其最大生命周期为
        //Lifecycle.State.RESUMED
            fragment.setMenuVisibility(true);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
            } else {
                fragment.setUserVisibleHint(true);
            }

            mCurrentPrimaryItem = fragment;
        }
    }

所以在ViewPager+fragment 结构中实现懒加载可以这样:

public class LazyLoadFragment extends Fragment {
    //判断是否已进行过加载,避免重复加载
    private boolean isLoad=false;

    @Override
    public void onResume() {
        super.onResume();
        if (!isLoad){
            lazyLoad();
            isLoad=true;
        }
    }

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

    private void lazyLoad() {
            //懒加载。。。
    }
}
  • add+hide+show
    参考viewpager做法,在add fragment时仅把要显示的fragment通过setMaxLifecycle设置为resume,其他fragment均设置为start。
    在show、hide切换显示的fragment时仅把show的fragment通过setMaxLifecycle设置为resume,其他hide的fragment均设置为start

  • 复杂嵌套
    当fragment嵌套fragment等复杂情况下,只要父fragment回调onresume生命周期函数那被嵌套的所有同级子fragment都会回调onresume,所以我们需要再加上fragment是否隐藏来判断是否要进行懒加载。

public class LazyLoadFragment extends Fragment {
    //判断是否已进行过加载,避免重复加载
    private boolean isLoad=false;

    @Override
    public void onResume() {
        super.onResume();
        if (!isLoad&& !isHidden()){
            lazyLoad();
            isLoad=true;
        }
    }

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

    private void lazyLoad() {
            //懒加载。。。
    }
}

viewpager2 本身已支持懒加载

原文地址:https://www.cnblogs.com/Robin132929/p/13819386.html