上周在《学不动也要学!深刻了解ViewPager2》一篇文章的留言中,你们广泛对于Fragment懒加载的问题比较关心。其实,对于Fragment懒加载问题的处理由来已久,网上不乏相关的优秀文章。可是,因为Fragment生命周期的缘由使得懒加载问题的处理并非那么的优雅。显然,Google也意识到了问题所在。所以,在Androidx的库中对于Fragment的生命周期状态的控制进行了深度优化,使得咱们更容易的去管控Fragment的生命周期,也使得咱们更容易的去处理懒加载问题。可是,前提条件是咱们要了解Google对于Fragment作了哪些优化。那么就让咱们借此机会一块儿来探究一下吧!( 懒加载称做延迟加载我以为更贴切一些,因此下文就统称为延迟加载了。 )java
虽然本篇文章是对于Fragment新特性的探究,可是我以为写文章总要有个因果。也为了照顾一下还不太了解什么是延迟加载的同窗。咱们仍是先来了解一下延迟加载,顺便回顾一下Fragment延迟加载的旧方案。android
首先,咱们要搞清楚一个问题。“Fragment延迟加载“中的“延迟”并不指的是延迟加载Fragment,而是延迟加载Fragment中的数据。对于Fragment的使用一般咱们会结合ViewPager。在《学不动也要学!深刻了解ViewPager2》一文中咱们也提到ViewPager的预加载问题。ViewPager会默认在当前页面的左右两边至少预加载一个页面以保证ViewPager的流畅性。咱们假设在ViewPager的全部Fragment中都存在网络请求。当咱们打开这个页面的时候因为ViewPager的预加载缘由,即便在其它Fragment不可见的状况下也会去进行网络请求加载数据。而若是此时用户根本就没有去滑动ViewPager就退出了应用或者切换到了其余页面。那么对于这个不可见的Fragment中的网络请求岂不是既浪费了流量也浪费了手机和服务器的性能?git
那么此时有的同窗就有问题了。你就不能在Fragment显示的时候去加载数据吗?问的好!在解答以前咱们先来看下Fragment的生命周期 github
经过上一小节的分析咱们知道想要在Fragment的生命周期中处理延迟加载的问题显然是走不通的。因此想要处理Fragment的延迟加载就须要另想它法了。还好,在Fragment中为咱们提供了一个setUserVisibleHint(isVisibleToUser: Boolean)的方法,这个方法中有一个isVisibleToUser的boolean类型的参数,其意义表示当前的Fragment是否对用户可见。所以,对于Fragment的延迟加载咱们即可以利用这个方法中展开。 既然要使用setUserVisibleHint(isVisibleToUser: Boolean)那么就应该知道这个方法的调用时机。咱们写一个ViewPager嵌套Fragment的例子来打印下日志:缓存
可见该方法是在Fragment的onAttach以前就已经被调用了。所以,对于延迟加载咱们能够在setUserVisibleHint(isVisibleToUser: Boolean)方法及onViewCreated(view: View, savedInstanceState: Bundle?)添加标志位来控制是否加载数据。咱们来看下代码:服务器
abstract class BaseLazyFragment : Fragment() {
/** * 当前Fragment状态是否可见 */
private var isVisibleToUser: Boolean = false
/** * 是否已建立View */
private var isViewCreated: Boolean = false
/** * 是否第一次加载数据 */
private var isFirstLoad = true
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
this.isVisibleToUser = isVisibleToUser
onLazyLoad()
}
private fun onLazyLoad() {
if (isVisibleToUser && isViewCreated && isFirstLoad) {
isFirstLoad = false
lazyLoad()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isViewCreated = true
onLazyLoad()
}
protected abstract fun lazyLoad()
}
复制代码
咱们经过在Fragment中添加了三个标志位实现了延迟加载的功能。咱们到TestFragment尝试一下:网络
class TestFragment : BaseLazyFragment() {
private var position: Int = 0
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
val bundle = arguments
position = bundle!!.getInt(KEY_POSITION)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val cardView = CardView(inflater, container)
cardView.bind(Card.fromBundle(arguments!!),position)
return cardView.view
}
companion object {
private const val KEY_POSITION = "position"
fun getInstance(card: Card, position: Int): TestFragment {
val fragment = TestFragment()
val bundle = card.toBundle()
bundle.putInt(KEY_POSITION, position)
fragment.arguments = bundle
return fragment
}
}
override fun lazyLoad() {
showToast("Fragment$position is loading data")
}
private fun showToast(content: String) {
Toast.makeText(context, content, Toast.LENGTH_SHORT).show()
}
}
复制代码
咱们来看下效果: app
上一节中咱们讲到由于ViewPager的预加载机制以及Fragment的生命周期没法得以控制,咱们不得不经过setUserVisibleHint(isVisibleToUser: Boolean)和onViewCreated(view: View, savedInstanceState: Bundle?)方法以及添加三个标志位来处理延迟加载,显然这样的代码并不优雅。ide
当咱们将Android项目迁移到Androidx并将androidx版本升级到1.1.0以后发现,咱们第一节中用到的setUserVisibleHint(isVisibleToUser: Boolean)方法已被标记为废弃了! post
/** * ... 省略其它注释 * @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(Fragment, Lifecycle.State)方法来替换setUserVisibleHint方法。setMaxLifecycle实在Androidx 1.1.0中新增长的一个方法。setMaxLifecycle从名字上来看意思是设置一个最大的生命周期,由于这个方法是在FragmentTransaction中,所以咱们能够知道应该是为Fragment来设置一个最大的生命周期。咱们来看下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;
}
复制代码
这个方法接收一个Fragment参数和一个Lifecycle的状态参数。Lifecycle是jetpack中很重要的一个库,它具备对Activity和Fragment生命周期感知能力,相信不少同窗都应该对Lifecycle都略知一二。在Lifecycle的State中定义了五种生命周期状态,以下:
public enum State {
/** * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch * any more events. For instance, for an {@link android.app.Activity}, this state is reached * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call. */
DESTROYED,
/** * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is * the state when it is constructed but has not received * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet. */
INITIALIZED,
/** * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached in two cases: * <ul> * <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call; * <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call. * </ul> */
CREATED,
/** * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached in two cases: * <ul> * <li>after {@link android.app.Activity#onStart() onStart} call; * <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call. * </ul> */
STARTED,
/** * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached after {@link android.app.Activity#onResume() onResume} is called. */
RESUMED;
/** * Compares if this State is greater or equal to the given {@code state}. * * @param state State to compare with * @return true if this State is greater or equal to the given {@code state} */
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
复制代码
而在setMaxLifecycle中接收的生命周期状态要求不能低于CREATED,不然会抛出一个IllegalArgumentException的异常。当传入参数为DESTROYED或者INITIALIZED时则会抛出以下图的异常:
咱们先来看下在不设置setMaxLifecycle的时候添加一个Fragment的状态,以便和后边的状况进行对比。首先咱们在Activity中添加一个Fragment,代码以下:
fragment = TestLifecycleFragment.getInstance(Card.DECK[0], 0)
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(R.id.ll_fragment, fragment)
fragmentTransaction.commit()
复制代码
启动Activity,咱们将该Fragment生命周期的日志打印出来以下:
接下来,咱们将maxLifecycle设置为CREATED:
fragment = TestLifecycleFragment.getInstance(Card.DECK[0], 0)
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(R.id.ll_fragment, fragment)
fragmentTransaction.setMaxLifecycle(fragment, Lifecycle.State.CREATED)
fragmentTransaction.commit()
复制代码
再来看日志输出:
那么如今问题来了,假设Fragment已经执行到了onResume,此时再为Fragment设置一个CREATED的最大生命周期会出现什么样的状况呢?咱们经过日志来验证一下:
接下来,咱们将maxLifecycle设置为STARTED:
fragment = TestLifecycleFragment.getInstance(Card.DECK[0], 0)
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(R.id.ll_fragment, fragment)
fragmentTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED)
fragmentTransaction.commit()
复制代码
日志输出以下:
一样,假设Fragment已经执行到了onResume方法再为其设置最大生命周期为STARTED会怎样呢?来看日志:
最后,咱们将maxLifecycle设置为RESUMED:
fragment = TestLifecycleFragment.getInstance(Card.DECK[0], 0)
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(R.id.ll_fragment, fragment)
fragmentTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED)
fragmentTransaction.commit()
复制代码
而对于已经执行了onResume后的Fragment,再去设置最大生命周期为RESUMED会怎么样呢?由于当前Fragment已是RESUMED状态了,因此不会再去执行任何代码。
到这里咱们能够得出一个结论:
经过setMaxLifecycle方法能够精确控制Fragment生命周期的状态,若是Fragment的生命周期状态小于被设置的最大生命周期,则当前Fragment的生命周期会执行到被设置的最大生命周期,反之,若是Fragment的生命周期状态大于被设置的最大生命周期,那么则会回退到被设置的最大生命周期。
有了这一结论,在ViewPager中即可以对Fragment的生命周期进行控制,以此来更方便的实现延迟加载功能了。
经过上一小节的分析咱们知道了能够经过setMaxLifecycle来设置Fragment的最大生命周期,从而能够实现ViewPager中Fragment的延迟加载。固然,关于生命周期状态处理的操做无需咱们本身实现,在Androidx 1.1.0版本中的FragmentStatePagerAdapter已经帮咱们实现了,只须要在使用时候传进去相应的参数便可。
FragmentStatePagerAdapter的构造方法接收两个参数,以下:
/** * Constructor for {@link FragmentStatePagerAdapter}. * * If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current * Fragment is in the {@link Lifecycle.State#RESUMED} state, while all other fragments are * capped at {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is * passed, all fragments are in the {@link Lifecycle.State#RESUMED} state and there will be * callbacks to {@link Fragment#setUserVisibleHint(boolean)}. * * @param fm fragment manager that will interact with this adapter * @param behavior determines if only current fragments are in a resumed state */
public FragmentStatePagerAdapter(@NonNull FragmentManager fm, @Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
复制代码
第一个FragmentManager 参数没必要多说,第二个参数时一个枚举类型的Behavior参数,其可选值以下:
@Retention(RetentionPolicy.SOURCE)
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
private @interface Behavior { }
复制代码
当behavior为BEHAVIOR_SET_USER_VISIBLE_HINT时,Fragment改变的时候,setUserVisibleHint方法会被调用,也就是这个参数实际上是为了兼容之前的老代码。而且BEHAVIOR_SET_USER_VISIBLE_HINT参数已经被置为废弃。因此咱们的可选参数只剩下了BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT。
当behavior为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时意味着只有当前显示的Fragment会被执行到onResume,而其它Fragment的生命周期都只会执行到onStart.
这一功能时如何实现的呢?咱们追随BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT的脚步找到了setPrimaryItem方法,这个方法的做用是设置ViewPager当前显示的Item,其源码以下:
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();
}
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();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
复制代码
这段代码很是简单易懂,mCurrentPrimaryItem是当前正在显示的item,fragment是接下来要显示的item。能够看到当mBehavior 为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时,mCurrentPrimaryItem的最大生命周期被设置为了STARTED,而fragment的最大生命周期则被设置为了RESUMED。而当mBehavior为BEHAVIOR_SET_USER_VISIBLE_HINT时仍然会调用setUserVisibleHint方法,这种状况就再也不讨论,由于BEHAVIOR_SET_USER_VISIBLE_HINT也已经被废弃掉了。 那么咱们着重来分析一下BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时的状况:
mCurrentPrimaryItem是当前显示的Fragment,因此该Fragment必然已经执行到了onResume方法,而此时为其设置了最大生命周期STARTED,那么mCurrentPrimaryItem必然会执行onPause退回到STARTED状态。 而fragment当前生命周期状态为onStart,当为其设置了RESUME的最大生命周期状态后,fragment必然会执行onResume方法进入RESUMED状态。
知道了这一结论后,咱们再去进行懒加载的控制是否是就异常简单了?此时咱们只须要一个flag去标志是不是第一次加载数据就能够了。所以,懒加载的实现能够以下:
abstract class TestLifecycleFragment : Fragment() {
private var isFirstLoad = true
override fun onResume() {
super.onResume()
if (isFirstLoad) {
isFirstLoad = false
loadData()
}
}
abstract fun loadData()
}
复制代码
上篇文章《学不动也要学!深刻了解ViewPager2》中咱们对ViewPager2有了比较深刻的了解,其中在讲解ViewPager2的offScreenPageLimit时候得出过这样一个结论:
ViewPager2的offScreenPageLimit默认值为OFFSCREEN_PAGE_LIMIT_DEFAULT,当setOffscreenPageLimit为OFFSCREEN_PAGE_LIMIT_DEFAULT时候会使用RecyclerView的缓存机制。默认只会加载当前显示的Fragment,而不会像ViewPager同样至少预加载一个item.当切换到下一个item的时候,当前Fragment会执行onPause方法,而下一个Fragment则会从onCreate一直执行到onResume。当再次滑动回第一个页面的时候当前页面一样会执行onPuase,而第一个页面会执行onResume。
也就是说在ViewPager2中,默认关闭了预加载机制。没有了预加载机制再谈延迟加载其实也没有任何意义了。因此关于ViewPager2的延迟加载也就不用多说了吧。相信随着ViewPager2的普及延迟加载的概念也会慢慢淡出开发者的视线。
本篇文章对于Fragment的延迟加载进行了深刻的探究,而且了解了在Androidx 1.1.0版本中对Fragment最大生命周期状态的控制,从而探究出了Fragment延迟加载的新方案。对于ViewPager2,因其默认不会进行预加载所以也就意味着咱们无需处理ViewPager2的延迟加载问题。好了,这一篇花费了我两个周末(实际上是上周末写了一半偷了个懒)的文章到此就结束了,若是你从中学有所收获那么请你不要吝啬留下你的赞。
给你们推荐一下BannerViewPager。这是一个基于ViewPager实现的具备强大功能的无限轮播库。腾讯视频、QQ音乐、酷狗音乐、支付宝、天猫、淘宝、优酷视频、喜马拉雅、网易云音乐、哔哩哔哩等APP的Banner样式以及指示器样式均可以经过BannerViewPager实现 。欢迎你们到github关注BannerViewPager!