ViewPager+Fragment首页布局的加载优化

ViewPager+Fragment首页布局的加载优化

简介

VIewPager+Fragment是多数APP的首页布局,也是用户打开app后除了广告外第一个看到的页面,所以如果能对ViewPager+Fragment的布局有所优化,就是对app的冷启动时长有所优化。

本文通过设置ViewPager的setOffscreenPageLimit参数和提供适配ViewPager的Fragment来优化加载耗时。

优化思路

setOffscreenPageLimit(int limit)

setOffscreenPageLimit(int limit)是ViewPager提供的一个方法,用于控制提前加载页面的数量。不设置则默认参数是1。

offScreen就是屏幕外的意思,为了用户左右滑动到其他页面时能不卡顿,VIewPager允许提前加载左右相邻一定数目的页面(也就是page),这样切换到其他页面时可以看到加载好的页面。而limit参数就是预加载页面的数目,设置后会加载左右各limit个页面

举个例子,一个ViewPager+ABCDE五个Fragment,默认一开始在C Fragment。
a) 如果设置setOffscreenPageLimit(1),则会提前加载BCD三个
b)如果设置setOffscreenPageLimit(2),则会提前加载ABCDE五个
c) 如果设置setOffscreenPageLimit(1),而从C滑动到D时,E也会被预加载,而B可能会被destroy,也可能只是被remove,具体看adapter实现

优化点
最理想的情况是,启动时我只想加载用户能看到的那个页面,在首帧展示完成后,再利用空闲的时间来预加载剩余的页面,即,我们想达到setOffscreenPageLimit(0)的效果。
可惜的是,ViewPager并不支持设置为0,就算传入了0,也会在内部被纠正为默认值1,这个设计可能是为了用户操作更流畅吧。
既然不能设置为0,我们就退而求其次,设置limit为1,做到启动时尽可能少地加载页面。优化措施大概如下

1、开始时设置setOffscreenPageLimit(1)
2、对点击滑动等可能导致页面切换的操作,需要阻塞并设置setOffscreenPageLimit为最大值,保证全部Fragment被加载了,否则会有Fragment生命周期执行到destroy的风险,
3、在主线程空闲的时候,发消息在主线程执行全量加载,即设置setOffscreenPageLimit为最大值的操作,这样可以做到延迟预加载,减少对用户体验的影响。主线程是否空闲,一是可以把消息丢到idlehandler中处理,但如果handler一直繁忙,会有不会预加载的风险。二是发送延迟固定时间的消息来处理,比如延迟5s来执行,相当于预留了5s给其他业务操作,5s后再触发全量预加载

提供专门用于ViewPager的Fragment

setOffscreenPageLimit无法做到只加载可见页面,那么我们是否可以换个思路,尽量减少加载Fragment时候的耗时,进而做到近似的效果?
要想从这个角度优化,我们需要先知道ViewPager是怎么加载Fragment的
网上关于ViewPager源码的介绍已经是汗牛充栋了,我不展开细讲,只讲优化相关的流程

1、ViewPager#setAdapter时,会触发Fragment对象的创建,但此时Fragment的生命周期还没执行
2、ViewPager#setAdapter后,会触发一次页面绘制,即会有一次doFrame消息的执行,关键流程如下

doFrame
ViewPager#onMeasure
ViewPager#populate
adapter#finishUpdate
FragmentManager#moveToState
Fragment生命周期到onResume

我再贴一下Fragment的生命周期
在这里插入图片描述
总的来说,ViewPager加载Fragment时,会执行Fragment的
onAttach、onCreate、onCreateView、onActivityCreated、onStart、onResume
因为offScreenPageLimit不能为0,那么就存在对用户不可见的Fragment,也会在首帧时被执行。那么我们能否增加判断,只有当Fragment可见时,才执行onAttch到onResume等方法呢?
答案是基本可以的,判断Fragment是否对用户可见用userVisibleHint标记即可,我们只需要把onAttch到onResume中首次加载才会用到的代码归到一个方法里,在页面可见时再触发即可。
说太多了,直接上代码吧

public abstract class FragmentForViewPager extends Fragment {
	private boolean visible = false;
	private boolean loadDone =false;
	private boolean firstOnResumeDone = false;

	public abstract void loadForViewPager();
	
	public abstract void normalOnResume();
	
	@Override
	public void setUserVisibleHint(boolean isVisibleToUser) {
		super. setUserVisibleHint(isVisibleToUser);
		this.visible = isVisibleToUser;
	}
	
	// onResume方法加final限制,不允许子类重写onResume,而是改为用normalOnResume和loadForViewPager
	// 从其他页面回来触发的onResume放到normalOnResume中,而ViewPager触发的onResume则放到loadForViewPager中
	@Override
	public final void onResume() {
		super.onResume();
		// 对onResume做分流,只有第一次onResume,即ViewPager触发的onResume时
		// 才有可能触发loadForViewPager。其他情况执行normalOnResume
		if (!firstOnResumeDone) {
			// 只有当前Fragment是可见的,才执行loadForViewPager
			if (visible) {
				loadForViewPager();
				loadDone=true;
			}	
			firstOnResumeDone = true;
		} else {
			// 因为onResume会被很多情况调用,所以onResume里面并非所有操作都需要在ViewPager触发的onResume里执行
			// 所以我们可以把其他情况放到normalOnResume处理
			normalOnResume();
		}
	}

	// onDestroy后记得赋值为初始值,因为有可能Fragment被onDestroy了,但Fragment实例还在,
	// 这时系统可能会复用实例,在同一个实例上执行onCreate到onResume,如果此时不重新赋值,会导致流程失败
	@Override
	public void onDestroy() {
		super.onDestroy();
		loadDone=false;
		firstOnResumeDone=false;
	}
	
	// 用户看到这个Fragment前,程序需要调用forceLoad保证loadForViewPager一定要被执行一次,
	// 否则onResume会直接走到normaleOnResume,导致代码没有被正确初始化
	public boolean forceLoad() {
		if (!loadDone) {
			loadForViewPager();
			loadDone=true;
		}
	}

}

我们把Fragment从onAttach到onResume的生命周期拆分为两部分,loadForViewPager和normalOnResume,具体拆分如下

Fragment的onAttach到onStart中的初始化逻辑
loadForViewPager
Fragment的onResume中初始化需要的逻辑
Fragment的onResume中非初始化相关的逻辑
normalOnResume

拆分后,我们控制loadForViewPager只有在用户可见时才加载,只有loadForViewPager执行过后,后续onResume才走normalOnResume
流程变动如下
优化前

doFrame
onAttach到onStart
初始化相关的onResume逻辑
非初始化的onResume逻辑

优化后

yes
no
no
yes
doFrame
当前Fragment是否用户可见
loadForViewPager
绘制首帧
主线是否空闲
forceLoad
结束

优化点
1、子类不再需要处理userVisibleHint。很多针对ViewPager+Fragment的文章都介绍通过在setUserVisibleHint里面加逻辑来减少加载耗时,但这样与显得代码很繁杂,因为setUserVisibleHint会被频繁调用,所以需要加是否可见、是否第一次可见等状态,优化后的Fragment子类不再需要获取userVisibleHint状态。内部会自动处理好
2、减少加载不可见Fragment时的耗时,这个是因为onAttach到onStart被架空了,部分onResume逻辑也被架空了,导致首次加载的内容减少了。

总结

针对VIewPager+Fragment的布局加载,本文提供了两种思路进行优化,一是对offscreenPageLimit的动态设置,二是提供了专用于ViewPager的Fragment。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值