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^)┛明天见! 返回搜狐,查看更多

责任编辑:

声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
发布于: 山西省