没错,就是这样自信。
首先,我们要明白,我们平常所说的Fragment懒加载是指数据的懒加载,而不是指Fragment本身的懒加载;同时,懒加载这个说法,需要Fragment和ViewPager结合起来用,才有懒加载一说。
为什么这么说呢,因为在ViewPager结合Fragment使用的时候,google为了让用户体验更好,默认是初始化了两个Fragment,当滑动到第二个Fragmet的时候,由于已经初始化了,所以界面展示更快,提升了用户体验。但这就引起了一个问题,由于是默认初始化了两个Fragment,两个Fragment都会去请求数据,如果两个Fragment请求的数据过多,而且我只看了一个Fragment的数据,这就造成资源的浪费,所以懒加载(数据的懒加载)就出现了。
那什么是懒加载呢?就是在不可见的Fragment初始化的时候,不去请求数据,当它对用户可见的时候再去请求,这就是所谓的懒加载。
上面提到了ViewPager结合Fragment的时候,默认初始化了两个Fragment。那有人问,你怎么知道是默认初始化了两个?能不能有方法控制默认初始化的个数?第一个问题,肯定是看源码知道的啊。第二个问题,肯定是可以控制初始化的个数的啊。
首先我们看ViewPager的一个方法:
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
这个方法就是控制默认初始化Fragment的个数的。一目了然,默认的离屏的Fragment个数是1,再加上当前的Fragment,默认就初始化了两个。那有人说了,我将默认离屏的Fragment个数设置成0,不就只初始化当前这一个Fragment了吗?想的太简单,看下这个方法里面,是做了判断的;当传入的个数小于默认值1时,还是使用默认值1。现在应该明白这个方法是干嘛的了吧。
那怎么实现懒加载呢,别急,要实现懒加载,我们需要先明白Fragment的生命周期才可以。
接下来我们来看个例子。我们定义了四个Fragment,Fragment1,Fragment2,Fragment3,Fragment4,将这个4个Fragment都放入ViewPager中,当进入第一个Fragment1的时候,我们看下Fragment1的生命周期。
Fragment1 setUserVisibleHint isVisibleToUser=false
Fragment1 setUserVisibleHint isVisibleToUser=true
Fragment1 onAttach
Fragment1 onCreate
Fragment1 onViewCreated
Fragment1 onActivityCreated
这里只罗列了部分的生命周期,在生命周期中,我们发现了一个方法setUserVisibleHint,它是优先于其他所有的生命周期方法。这个方法会在Fragment不可见和可见的时候调用,当不可见的时候,它有一个变量isVisibleToUser,返回值为false,可见的时候,返回值为true,这个方法就是实现懒加载的关键方法。
现在我们看下当进入第一个Fragment1,并且没有调用setOffscreenPageLimit方法的时候的生命周期:
这里有两个Fragment的生命周期,默认初始化了Fragment1和Fragment2两个Fragment,注意前三行,由于最开始Fragment1对用户不可见,所以isVisibleToUser为false,初始化了Fragment2,它对用户也不可见,所以isVisibleToUser也为false,但是当Fragment1对我们可见的时候就返回true了。
接着,我们滑动到第二个Fragment,即Fragment2,我们再看下生命周期的方法:
现在看一下,Fragment3,它目前对用户不可见,但是还是初始化了,不过它的isVisibleToUser为false,Fragment2对用户可见,它的isVisibleToUser就变成true了,而Fragment1这时也是不可见的,所以它的isVisibleToUser就变成false了。同理,我们再滑到到Fragment3,看下生命周期
和上面的一模一样,只是Fragment4这个时候初始化了。
按照上面的生命周期,我们就可以实现懒加载了,我们定义一个方法lazyLoadDataIfPrepared(),它来执行懒加载,但它在什么时候调用呢?
1、在第二个Fragment的生命周期的setUserVisibleHint(isVisibleToUser: Boolean)方法中,由于当isVisibleToUser为true的时候需要去请求数据,这样,就相当于Fragment2对用户可见时再去请求数据了。所以lazyLoadDataIfPrepared这个方法应该在setUserVisibleHint(isVisibleToUser: Boolean)方法中并且isVisibleToUser为true的时候调用。
2、接着,我们需要在页面渲染完成之后再去请求数据,所以请求数据的方法应该也要放在onViewCreated生命周期中,所以也需要在onViewCreated方法中调用lazyLoadDataIfPrepared方法。
3、但是在来回滑动的过程中,肯定是不需要再次去请求数据了,如果在setUserVisibleHint去请求数据,那么来回滑动Fragment,都还是要请求数据,所以我们可以用一个变量hasLoadData控制是否已经加载过数据了,加载之后,hasLoadData就置为true。
考虑到以上三种情况,我们还需要在lazyLoadDataIfPrepared方法做一个判断,当hasLoadData为false(表示未加载过数据),执行了onViewCreated生命周期,并且getUserVisibleHint为true的时候,再去执行真正的数据加载。说了这么多,看下完整的源码就知道了。
abstract class BaseFragment: Fragment() {
private val TAG = this::class.java.name
/**
* 视图是否加载完毕
*/
private var isViewPrepare = false
/**
* 数据是否加载过了
*/
private var hasLoadData = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(getLayoutId(),null)
Log.d(TAG, "$TAG onCreateView")
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
Log.d(TAG, "$TAG setUserVisibleHint isVisibleToUser=$isVisibleToUser")
if (isVisibleToUser) {
lazyLoadDataIfPrepared()
}
}
override fun onAttach(context: Context?) {
super.onAttach(context)
Log.d(TAG, "$TAG onAttach")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "$TAG onCreate")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "$TAG onViewCreated")
isViewPrepare = true
initView()
lazyLoadDataIfPrepared()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Log.d(TAG, "$TAG onActivityCreated")
}
private fun lazyLoadDataIfPrepared() {
if (userVisibleHint && isViewPrepare && !hasLoadData) {
lazyLoad()
hasLoadData = true
}
}
/**
* 加载布局
*/
@LayoutRes
abstract fun getLayoutId():Int
/**
* 初始化 ViewI
*/
abstract fun initView()
/**
* 懒加载
*/
abstract fun lazyLoad()
}
我们可以将懒加载的逻辑放在BaseFragment中,就可以实现每个Fragment对用户可见时再去请求数据了。
至此,Fragment懒加载就实现了。