zoukankan      html  css  js  c++  java
  • Androidx Fragment 懒加载机制实现

    在到Androidx之前我们使用support提供的Fragment的懒加载机制,基本上使用的是在setUserVisible + onHiddenChanged 这两个函数。但是在Androidx下setUserVisible已经被Google官方弃用了,推荐我们使用Fragment.setMaxLifecyCle()的方式来处理Fragment的懒加载。

    一、Androidx增加FragmentTransaction.setMaxLifecycle方法控制最大生命周期

    Google在Androidx的FragmentTransaction中增加了setMaxLifecycle方法来控制Fragment所能调用的最大的声明周期函数。如下图所示:

       /**
         * 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;
        }

    可以看到,该方法可以设置活跃状态下Fragment的最大状态,如果该Fragment超过了设置的最大状态,那么会强制将Fragment降级到正确的状态。

    二、Fragment 在 add+show+hide 模式下的懒加载实现

    将需要显示的 Fragment ,在调用 add 或 show 方法后,setMaxLifecycle(showFragment, Lifecycle.State.RESUMED).

    将需要隐藏的 Fragment ,在调用 hide 方法后,setMaxLifecycle(fragment, Lifecycle.State.STARTED).

    切换时的代码如下:

    fragmentManager.beginTransaction().apply {
        for (index in fragments.indices) {
            val fragment = fragments[index]
            add(containerViewId, fragment, fragment.javaClass.name)
            if (showPosition == index) {
                setMaxLifecycle(fragment, Lifecycle.State.RESUMED)
            } else {
                hide(fragment)
                setMaxLifecycle(fragment, Lifecycle.State.STARTED)
            }
        }
    
    }.commit()

    其中Fragment的代码如下:

    abstract class LazyFragment : Fragment() {
    
        private var isLoaded = false
    
        override fun onResume() {
            super.onResume()
            //增加了Fragment是否可见的判断
            if (!isLoaded && !isHidden) {
                lazyInit()
                Log.d(TAG, "lazyInit:!!!!!!!”)
                isLoaded = true
            }
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
            isLoaded = false
        }
    
        abstract fun lazyInit()
    }

    此实现方案,在较复杂的Fragment嵌套模式下,也能保证正常的懒加载的实现。

    三、ViewPager + Fragment 模式下的懒加载实现

    在Androidx下,FragmentPagerAdapter、FragmentStatePagerAdapter 类新增了含有behavior的字段的构造函数,并舍弃了FragmentPagerAdapter(@NonNull FragmentManager fm)方法。

      @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;
      }
    
      public FragmentStatePagerAdapter(@NonNull FragmentManager fm, @Behavior int behavior) {
            mFragmentManager = fm;
            mBehavior = behavior;
      }
    其中 Behavior 的类型如下:
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
        private @interface 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:当 Fragment 对用户的可见状态发生改变时,setUserVisibleHint 方法会被调用。
    • BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:那么当前选中的 Fragment 在 Lifecycle.State#RESUMED 状态 ,其他不可见的 Fragment 会被限制在 Lifecycle.State#STARTED 状态。

    使用 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 后,然后Fragment 继承使用 LazyFragment 后,即可实现Androidx下的ViewPager+Fragment的懒加载机制。

    这里我们探究一下设置了 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 能实现懒加载的原因。通过看源码可以发现,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;
            }
        }

    四、FragmentPagerAdapter 和 FragmentStatePagerAdapter 区别

    1. FragmentPagerAdapter

    这两个Adapter都能配合ViewPager实现Fragment的加载。下面我们来比较一下这两个类在实现及使用机制上有什么区别,主要从加载和销毁两方面来进行分析。

    FragmentPagerAdapter加载Fragment的方法为:instantiateItem,源码如下:

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
    
            final long itemId = getItemId(position);
    
            // Do we already have this fragment?
            String name = makeFragmentName(container.getId(), itemId);
            Fragment fragment = mFragmentManager.findFragmentByTag(name);
            if (fragment != null) {
                if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
                mCurTransaction.attach(fragment);
            } else {
                fragment = getItem(position);
                if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
                mCurTransaction.add(container.getId(), fragment,
                        makeFragmentName(container.getId(), itemId));
            }
            if (fragment != mCurrentPrimaryItem) {
                fragment.setMenuVisibility(false);
                if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                    mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
                } else {
                    fragment.setUserVisibleHint(false);
                }
            }
    
            return fragment;
        }
    可以看出来,在instantiateItem方法中,主要是将Fragment添加到FragmentManager中。未添加到FragmentManager中的执行add操作,已添加到FragmentManager中的只进行attach操作。
    FragmentPagerAdapter 销毁 Fragment的方法为:instantiateItem。
        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            Fragment fragment = (Fragment) object;
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.detach(fragment);
            if (fragment == mCurrentPrimaryItem) {
                mCurrentPrimaryItem = null;
            }
        }

    在destroyItem方法中,只是进行detach操作。detach操作并不会将Fragment销毁,Fragment依旧是由FragmentManager进行管理。

    2. FragmentStatePagerAdapter

    FragmentStatePagerAdapter的加载和销毁的方法名同FragmentPagerAdapter。下面我们先将代码放出来:

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            // If we already have this item instantiated, there is nothing
            // to do.  This can happen when we are restoring the entire pager
            // from its saved state, where the fragment manager has already
            // taken care of restoring the fragments we previously had instantiated.
            if (mFragments.size() > position) {
                Fragment f = mFragments.get(position);
                if (f != null) {
                    return f;
                }
            }
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
    
            Fragment fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
            if (mSavedState.size() > position) {
                Fragment.SavedState fss = mSavedState.get(position);
                if (fss != null) {
                    fragment.setInitialSavedState(fss);
                }
            }
            while (mFragments.size() <= position) {
                mFragments.add(null);
            }
            fragment.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
                fragment.setUserVisibleHint(false);
            }
    
            mFragments.set(position, fragment);
            mCurTransaction.add(container.getId(), fragment);
    
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            }
    
            return fragment;
        }
        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            Fragment fragment = (Fragment) object;
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                    + " v=" + ((Fragment)object).getView());
            while (mSavedState.size() <= position) {
                mSavedState.add(null);
            }
            mSavedState.set(position, fragment.isAdded()
                    ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
            mFragments.set(position, null);
    
            mCurTransaction.remove(fragment);
            if (fragment == mCurrentPrimaryItem) {
                mCurrentPrimaryItem = null;
            }
        }
    可以看出来:FragmentStatePagerAdapter是通过一个mFragments数组来存储fragment的,通过mSavedState数组来存储fragment销毁时的状态,通过position获取到的fragment可能为空(被回收),如果为空,则会再次调用getItem方法重新创建新的fragment,然后将mSavedState中存储的状态重新赋予这个新的fragment, 达到fragment恢复的效果。当item在页面中不可见时,该fragment的状态会先被保存到mSavedState中,而fragment实例则会被销毁。

    3. 异同比较

    相同点:

    a). 两者都会保持当前item(即fragment)和前后的item的状态。

    b). 显示当前item的同时,Adapter会提前初始化后一个item,并把当前item的前一个item保存在内存中。

    不同点:

    fragment 存储、恢复、销毁 的方式不同,FragmentStatePagerAdapter会完全销毁滑动过去的item,当需要初始化的时候,会重新初始化页面。FragmentPagerAdapter 则会保留页面的状态,并不会完全销毁掉。

    4. 如何选择

    当Viewpager中fragment数量多的时候,为保证性能推荐使用FragmentStatePagerAdapter,反之则推荐使用FragmentPagerAdapter。

    五、参考资料

    1. https://www.jianshu.com/p/2201a107d5b5

    2. https://www.jianshu.com/p/a778649c254d

  • 相关阅读:
    C#文件下载(实现断点续传)
    C#winform实现跑马灯
    Asp.net GridView转换成DataTable
    SQL Server 索引重建脚本
    SQL SERVER数据库维护与重建索引
    python try except 出现异常时,except 中如何返回异常的信息字符串
    UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 0: invalid continuation byte
    bower 安装依赖提示 EINVRES Request to https://bower.herokuapp.com/packages/xxx failed with 502
    EINVRES Request to https://bower.herokuapp.com/packages/ failed with 502
    使用notepad++插件远程编辑linux下的配置文件
  • 原文地址:https://www.cnblogs.com/renhui/p/13294883.html
Copyright © 2011-2022 走看看