携手创作,共同成长!这是我参与「掘金日新计划 · 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,这一期就此完结。