Android开发中ViewPager+Fragment的懒加载
TabLayout+ViewPager+Fragment是我们开发常用的组合。ViewPager的默认机制就是把全部的Fragment都加载出来,而为了保障一些用户体验,我们使用懒加载的Fragment,就是让我们在用户可见这个Fragment之后才处理业务逻辑。
而我们在一些设备或版本中可能就出现懒加载失效的问题。其实谷歌早就把一些懒加载的方案都标记弃用了,我们一直都用的老的随时会失效的Api。万一哪天彻底失效了就会导致线上事故。
接下来我们就看看Fragment的懒加载是如何演变的。谷歌又是推荐我们如何使用的。
Support时代的懒加载
在AndroidX还没出来的时候,大家的懒加载应该都是这样。判断
setUserVisibleHint
的方法,当用户可见的时候才回调方法去加载逻辑。
例如我的封装:
abstract
class
BaseVDBLazyLoadingFragment
<
VM : BaseViewModel, VDB : ViewDataBinding
> :
AbsFragment
{
protected
lateinit
var
mViewModel: VM
protected
lateinit
var
mBinding: VDB
private
var
isViewCreated =
false
//布局是否被创建
private
var
isLoadData =
false
//数据是否加载
private
var
isFirstVisible =
true
//是否第一次可见
protected
lateinit
var
mGLoadingHolder: Gloading.Holder
override
fun
onViewCreated
(view:
View
, savedInstanceState:
Bundle
?)
{
super
.onViewCreated(view, savedInstanceState)
isViewCreated =
true
startObserve
override
fun
onActivityCreated
(savedInstanceState:
Bundle
?)
{
super
.onActivityCreated(savedInstanceState)
if
(isFragmentVisible(
this
) &&
this
.isAdded) {
if
(parentFragment ==
null
|| isFragmentVisible(parentFragment)) {
onLazyInitData
isLoadData =
true
if
(isFirstVisible) isFirstVisible =
false
//使用这个方法简化ViewModewl的Hilt依赖注入获取
protected
inline
fun
<
reified
VM : BaseViewModel>
getViewModel
: VM {
val
viewModel: VM
by
viewModels
return
viewModel
//反射获取ViewModel实例
private
fun
createViewModel
: VM {
return
ViewModelProvider(
this
).
get
(getVMCls(
this
))
override
fun
setContentView
(container:
ViewGroup
?)
: View {
mViewModel = createViewModel
//观察网络数据状态
mViewModel.getActionLiveData.observe(viewLifecycleOwner, stateObserver)
val
config = getDataBindingConfig
mBinding = DataBindingUtil.inflate(layoutInflater, config.getLayout, container,
false
)
mBinding.lifecycleOwner = viewLifecycleOwner
if
(config.getVmVariableId !=
0
) {
mBinding.setVariable(
config.getVmVariableId,
config.getViewModel
val
bindingParams = config.getBindingParams
bindingParams.forEach { key, value ->
mBinding.setVariable(key, value)
return
mBinding.root
abstract
fun
getDataBindingConfig
: DataBindingConfig
abstract
fun
startObserve
abstract
fun
init
abstract
fun
onLazyInitData
//Loading Create Root View
override
fun
transformRootView
(view:
View
)
: View {
mGLoadingHolder = generateGLoading(view)
return
mGLoadingHolder.wrapper
//如果要替换GLoading,重写次方法
open
protected
fun
generateGLoading
(view:
View
)
: Gloading.Holder {
return
Gloading.getDefault.wrap(view).withRetry {
onGoadingRetry
protected
open
fun
onGoadingRetry
{
override
fun
onNetworkConnectionChanged
(isConnected:
Boolean
, networkType:
NetWorkUtil
.
NetworkType
?)
{
// ============================ Lazy Load begin ↓ =============================
override
fun
setUserVisibleHint
(isVisibleToUser:
Boolean
)
{
super
.setUserVisibleHint(isVisibleToUser)
if
(isFragmentVisible(
this
) && !isLoadData && isViewCreated &&
this
.isAdded) {
onLazyInitData
isLoadData =
true
override
fun
onHiddenChanged
(hidden:
Boolean
)
{
super
.onHiddenChanged(hidden)
//onHiddenChanged调用在Resumed之前,所以此时可能fragment被add, 但还没resumed
if
(!hidden && !
this
.isResumed)
return
//使用hide和show时,fragment的所有生命周期方法都不会调用,除了onHiddenChanged
if
(!hidden && isFirstVisible &&
this
.isAdded) {
onLazyInitData
isFirstVisible =
false
override
fun
onDestroy
{
super
.onDestroy
isViewCreated =
false
isLoadData =
false
isFirstVisible =
true
* 当前Fragment是否对用户是否可见
*
@param
fragment 要判断的fragment
*
@return
true表示对用户可见
private
fun
isFragmentVisible
(fragment:
Fragment
?)
:
Boolean
{
return
!fragment?.isHidden!! && fragment.userVisibleHint
使用的示例:
mBinding
.viewPager.bindFragment
(
supportFragmentManager
,
listOf
(
LazyLoad1Fragment
.obtainFragment
,
LazyLoad2Fragment
.obtainFragment
,
LazyLoad3Fragment
.obtainFragment
),
listOf
("
Demo1
", "
Demo2
", "
Demo3
")
mBinding
.tabLayout.setupWithViewPager
(
mBinding
.viewPager
)
扩展方法:
fun
ViewPager.
bindFragment
(
fm:
FragmentManager
,
fragments:
List
<
Fragment
>,
pageTitles:
List
<
String
>? =
null
,
behavior:
Int
=
0
) : ViewPager {
offscreenPageLimit = fragments.size -
1
adapter =
object
: FragmentStatePagerAdapter(fm, behavior) {
override
fun
getItem
(p:
Int
)
= fragments[p]
override
fun
getCount
= fragments.size
override
fun
getPageTitle
(p:
Int
)
=
if
(pageTitles ==
null
)
null
else
pageTitles[p]
return
this
Fragment:
class
LazyLoad1Fragment
:
BaseVDBLazyLoadingFragment
<
EmptyViewModel, FragmentDemo2Binding
>
{
companion
object
{
fun
obtainFragment
: LazyLoad1Fragment {
return
LazyLoad1Fragment
override
fun
getDataBindingConfig
: DataBindingConfig {
return
DataBindingConfig(R.layout.fragment_demo2)
override
fun
startObserve
{
override
fun
init
{
YYLogUtils.w(
"LazyLoad1Fragment - init"
)
mBinding.tvPage2.click {
Demo2Pager2Activity.startInstance
//重新生成GLoading对象
override
fun
generateGLoading
(view:
View
)
: Gloading.Holder {
return
Gloading.from(GloadingRoatingAdapter).wrap(view).withRetry {
onGoadingRetry
override
fun
onResume
{
super
.onResume
YYLogUtils.w(
"LazyLoad1Fragment - onResume"
)
override
fun
onGoadingRetry
{
toast(
"重试一个请求"
)
onLazyInitData
override
fun
onLazyInitData
{
YYLogUtils.w(
"LazyLoad1Fragment - initData"
)
//模拟的Loading的情况
showStateLoading
CommUtils.getHandler.postDelayed({
showStateSuccess
},
2500
)
到此就实现了
onLazyInitData
的回调,只有出现Fragment显示在前台的时候才会调用方法,执行逻辑。
AndrodX时代的懒加载
每次判断
setUserVisibleHint
和
onHiddenChanged
也麻烦,并且他们并不稳定,我也遇到过不回调的时候。
Android出来之后,给
FragmentStatePagerAdapter
添加了一个
@Behavior int behavior
的参数。
其本质就是内部帮你处理和切换MaxLifecycle:
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
如何使用呢:
mBinding.viewPager.bindFragment(
supportFragmentManager,
listOf(LazyLoad1Fragment.obtainFragment, LazyLoad2Fragment.obtainFragment, LazyLoad3Fragment.obtainFragment),
listOf(
"Demo1"
,
"Demo2"
,
"Demo3"
),
behavior = 1
之前的扩展方法以及预留了 behavior 参数,当为1的时候就不会回调
setUserVisibleHint
方法了,我们直接监听
OnResume
即可。
class
LazyLoad3Fragment
:
BaseVDBLoadingFragment
<
EmptyViewModel, FragmentDemo2Binding
>
{
var
isLoaded =
false
companion
object
{
fun
obtainFragment
: LazyLoad3Fragment {
return
LazyLoad3Fragment
override
fun
getDataBindingConfig
: DataBindingConfig {
return
DataBindingConfig(R.layout.fragment_demo2)
//重新生成GLoading对象
override
fun
generateGLoading
(view:
View
)
: Gloading.Holder {
return
Gloading.from(GloadingLoadingAdapter).wrap(view).withRetry {
onGoadingRetry
override
fun
startObserve
{
override
fun
init
{
YYLogUtils.w(
"LazyLoad3Fragment - init"
)
private
fun
initData
{
YYLogUtils.w(
"LazyLoad3Fragment - initData"
)
//模拟的Loading的情况
showStateLoading
CommUtils.getHandler.postDelayed({
showStateSuccess
},
2500
)
isLoaded =
true
override
fun
onResume
{
super
.onResume
YYLogUtils.w(
"LazyLoad3Fragment - onResume"
)
if
(!isLoaded) initData
override
fun
onGoadingRetry
{
toast(
"重试一个请求"
)
initData
注意这个页面继承的就不是我们自定义的懒加载Fragment了。普通的Fragment 回调
onResume
即可。
ViewPager2时代的懒加载
ViewPager2出来之后。我们的
FragmentStatePagerAdapter
退出历史舞台。
即便能用,即便效果还是和ViewPager2的效果一样,但是还是标记废弃了。具体原因我也不知道,据说是因为老版本会出现问题导致数据丢失,页面空白。
ViewPager2我们都知道内部是通过RV实现的。但是对于Fragment的处理有单独的Adapter实现。
扩展方法:
* 给ViewPager2绑定Fragment
fun
ViewPager2.
bindFragment
(
fm:
FragmentManager
,
lifecycle:
Lifecycle
,
fragments:
List
<
Fragment
>
) : ViewPager2 {
offscreenPageLimit = fragments.size -
1
adapter =
object
: FragmentStateAdapter(fm, lifecycle) {
override
fun
getItemCount
:
Int
= fragments.size
override
fun
createFragment
(position:
Int
)
: Fragment = fragments[position]
return
this
mBinding.viewPager2.bindFragment(
supportFragmentManager,
this
.lifecycle,
listOf(LazyLoad1Fragment.obtainFragment, LazyLoad2Fragment.obtainFragment, LazyLoad3Fragment.obtainFragment)
val
title = listOf(
"Demo1"
,
"Demo2"
,
"Demo3"
)
TabLayoutMediator(mBinding.tabLayout, mBinding.viewPager2) { tab, position ->
tab.text = title[position]
}.attach
使用的方式和ViewPager差不多,这里的Fragment也是使用普通的Fragment即可。
ViewPage和ViewPager2的性能对比
内存占用分别取三组数据。
ViewPager数据
一。111 二。117.6 三。115.1
ViewPager2数据
一。110 二。107.4 三。107.6
结论 ViewPager2基于RV实现的效果还是比老版ViewPager要稍好一点。
并且老版本标记废弃,大家如果是用ViewPager2的话,还是推荐使用ViewPager2实现。如果大家还是用的老版本的ViewPager也推荐使用behavor参数。使用
onResume
实现懒加载的实现。以后再换到ViewPager2的话,可以无缝切换过来。
说明一下,测试数据仅供参考,毕竟我也不是专业测试,测试数据源也不不多。如有不对的地方,也望大家指正。
好了,关于懒加载就到这里了。
源码
在此。
到此完结。
androidx来袭,Fragment如何更简单的实现懒加载?
为你的Fragment换装ViewPager2吧!
最后推荐一下我做的网站,玩Android:
wanandroid.com
,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
新技术又又叒叒叒叒来了?
两年经验妹子的面试总结
LiveData 面试 7 连问!
点击
关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!
返回搜狐,查看更多
责任编辑:
平台声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。