在到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; }
@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; }
@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; } }
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