Fragment懒加载

为什么需要懒加载?

我们在做安卓项目的时候,经常会有一个使用场景:ViewPage与多个Fragment组合使用。

在这里插入图片描述

然而,viewpager有着预加载机制:默认一次加载当前页面前后两个页面,即使设置setOffLimit(0)也没有效果。 虽然预加载优化了app的体验效果,但是这样把我们看不到的页面的数据也加载了,大大降低了性能,浪费初始化资源。

这时候,我们就需要懒加载。

什么是懒加载?

Fragment可见的时候,才加载当前页面。 没有打开的页面,就不会预加载。

说白了,懒加载就是可见的时候才去请求数据。

懒加载主要的方法

1. setUserVisibleHint()方法

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
    }

设置Fragment可见或者不可见时会调用此方法。isVisibleToUser表示Fragment是否可见,在该方法里面也可以通过调用getUserVisibleHint()获得Fragment的状态是可见还是不可见的,如果可见则进行懒加载操作。

此方法会在onCreateView()之前执行,所以如果在setUserVisibleHint()要实现懒加载的话,就必须要确保View以及其他变量都已经初始化结束,避免空指针。

2. getUserVisibleHint()方法

setUserVisibleHint(boolean isVisibleToUser)方法是比onCreate更早调用的,但是我们一般在加载数据时,都会在数据加载完成时进行UI更新,所以这就有了一个问题,假如拉取数据是秒回,但是我们还没有进行UI绑定,或者是Adapter初始化等,那么我们就无法更新UI了,所以Fragment给我们提供了另一个方getUserVisibleHint(),它就是用来判断当前Fragment是否可见,所以我们就可以在一系列变量初始化完成后再判断是否可见,若可见再进行数据拉取。

Fragment懒加载方式1

LazyLoadFragment

/**
 * Cerated by xiaoyehai
 * Create date : 2021/1/20 16:40
 * description : Fragment懒加载
 * <p>
 * 网络请求的方法真的只有当fragment可见时才请求,而且,加载过一次不会重复加载。
 */
public abstract class LazyLoadFragment extends Fragment {

    protected Context mContext;

    protected View mRootView;

    /**
     * 是否初始化过布局
     */
    private boolean isViewInitiated;

    /**
     * 当前Fragment是否可见
     */
    private boolean isVisibleToUser;

    /**
     * 是否加载过数据,确保fragment来回切换时不会重复加载数据
     */
    protected boolean isDataInitiated;


    /**
     * Fragment创建的时候回调
     *
     * @param savedInstanceState
     */
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getContext();
    }

    /**
     * 初始化fragment的布局
     *
     * @param inflater
     * @param container
     * @param savedInstanceState
     * @return
     */
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mRootView = inflater.inflate(getLayoutID(), container, false);
        return mRootView;
    }

    /**
     * 不是表示fragment所依赖的activity创建完成,fragment所依赖的activity的onCreate方法执行结束
     *
     * @param savedInstanceState
     */
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInitiated = true;

        init();

        //第一个默认显示的Fragment加载数据   在这里需要加载一次
        initLazyData();
    }


    /**
     * 设置Fragment可见或者不可见时会调用此方法。isVisibleToUser表示Fragment是否可见,在该方法里面也可以通过调用getUserVisibleHint()
     * 获得Fragment的状态是可见还是不可见的,如果可见则进行懒加载操作。
     * <p>
     * 此方法比onCreate()更早调用的,所以如果在setUserVisibleHint()要实现懒加载的话,
     * 就必须要确保View以及其他变量都已经初始化结束,避免空指针。
     * <p>
     * setUserVisibleHint(boolean isVisibleToUser)方法是比onCreate更早调用的,
     * 但是我们一般在加载数据时,都会在数据加载完成时进行UI更新,所以这就有了一个问题,假如拉取数据是秒回,
     * 但是我们还没有进行UI绑定,或者是Adapter初始化等,那么我们就无法更新UI了,所以Fragment给我们提供了
     * 另一个方法getUserVisibleHint(),它就是用来判断当前Fragment是否可见
     *
     * @param isVisibleToUser
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        //获得Fragment的状态是可见还是不可见的
       /* if (getUserVisibleHint()) {
            isVisibleToUser = true;
            initLazyData();
        }*/

        this.isVisibleToUser = isVisibleToUser;
        if (isVisibleToUser) {
            initLazyData();
        }
    }


    private void initLazyData() {
        if (isVisibleToUser && isViewInitiated && !isDataInitiated) {
            initData();
            isDataInitiated = true;
        }
    }

    /**
     * 判断懒加载条件
     *
     * @param forceUpdate 强制更新,没啥用
     */
    public void initLazyData(boolean forceUpdate) {
        if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
            initData();
            isDataInitiated = true;
        }
    }
    
   @Override
    public void onDestroyView() {
        super.onDestroyView();

        isViewInitiated = false;
        isDataInitiated = false;
    }

    protected abstract int getLayoutID();

    protected abstract void init();

    protected abstract void initData();
}

注意事项

  • 添加isViewInitiated参数。在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保initLazyData()方法不会报空指针异常。

  • getUserVisibleHint()会返回是否可见状态,这是fragment实现懒加载的关键,只有fragment可见才会调用加载数据。

  • 添加isDataInitiated参数。确保ViewPager来回切换时LazyLoadFragment的initData方法不会被重复调用,initLazyData在该Fragment的整个生命周期只调用一次,第一次调用initLazyData()方法后马上执行 isDataInitiated = true。

我在Fragment中打印了Fragment生命周期的几个比较重要的方法,从log上看setUserVisibleHint()的调用早于onCreateView,所以如果在setUserVisibleHint()要实现懒加载的话,就必须要确保View以及其他变量都已经初始化结束,避免空指针。

MainActivity

public class MainActivity extends AppCompatActivity {

    private TabLayout mTabLayout;
    private ViewPager mViewPager;

    private String[] titles = {"女装", "美食", "美妆", "居家用品", "男装", "鞋品"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
        mViewPager = (ViewPager) findViewById(R.id.viewPager);

        MyPagerAdapter myPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), titles);
        mViewPager.setAdapter(myPagerAdapter);

        mViewPager.setOffscreenPageLimit(titles.length);
        mTabLayout.setupWithViewPager(mViewPager);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:tabIndicatorColor="#f00"
        app:tabMode="scrollable"
        app:tabSelectedTextColor="#f00"
        app:tabTextColor="@color/black" />

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tabLayout">

    </androidx.viewpager.widget.ViewPager>

</LinearLayout>

MyPagerAdapter

/**
 * Cerated by xiaoyehai
 * Create date : 2019/12/15 17:40
 * description :
 */
public class MyPagerAdapter extends FragmentPagerAdapter {

    private String[] titles;

    private String[] tabIds = {"1", "17", "31", "47", "59", "72"};

    public MyPagerAdapter(@NonNull FragmentManager fm, String[] titles) {
        super(fm);
        this.titles = titles;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        return TabFragment.newInstance(tabIds[position]);
    }

    @Override
    public int getCount() {
        return titles.length;
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return titles[position];
    }
}

TabFragment

public class TabFragment extends LazyLoadFragment {

    private RecyclerView mRecyclerView;
    private LinearLayout mLlProgress;

    private String mTabId;

    private String url;

    private List<ShopInfo.PDataBean> mDatas = new ArrayList<>();

    private TabAdaper mTabAdaper;

    public static TabFragment newInstance(String tabId) {
        TabFragment tabFragment = new TabFragment();
        Bundle bundle = new Bundle();
        bundle.putString("tabId", tabId);
        tabFragment.setArguments(bundle);
        return tabFragment;
    }

    @Override
    protected int getLayoutID() {
        return R.layout.fragment_tab;
    }

    @Override
    protected void init() {

        mRecyclerView = mRootView.findViewById(R.id.recyclerView);
        mLlProgress = mRootView.findViewById(R.id.ll_progress);

        if (getArguments() != null) {
            mTabId = getArguments().getString("tabId");
            url = "http://mgapp.sitezt.cn/api/info/itemsearch/searchall?search.Keyword=&search.Page=1" +
                    "&search.PageSize=20&search.Sort=0&search.SortOrder=1&search.Category=" + mTabId + "&search.SecondCategory=-1" +
                    "&search.QueryTime=-1";

        }

        Log.e("xyh", "init: " + mTabId);

        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
        mTabAdaper = new TabAdaper(getActivity(), mDatas, R.layout.item_shop);
        mRecyclerView.setAdapter(mTabAdaper);

    }

    @Override
    protected void initData() {
        Log.e("xyh", "initData: " + mTabId);
        loadData();
    }

    private void loadData() {
        mLlProgress.setVisibility(View.VISIBLE);

        OkHttpManager.getInstance().asyncJsonStringByURL(url, new OkHttpManager.StringCallback() {
            @Override
            public void onResponse(String result) {
                mLlProgress.setVisibility(View.GONE);
                //Log.e("xyh", "onResponse: " + result);
                ShopInfo shopInfo = new Gson().fromJson(result, ShopInfo.class);
                mDatas.addAll(shopInfo.getPData());
                mTabAdaper.notifyDataSetChanged();
            }

            @Override
            public void onFailure(IOException e) {
                mLlProgress.setVisibility(View.GONE);
                 //Log.e("xyh", "onFailure: " + e.getMessage());
            }
        });
    }
}

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:id="@+id/ll_progress"
        android:layout_width="130dp"
        android:layout_height="130dp"
        android:gravity="center"
        android:visibility="gone"
        android:layout_marginBottom="25dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:background="#99000000"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />


    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Fragment懒加载方式2

加上FragmentTransaction来控制fragment的hide和show。

在这里插入代码片

Fragment懒加载方式3

多层嵌套后的 Fragment 懒加载实现

LazyLoadFragment2

/**
 * Frgament懒加载
 */
public abstract class LazyLoadFragment2 extends Fragment {

    //当前Fragment是否首次可见,默认是首次可见
    private boolean mIsFirstVisible = true;

    //当前Fragment是否首次不可见
    private boolean mIsFirstInvisible = true;

    //当前Fragment的View是否已经创建
    private boolean isViewCreated = false;

    //当前Fragment的可见状态,一种当前可见,一种当前不可见
    private boolean currentVisibleState = false;

    protected View mRootView;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        // 对于默认 tab 和 间隔 checked tab 需要等到 isViewCreated = true 后才可以通过此通知用户可见
        // 这种情况下第一次可见不是在这里通知 因为 isViewCreated = false 成立,等从别的界面回到这里后会使用 onFragmentResume 通知可见
        // 对于非默认 tab mIsFirstVisible = true 会一直保持到选择则这个 tab 的时候,因为在 onActivityCreated 会返回 false
        if (isViewCreated) {
            if (isVisibleToUser && !currentVisibleState) {
                dispatchUserVisibleHint(true);
            } else if (!isVisibleToUser && currentVisibleState) {
                dispatchUserVisibleHint(false);
            }
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mRootView = inflater.inflate(getLayoutID(), container, false);
        return mRootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        isViewCreated = true;

        init();

        //isHidden()是Fragment是否处于隐藏状态和isVisible()有区别
        // !isHidden() 默认为true,在调用 hide show 的时候可以使用
        //getUserVisibleHint(),Fragement是否可见
        if (!isHidden() && getUserVisibleHint()) {
            //如果Fragment没有隐藏且可见
            //执行分发的方法,三种结果对应自Fragment的三个回调,对应的操作,Fragment首次加载,可见,不可见
            dispatchUserVisibleHint(true);
        }

    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (hidden) {
            dispatchUserVisibleHint(false);
        } else {
            dispatchUserVisibleHint(true);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!mIsFirstVisible) {
            //表示点击home键又返回操作,设置可见状态为ture
            if (!isHidden() && !currentVisibleState && getUserVisibleHint()) {
                dispatchUserVisibleHint(true);
            }
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        // 当前 Fragment 包含子 Fragment 的时候 dispatchUserVisibleHint 内部本身就会通知子 Fragment 不可见
        // 子 fragment 走到这里的时候自身又会调用一遍 ?
        if (currentVisibleState && getUserVisibleHint()) {
            dispatchUserVisibleHint(false);
        }
    }


    /**
     * 统一处理 显示隐藏
     *
     * @param visible
     */
    private void dispatchUserVisibleHint(boolean visible) {
        //当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment getUserVisibleHint = true
        //但当父 fragment 不可见所以 currentVisibleState = false 直接 return 掉
        // 这里限制则可以限制多层嵌套的时候子 Fragment 的分发
        if (!isViewCreated)
            return;
        if (visible && isParentInvisible())
            return;

        //此处是对子 Fragment 不可见的限制,因为 子 Fragment 先于父 Fragment回调本方法 currentVisibleState 置位 false
        // 当父 dispatchChildVisibleState 的时候第二次回调本方法 visible = false 所以此处 visible 将直接返回
        if (currentVisibleState == visible) {
            return;
        }

        currentVisibleState = visible;

        if (visible) {
            if (mIsFirstVisible) {
                mIsFirstVisible = false;
                onFirstUserVisible();
            } else {
                onUserVisible();
            }
            dispatchChildVisibleState(true);
        } else {
            dispatchChildVisibleState(false);
            if (mIsFirstInvisible) {
                mIsFirstInvisible = false;
                onFirstUserInvisible();
            } else {
                onUserInvisible();
            }
        }
    }

    /**
     * 用于分发可见时间的时候父获取 fragment 是否隐藏
     *
     * @return true fragment 不可见, false 父 fragment 可见
     */
    private boolean isParentInvisible() {
        Fragment parentFragment = getParentFragment();
        if (parentFragment instanceof LazyLoadFragment2) {
            LazyLoadFragment2 fragment = (LazyLoadFragment2) parentFragment;
            return !fragment.isSupportVisible();
        } else {
            return false;
        }
    }

    private boolean isSupportVisible() {
        return currentVisibleState;
    }

    /**
     * 当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment 的唯一或者嵌套 VP 的第一 fragment 时 getUserVisibleHint = true
     * 但是由于父 Fragment 还进入可见状态所以自身也是不可见的, 这个方法可以存在是因为庆幸的是 父 fragment 的生命周期回调总是先于子 Fragment
     * 所以在父 fragment 设置完成当前不可见状态后,需要通知子 Fragment 我不可见,你也不可见,
     * <p>
     * 因为 dispatchUserVisibleHint 中判断了 isParentInvisible 所以当 子 fragment 走到了 onActivityCreated 的时候直接 return 掉了
     * <p>
     * 当真正的外部 Fragment 可见的时候,走 setVisibleHint (VP 中)或者 onActivityCreated (hide show) 的时候
     * 从对应的生命周期入口调用 dispatchChildVisibleState 通知子 Fragment 可见状态
     *
     * @param visible
     */
    private void dispatchChildVisibleState(boolean visible) {
        FragmentManager childFragmentManager = getChildFragmentManager();
        List<Fragment> fragments = childFragmentManager.getFragments();
        if (!fragments.isEmpty()) {
            for (Fragment child : fragments) {
                if (child instanceof LazyLoadFragment2 && !child.isHidden() && child.getUserVisibleHint()) {
                    ((LazyLoadFragment2) child).dispatchUserVisibleHint(visible);
                }
            }
        }
    }

    protected abstract int getLayoutID();

    protected abstract void init();

    /**
     * 第一次fragment可见(进行初始化工作)
     */
    public void onFirstUserVisible() {
    }

    /**
     * fragment可见(切换回来或者onResume)
     */
    public void onUserVisible() {
    }

    /**
     * 第一次fragment不可见(不建议在此处理事件)
     */
    public void onFirstUserInvisible() {
    }

    /**
     * fragment不可见(切换掉或者onPause)
     */
    public void onUserInvisible() {
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isViewCreated = false;
        mIsFirstVisible = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        isViewCreated = false;
        mIsFirstVisible = true;
    }
}

使用

public class TabFragment2 extends LazyLoadFragment2 {

    private RecyclerView mRecyclerView;

    private LinearLayout mLlProgress;

    private String mTabId;

    private String url;

    private List<ShopInfo.PDataBean> mDatas = new ArrayList<>();

    private TabAdaper mTabAdaper;

    public static TabFragment2 newInstance(String tabId) {
        TabFragment2 tabFragment = new TabFragment2();
        Bundle bundle = new Bundle();
        bundle.putString("tabId", tabId);
        tabFragment.setArguments(bundle);
        return tabFragment;
    }

    @Override
    protected int getLayoutID() {
        return R.layout.fragment_tab;
    }

    @Override
    protected void init() {

        mRecyclerView = mRootView.findViewById(R.id.recyclerView);
        mLlProgress = mRootView.findViewById(R.id.ll_progress);

        if (getArguments() != null) {
            mTabId = getArguments().getString("tabId");
            url = "http://mgapp.sitezt.cn/api/info/itemsearch/searchall?search.Keyword=&search.Page=1" +
                    "&search.PageSize=20&search.Sort=0&search.SortOrder=1&search.Category=" + mTabId + "&search.SecondCategory=-1" +
                    "&search.QueryTime=-1";

        }

        Log.e("xyh", "init: " + mTabId);

        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
        mTabAdaper = new TabAdaper(getActivity(), mDatas, R.layout.item_shop);
        mRecyclerView.setAdapter(mTabAdaper);

    }

    @Override
    public void onFirstUserVisible() {
        super.onFirstUserVisible();
        Log.e("xyh", "onFirstUserVisible: " + mTabId);
        loadData();
    }

    @Override
    public void onUserVisible() {
        super.onUserVisible();
        Log.e("xyh", "onUserVisible: " + mTabId);

        //如果每次切换到fragment都需要加载数据
        //loadData();
    }

    @Override
    public void onFirstUserInvisible() {
        super.onFirstUserInvisible();
        Log.e("xyh", "onFirstUserInvisible: " + mTabId);
    }

    @Override
    public void onUserInvisible() {
        super.onUserInvisible();
        Log.e("xyh", "onUserInvisible: " + mTabId);
    }


    private void loadData() {
        mLlProgress.setVisibility(View.VISIBLE);

        OkHttpManager.getInstance().asyncJsonStringByURL(url, new OkHttpManager.StringCallback() {
            @Override
            public void onResponse(String result) {
                mLlProgress.setVisibility(View.GONE);
                //Log.e("xyh", "onResponse: " + result);
                ShopInfo shopInfo = new Gson().fromJson(result, ShopInfo.class);
                mDatas.addAll(shopInfo.getPData());
                mTabAdaper.notifyDataSetChanged();
            }

            @Override
            public void onFailure(IOException e) {
                mLlProgress.setVisibility(View.GONE);
            }
        });
    }
}

Fragment新功能,setMaxLifecycle()

Fragment新功能,setMaxLifecycle了解一下

setUserVisibleHint()方法已经过时了,我们点进setUserVisibleHint()方法的源码来看一下,对于过时的方法源码中都会注明使用哪个方法来替代,可以看到注释中写明了使用FragmentTransaction的setMaxLifecycle()方法来替代setUserVisibleHint()方法,这个setMaxLifecycle()方法是什么呢,下面我们就来具体看一下。

在这里插入图片描述

setMaxLifecycle()方法

最新的Fragment代码淘汰了setUserVisibleHint方法,转而支持用setMaxLifecycle方法,setMaxLifecycle言外之意是设置最大生命周期,懂行的人应该知道,Fragment一直都是无法直接设置生命周期,必须通过add、attach、remove、detach、show、hide方法间接干预。

setMaxLifecycle定义在FragmentTransaction中,和之前的add、attach、remove、detach、show、hide等方法是并列关系。

    @NonNull
    public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
            @NonNull Lifecycle.State state) {
        addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
        return this;
    }

setMaxLifecycle()方法,需要传入的参数有两个:fragment和state。fragment不用多说,就是要设置的目标Fragment,不过需要注意的是此时Fragment必须已经被添加到了FragmentManager中,也就是调用了add()方法,否则会抛出异常。state就是Lifecycle中定义的枚举类型,同样需要注意传入的state应该至少为CREATED,换句话说就是只能传入CREATED、STARTED和RESUMED,否则同样会抛出异常。

Lifecycle.State

Lifecycle.State一共有五个状态,最低要求是Lifecycle.State.CREATED,所以该方法可用的参数有CREATED、STARTED、RESUMED。

public enum State {
    DESTROYED,
    INITIALIZED,
    CREATED,
    STARTED,
    RESUMED;
    public boolean isAtLeast(@NonNull State state) {
        return compareTo(state) >= 0;
    }
}

setMaxLifecycle()方法的作用

是为Fragment的状态设置上限,如果当前Fragment的状态已经超过了设置的上限,就会强制被降到相应状态。

由于setMaxLifecycle()方法要求传入的state至少为CREATED,因此我们只需研究CREATED、STARTED和RESUMED这三个状态:

  1. 参数传入Lifecycle.State.CREATED

Lifecycle.State.CREATED对应Fragment的CREATED状态,如果当前Fragment状态低于CREATED,也就是INITIALIZING,那么Fragment的状态会变为CREATED,依次执行onAttach()、onCreate()方法;如果当前Fragment状态高于CREATED,那么Fragment的状态会被强制降为CREATED,以当前Fragment状态为RESUMED为例,接下来会依次执行onPause()、onStop()和onDestoryView()方法。如果当前Fragment的状态恰好为CREATED,那么就什么都不做。

  1. 参数传入Lifecycle.State.STARTED

Lifecycle.State.STARTED对应Fragment的STARTED状态,如果当前Fragment状态低于STARTED,那么Fragment的状态会变为STARTED,以当前Fragment状态为CREATED为例,接下来会依次执行onCreateView()、onActivityCreate()和onStart()方法;如果当前Fragment状态高于STARTED,也就是RESUMED,那么Fragment的状态会被强制降为STARTED,接下来会执行onPause()方法。如果当前Fragment的状态恰好为STARTED,那么就什么都不做。

  1. 参数传入Lifecycle.State.RESUMED

Lifecycle.State.RESUMED对应Fragment的RESUMED状态,如果当前Fragment状态低于RESUMED,那么Fragment的状态会变为RESUMED,以当前Fragment状态为STARTED为例,接下来会执行onResume()方法。如果当前Fragment的状态恰好为RESUMED,那么就什么都不做。

光介绍结论可能不是很直观,下面就写几个例子。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

FragmentPagerAdapter变动

由于setMaxLifecycle带来了生命周期设置,替换掉了老旧的setUserVisibleHint方法,所以在FragmentPagerAdapter中也进行了适配。

public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;

private final int mBehavior;

public FragmentPagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}

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

可以看出一个参数的构造方法默认传入BEHAVIOR_SET_USER_VISIBLE_HINT,将其赋值给mBehavior,那么这个mBehavior在什么地方用到了呢。在FragmentPagerAdapter.java文件中全局搜索一下,发现只有两个地方用到了mBehavior:instantiateItem()方法和setPrimaryItem()方法。instantiateItem()方法我们很熟悉,是初始化ViewPager中每个Item的方法,setPrimaryItem()方法我此前没有接触过,简单地看了一下源码发现它的作用是设置ViewPager将要显示的Item,在ViewPager切换时会调用该方法,我们来看一下FragmentPagerAdapter中的setPrimaryItem()方法:

  @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                    if (mCurTransaction == null) {
                        mCurTransaction = mFragmentManager.beginTransaction();
                    }
                     // 如果mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,则将上一个Fragment的状态设置为STARTED
                    mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
                } else {
                    mCurrentPrimaryItem.setUserVisibleHint(false);
                }
            }
            fragment.setMenuVisibility(true);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                 // 如果mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,则将当前Fragment的状态设置为RESUMED
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
            } else {
                fragment.setUserVisibleHint(true);
            }

            mCurrentPrimaryItem = fragment;
        }
    }

方法的逻辑还是很简单的,如果mBehavior的值为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,那么就调用setMaxLifecycle()方法将上一个Fragment的状态设置为STARTED,将当前要显示的Fragment的状态设置为RESUMED;反之如果mBehavior的值为BEHAVIOR_SET_USER_VISIBLE_HINT,那么依然使用setUserVisibleHint()方法设置Fragment的可见性,相应地可以根据getUserVisibleHint()方法获取到Fragment是否可见,从而实现懒加载,具体做法我就不说了。

Fragment懒加载新方案

androidx中的Fragment懒加载方案

setUserVisibleHint()方法已经过时了,过去使用setUserVisibleHint来控制Fragment懒加载,在最新版的FragmentPagerAdapter里有新思路,可以切换到BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT模式,在FragmentonResume里判断,更符合显示逻辑。

在Fragment变为可见时都会执行onResume()方法,我们可以利用这一点来实现懒加载,将Fragment加载数据的逻辑放到onResume()方法中,这样就保证了Fragment可见时才会加载数据。声明一个变量标记是否是首次执行onResume()方法,因为每次Fragment由不可见变为可见都会执行onResume()方法,需要防止数据的重复加载。此外,如果我们使用的是FragmentPagerAdapter,切换导致Fragment被销毁时是不会执行onDestory()和onDetach()方法的,只会执行到onDestroyView()方法,因此在onDestroyView()方法中我们还需要将这个变量重置,否则当Fragment再次可见时就不会重新加载数据了。

/**
 * setMaxLifecycle()方式懒加载
 */
public abstract class LazyLoadFragment3 extends Fragment {

    protected Context mContext;

    protected View mRootView;

    private boolean mIsFirstLoad = true; // 是否第一次加载


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getContext();
    }

    //在Fragment每次变为可见时都会执行onResume()方法,我们可以利用这一点来实现懒加载
    @Override
    public void onResume() {
        super.onResume();
        if (mIsFirstLoad) {
            initData();
            mIsFirstLoad = false;
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mRootView = inflater.inflate(getLayoutID(), container, false);
        return mRootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        init();
    }


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

        mIsFirstLoad = true;
    }

    protected abstract int getLayoutID();

    protected abstract void init();

    protected abstract void initData();
}

public class MainActivity4 extends AppCompatActivity {

    private TabLayout mTabLayout;
    private ViewPager mViewPager;

    private String[] titles = {"女装", "美食", "美妆", "居家用品", "男装", "鞋品"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
        mViewPager = (ViewPager) findViewById(R.id.viewPager);

        MyPagerAdapter4 myPagerAdapter = new MyPagerAdapter4(getSupportFragmentManager(), titles);
        mViewPager.setAdapter(myPagerAdapter);

        mViewPager.setOffscreenPageLimit(titles.length);
        mTabLayout.setupWithViewPager(mViewPager);
    }
}

在构造FragmentPagerAdapter时传入:BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

public class MyPagerAdapter4 extends FragmentPagerAdapter {

    private String[] titles;

    private String[] tabIds = {"1", "17", "31", "47", "59", "72"};

    public MyPagerAdapter4(@NonNull FragmentManager fm, String[] titles) {
        super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        this.titles = titles;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        return TestFragment4.newInstance(tabIds[position]);
    }

    @Override
    public int getCount() {
        return titles.length;
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return titles[position];
    }
}

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值