添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

我们点进 setUserVisibleHint() 方法的源码来看一下,对于过时的方法源码中都会注明使用哪个方法来替代:

* Set a hint to the system about whether this fragment's UI is currently visible * to the user. This hint defaults to true and is persistent across fragment instance * state save and restore. * <p>An app may set this to false to indicate that the fragment's UI is * scrolled out of visibility or is otherwise not directly visible to the user. * This may be used by the system to prioritize operations such as fragment lifecycle updates * or loader ordering behavior.</p> * <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle. * and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p> * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default), * false if it is not. * @deprecated Use {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)} * instead. @Deprecated public void setUserVisibleHint(boolean isVisibleToUser) { if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager != null && isAdded() && mIsCreated) { mFragmentManager.performPendingDeferredStart(this); mUserVisibleHint = isVisibleToUser; mDeferStart = mState < STARTED && !isVisibleToUser; if (mSavedFragmentState != null) { // Ensure that if the user visible hint is set before the Fragment has // restored its state that we don't lose the new value mSavedUserVisibleHint = isVisibleToUser;

可以看到注释中写明了使用FragmentTransaction的 setMaxLifecycle() 方法来替代 setUserVisibleHint() 方法,这个 setMaxLifecycle() 方法是什么呢,下面我们就来具体看一下。

setMaxLifecycle()方法

* Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is * already above the received state, it will be forced down to the correct state. * <p>The fragment provided must currently be added to the FragmentManager to have it's * Lifecycle state capped, or previously added as part of this transaction. The * {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise * an {@link IllegalArgumentException} will be thrown.</p> * @param fragment the fragment to have it's state capped. * @param state the ceiling state for the fragment. * @return the same FragmentTransaction instance @NonNull public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment, @NonNull Lifecycle.State state) { addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state)); return this;

setMaxLifecycle() 方法定义在 FragmentTransaction 类中,它的内部逻辑很简单,其实我们经常使用的 add() remove() show() hide() 等方法也是类似的逻辑,将操作封装为一个Op对象,最后调用 commit() 方法时再根据Op对象执行对应的操作。
注释中提到 setMaxLifecycle() 方法的作用是为Fragment的状态设置上限,如果当前Fragment的状态已经超过了设置的上限,就会强制被降到相应状态。在弄清楚上面这段文字的意义之前我首先要介绍两个相关概念: Fragment的状态 Lifecycle的状态

  • Fragment的状态
  • 在Fragment类中定义了5个int常量,表示Fragment的状态值:

    static final int INITIALIZING = 0;     // Not yet created.
    static final int CREATED = 1;          // Created.
    static final int ACTIVITY_CREATED = 2; // Fully created, not started.
    static final int STARTED = 3;          // Created and started, not resumed.
    static final int RESUMED = 4;          // Created started and resumed.
    
  • Lifecycle的状态
  • Lifecycle是Android Jetpack中的架构组件之一,用于帮助我们方便地管理Activity和Fragment的生命周期,关于Lifecycle的详细介绍和使用网上有很多文章,我这里就不说了,如果此前没有接触过可以自行了解一下哈。
    在Lifecycle定义了一个枚举类State

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

    可以看出Lifecycle中同样定义了5个状态,不过这里的状态和Fragment中定义的状态还是有一些区别的。
    回到setMaxLifecycle()方法,需要传入的参数有两个:fragment和state。fragment不用多说,就是要设置的目标Fragment,不过需要注意的是此时Fragment必须已经被添加到了FragmentManager中,也就是调用了add()方法,否则会抛出异常。state就是Lifecycle中定义的枚举类型,同样需要注意传入的state应该至少为CREATED,换句话说就是只能传入CREATED、STARTED和RESUMED,否则同样会抛出异常。
    看到这里可能会产生一个疑问,传入的state是Lifecycle中定义的,怎样转换为对Fragment状态的限制呢,上面也看到了,这两个状态还是有区别的。而且状态这个概念平时可能接触得不多,下面就以我们最熟悉的生命周期方法来说明这个状态的限制,先上一张图总结一下结论:

    Fragment状态与Lifecycle.State以及生命周期方法的关系

    图中展示了Fragment状态间切换会执行的生命周期方法以及Lifecycle.State对应的Fragment状态,由于setMaxLifecycle()方法要求传入的state至少为CREATED,因此我们只需研究CREATEDSTARTEDRESUMED这三个状态,结合上图解释一下setMaxLifecycle()方法的作用。

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

  • 参数传入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,那么就什么都不做。

  • 参数传入Lifecycle.State.RESUMED
  • Lifecycle.State.RESUMED对应Fragment的RESUMED状态,如果当前Fragment状态低于RESUMED,那么Fragment的状态会变为RESUMED,以当前Fragment状态为STARTED为例,接下来会执行onResume()方法。如果当前Fragment的状态恰好为RESUMED,那么就什么都不做。
    光介绍结论可能不是很直观,下面就以几个常见的例子来验证一下上图的结论。

  • 传入Lifecycle.State.CREATED
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    homefragment = new HomeFragment();
    ft.add(R.id.fl_container, homefragment);
    ft.setMaxLifecycle(homefragment, Lifecycle.State.CREATED);
    ft.commit();
    

    Fragment生命周期方法执行如下:

    可以看出,在调用setMaxLifecycle()方法传入了Lifecycle.State.CREATED后,会限制Fragment的状态为CREATED,因此只会执行到onCreate()方法,而不会执行后面的onCreateView()-onResume()方法。
    再来看“降级”的例子,调用setMaxLifecycle()之前已经调用过add()方法将Fragment添加到了FragmentManager中,此时Fragment的状态为RESUMED

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.setMaxLifecycle(homefragment, Lifecycle.State.CREATED);
    ft.commit();
    

    Fragment生命周期方法执行如下:

    可以看出此时Fragment的状态会降为CREATED,依次执行onPause()onStop()onDestroyView()方法。

  • 传入Lifecycle.State.STARTED
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    homefragment = new HomeFragment();
    ft.add(R.id.fl_container, homefragment);
    ft.setMaxLifecycle(homefragment, Lifecycle.State.STARTED);
    ft.commit();
    

    Fragment生命周期方法执行如下:

    可以看出,在调用setMaxLifecycle()方法传入了Lifecycle.State.STARTED后,会限制Fragment的状态为STARTED,因此只会执行到onStart()方法,而不会执行后面的onResume()方法。
    同样看一下“降级”的例子,调用setMaxLifecycle()之前已经调用过add()方法将Fragment添加到了FragmentManager中,此时Fragment的状态为RESUMED

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.setMaxLifecycle(homefragment, Lifecycle.State.STARTED);
    ft.commit();
    

    Fragment生命周期方法执行如下:

    在上一个例子的基础上执行下面的代码:

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.setMaxLifecycle(homefragment, Lifecycle.State.RESUMED);
    ft.commit();
    

    Fragment生命周期方法执行如下:

    可以看出,在调用setMaxLifecycle()方法传入了Lifecycle.State.RESUMED后,会限制Fragment的状态为RESUMED,由于当前Fragment的状态为STARTED,接下来会执行onResume()方法。

    懒加载的新方案

    此前Fragment的懒加载都是通过setUserVisibleHint()方法来实现,既然官方提出了setMaxLifecycle()方法来替代setUserVisibleHint()方法,那么肯定也提供了懒加载的新方案,这次的切入点在FragmentPagerAdapter中,我们会发现之前继承自FragmentPagerAdapter的构造方法同样过时了。

    同样地,我们点进这个构造方法看一下:

    * Constructor for {@link FragmentPagerAdapter} that sets the fragment manager for the adapter. * This is the equivalent of calling {@link #FragmentPagerAdapter(FragmentManager, int)} and * passing in {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}. * <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the * current Fragment changes.</p> * @param fm fragment manager that will interact with this adapter * @deprecated use {@link #FragmentPagerAdapter(FragmentManager, int)} with * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} @Deprecated public FragmentPagerAdapter(@NonNull FragmentManager fm) { this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);

    可以看到方法内部又调用了两个参数的构造方法,注释中也写明了要使用两个参数的构造方法,我们来看一下这个新的构造方法:

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

    多了一个int类型的参数behavior,可选的值有以下两个:

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

    可以看出一个参数的构造方法默认传入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是否可见,从而实现懒加载,具体做法我就不说了。
    下面我们来具体看一下mBehavior为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT的情况,首先在ViewPager的Adapter中调用两个参数的构造方法:

    public MyPagerAdapter(@NonNull FragmentManager fm, int behavior, List<Fragment> fragments) {
        super(fm, behavior);
        this.fragments = fragments;
    

    然后在构造Adapter对象的时候behavior参数传入FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

    mAdapter = new MyPagerAdapter(getSupportFragmentManager(),FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,mFragments);
    

    其他的代码就不展示了,这里我给ViewPager添加了三个Fragment,并调用了setOffscreenPageLimit(3)来预加载所有的Fragment,下面我们就来看一下几种情况下三个Fragment生命周期方法的执行情况。

    默认情况下显示第一个Fragment

    可以看出第一个Fragment此时可见,执行到了onResume()方法,即RESUMED状态,第二个和第三个Fragment此时不可见,均执行到onStart()方法,即STARTED状态。

    从第一个Fragment切换到第二个Fragment

    可以看出第一个Fragment此时变为不可见,由于调用setMaxLifecycle()方法传入了Lifecycle.State.STARTED,因此会从RESUMED状态变为STARTED状态,执行onPause()方法;第二个Fragment由不可见变为可见,由于调用setMaxLifecycle()方法传入了Lifecycle.State.RESUMED,因此会从STARTED状态变为RESUMED状态,执行onResume()方法。

    从第二个Fragment切换到第三个Fragment

    和上一个情况相同,第二个Fragment变为不可见,执行onPause()方法,状态变为STARTED;第三个Fragment变为可见,执行onResume()方法,状态变为RESUMED
    之后在几个Fragment之间切换的情况也是一样的,变为不可见的Fragment执行onPause()方法,变为可见的Fragment执行onResume()方法。
    从以上几种情况中我们不难发现,在Fragment变为可见时都会执行onResume()方法,我们可以利用这一点来实现懒加载,基本思路有两点:

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

    public abstract class NewLazyFragment extends Fragment {
        private Context mContext;
        private boolean isFirstLoad = true; // 是否第一次加载
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mContext = getActivity();
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view = LayoutInflater.from(mContext).inflate(getContentViewId(), null);
            initView(view);
            return view;
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            isFirstLoad = true;
        @Override
        public void onResume() {
            super.onResume();
            if (isFirstLoad) {
                // 将数据加载逻辑放到onResume()方法中
                initData();
                initEvent();
                isFirstLoad = false;
         * 设置布局资源id
         * @return
        protected abstract int getContentViewId();
         * 初始化视图
         * @param view
        protected void initView(View view) {
         * 初始化数据
        protected void initData() {
         * 初始化事件
        protected void initEvent() {
    

    本文主要介绍了androidx包中setMaxLifecycle()方法的使用和作用,它用于替代setUserVisibleHint()方法,可以设置Fragment的状态,由此引出了实现Fragment懒加载的新方案,在构造FragmentPagerAdapter时传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,将加载数据的逻辑放到Fragment的onResume()方法中即可。

  •