再讲Fragment的懒加载-深度懒加载之布局的懒加载优化

1,446 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

ViewPager+Fragment懒加载的进阶之布局的懒加载优化

本文是基于 ViewPager的懒加载实现,和 异步加载布局的几种方案 两篇文章结合的实践,如果不太了解或感兴趣的同学,强烈推荐先去看看文章,把前置技能点亮。(都很简单的知识点,一点不难,顺便点个赞哦 😂 😂)

之前的文章我们讲到 ViewPager+Fragment 实现懒加载的几种方式,如果我们的Fragment比较多,又或者Fragment内部的布局比较复杂,那我们加载这样的ViewPager会不会卡顿?如果有,那可以优化吗?又有哪些优化的方案呢?

一、原生的使用

我们快速的实现一个ViewPager2+Fragment懒加载的效果

布局为普通的TabLayout+ViewPager2

    override fun init() {

        mBinding.viewPager2.bindFragment(
            supportFragmentManager,
            this.lifecycle,
            listOf(Lazy2Fragment1(), Lazy2Fragment2(), Lazy2Fragment3())
        )

        val title = listOf("Demo1", "Demo2", "Demo3")
        TabLayoutMediator(mBinding.tabLayout, mBinding.viewPager2) { tab, position ->
            //回调
            tab.text = title[position]
        }.attach()
    }

Fragment为普通的带Loading封装的一个Fragment。

class Lazy2Fragment1 : BaseVMLoadingFragment() {

    var isInitDataLoaded = false

    companion object {
        fun obtainFragment(): Lazy2Fragment1 {
            return Lazy2Fragment1()
        }
    }

    //重新生成GLoading对象
    override fun generateGLoading(view: View): Gloading.Holder {
        return Gloading.from(GloadingRoatingAdapter()).wrap(view)
    }

    override fun getLayoutIdRes(): Int = R.layout.lazy2_fragment1


    override fun init() {

    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        YYLogUtils.w("Lazy2Fragment1 - onCreateView")
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        YYLogUtils.w("Lazy2Fragment1 - onViewCreated")
        super.onViewCreated(view, savedInstanceState)
    }

    override fun onResume() {
        super.onResume()
        YYLogUtils.w("Lazy2Fragment1 - onResume")

        if (!isInitDataLoaded) {
            onLazyInitData()
        }
    }

    private fun onLazyInitData() {
        YYLogUtils.w("Lazy2Fragment1 - initData")
        //模拟的Loading的情况
        showStateLoading()

        CommUtils.getHandler().postDelayed({

            showStateSuccess()
            isInitDataLoaded = true

        }, 1000)

    }
}

懒加载就已经默认实现了,在 OnResume 的时候触发网络请求即可,每次滑动ViewPager会自动触发 OnResume 方法。

生命周期如下:

然后滚动到Fragment2 Fragment3 再滚动回来,生命周期如下

很简单的就能实现懒加载的效果,那这样懒加载就能优化性能了吗?

确实优化了,特别是RV列表,没有网络数据就不会填充列表。我觉得更重要的是帮用户节约了流量吧。毕竟没有显示出来的页面不会请求网络。

那么如果Fragment就是一些静态的页面,懒加载还能优化性能吗?

二、懒加载的痛点与优化方案

可以看到我们所谓的 Fragment 懒加载,也只是逻辑的懒加载,处理的是 initData 加载数据的懒加载,如果 Fragment 内部的布局是列表RV这样的,问题不大,但是如果是复杂的长滚动布局,又或者是不同的复杂布局,那么就会卡顿。

因为 ViewPager+Fragment 的懒加载,是会加载子 Fragment 的布局的,内部每一个 Fragment 都会走 onCreateView 和 onViewCreated 来加载布局。如果你的布局比较复杂当然很耗时了。

有什么方案解决和优化这样的情况?大家普遍的方法都是 Loading 的时候不加载真正的布局,等加载数据完成之后再填充真正的布局。

于是就有了这样的写法

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical">

    <ViewStub
        android:id="@+id/view_stub_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/view_stub_fragment1" />

</FrameLayout>

    private lateinit var mViewStub: ViewStub

    override fun initViews(view: View) {
        mViewStub = view.findViewById(R.id.view_stub_1)
    }

    private fun onLazyInitData() {
        YYLogUtils.w("Lazy2Fragment1 - initData")
        //模拟的Loading的情况
        showStateLoading()

        CommUtils.getHandler().postDelayed({

            showStateSuccess()
            mViewStub.inflate()  //加载网络数据成功之后展示布局
            isInitDataLoaded = true

        }, 1000)

    }

这样才能做到真正的懒加载,布局懒加载,业务逻辑懒加载。

这也是大家普遍的优化做法,好处是可以加快 Activity 的加载速度。如果Fragment 内布局复杂,那么更能提现此做法的好处,越是复杂的布局优化效果越大。

但是这样也只是优化了Activity的启动速度,当数据加载出来之后 inflate 布局,算是懒加载了,但是如果布局实在是复杂,依然会卡顿,之前的项目就是复杂布局加载耗时800毫秒,就会明显感知到一顿。掉帧的情况。

那我们之前的文章也有讲到,AsyncViewStub 的自定义,我们可以通过此方法与加载数据的方法并发执行来优化 Fragment 懒加载。

三、修改自定义加载布局

AsyncViewStub 的自定义,在之前的文章又讲到过,这里就不过多赘述,可以查看之前的文章。

最简单的方式,直接替换ViewStub

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical">

    <com.xxx.lib_baselib.view.AsyncViewStub
        android:id="@+id/view_stub_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/view_stub_fragment1" />

</FrameLayout>

    private lateinit var mViewStub: ViewStub

    override fun initViews(view: View) {
        mViewStub = view.findViewById(R.id.view_stub_1)
    }

    private fun onLazyInitData() {
        YYLogUtils.w("Lazy2Fragment1 - initData")
        //模拟的Loading的情况
        showStateLoading()

        CommUtils.getHandler().postDelayed({

            showStateSuccess()
            mViewStub.inflateAsync()  //加载网络数据成功之后展示布局
            isInitDataLoaded = true

        }, 1000)

    }

只是替换为异步加载的ViewStub。这样当数据加载成功之后我们使用的是异步加载布局然后填充布局,这样如果是复杂的布局不会卡顿但是会等待。

是优化了,但是又没完全优化。

我们能不能让异步加载布局和网络请求并发?然后两者任务都完成了再展示布局填充数据?

试试!

 private fun onLazyInitData() {
        
        lifecycleScope.launch {

            //模拟的Loading的情况
            showStateLoading()

            val isInflate = async {
                if (!mViewStub.isInflate()) {
                    mViewStub.inflateAsync()
                }

                true
            }

            val data = async(Dispatchers.IO) {
                delay(1000)
                isInitDataLoaded = true
                "return data"
            }

            if (!TextUtils.isEmpty(data.await()) && isInflate.await()) {
                showStateSuccess()
                //popupData2View
            }

        }

    }

这样就能并发实现异步加载布局,异步请求数据,然后展示到控件并展示。

如果大家不是使用的协程,一样的可以通过成员变量来控制,达到同样的效果。

总结

深度懒加载的实现:Fragment的懒加载 + 布局的异步懒加载。

通过两者结合的方法就能真正的实现 Fragment 的完全懒加载,布局和逻辑的懒加载达到性能的最优化。

代码都在本文贴出,如果想测试可以直接运行源码。如有需求也可以自取。

本期内容如讲的不到位或错漏的地方,希望同学们可以指出。如果你有更好的方法可以评论区交流。

如果感觉本文对你有一点点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。

Ok,这一期就此完结。