UI加载优化

如果打开一个界面非常卡,是不是因为加载太慢了呢?是不是因为资源太大了呢?
这个问题,一定仔细分析,因为打开界面,不是单纯的加载行为。

我们目前的AssetManager管理资源,加载支持异步与同步。异步是完全异步,加载AssetBundle文件、从AssetBundle加载文件都是异步操作(因为Unity版本,异步加载AssetBundle主线程还是会有消耗,最新的Unity版本解决了这个问题,同时从AssetBundle异步加载文件结束,返回主线程时的消耗也进行了优化)。而同步加载,加载AssetBundle、从AssetBundle加载文件,都是同步完成的。

异步加载是不是肯定更快?
异步加载因为是多线程加载,更充分的利用了IO性能,但是加载最大的耗时还是在IO,多线程也不能超越IO的限制。同步加载不需要返回主线程的操作,如果是单独加载一张小贴图,同步加载是可能更快的。

加载速度大概多块?
加载一个资源,往往只有几ms,取决与IO速度,但是打开一个UI或者一个场景,往往需要加载大量的资源,并不是一个单独的Prefab或者Scene文件,大量的依赖文件是需要加载的。

回到UI,打开一个UI一个进行哪些操作?我们目前提供的UI管理里,也是同时支持异步和同步的。

如果是异步:
打开的UI会进入UIResManager队列,等待进行加载,单帧有数量上限。
异步加载主AssetBundle以及依赖的AssetBundle,AssetBundle数量越多,发生的加载越多,所有的AssetBundle加载会有单帧限制,这一步几乎不会导致掉帧。
从主AssetBundle里异步加载Prefab文件,Unity自动从依赖的Bundle里加载文件例如依赖的贴图材质动画等等,多线程操作,几乎不会导致掉帧。
Prefab返回,我们SetActive(false)掉根节点,然后空等一帧,加载返回后会对主线程有一些影响,这一步是为了尽量降低这一帧的耗时,保持帧数平滑。
实例化Prefab,因为上一帧我们把GameObject关闭了,因此这一帧的消耗只有实例化,挂载的脚本不会执行Awake,也不会发生Mesh重构等行为,然后我们再等一帧。
SetActive(true)根节点,挂载的脚本会执行Awake,UI的逻辑会执行,这一帧会发生Mesh重构。

一个完整的异步打开流程就是这些步骤,4,5已经是加载完的额外步骤,是一个比较Hack的方式,用来尽量保证帧数的平滑。我们整个异步流程,都尽量限制了单帧执行的操作。
那么哪几步是对主线程消耗最大的呢?其实就是5,6。实例化是一个完全的同步过程,没有任何多线程的可能。实例化没有任何魔法,本质上就是序列化反序列化的过程。因此UI节点数量越多,实例化的速度越慢。
6这一步会触发UI的业务逻辑,这一步做的事越多,消耗就会越大,同时这一帧还有进行Mesh重构,性能非常紧张,这一步也非常容易掉帧,同样的UI的节点数量(参与UI绘制的节点)越多,Mesh重构的速度越慢。

如果是同步:
1.直接发起同步加载,立即进行
实例化,Awake当帧会执行,业务逻辑当帧触发,Mesh重构当帧触发
同步加载完全享受不到任何多线程。

因此如果打开一个UI出现明显掉帧,分析问题时应该按照以下顺序:
是不是同步打开的UI,如果是同步,可不可以替换成异步UI,比如通过同步开启一个遮罩UI,来保证业务上不受影响。
是不是实例化太慢。可以通过Profiler观察;或者在Unity里打开这个Prefab时,如果就已经很慢了,那问题已经很严重。 Prefab的节点数量越少越好,如果有大量相同的节点,只是位置、内容不一样,可以考虑在业务里,通过clone的方式去添加节点。
是不是业务太慢了,或者业务里同步实例化了太多节点。Lua里操作字符串,或者反复和C#交互时有大量的字符串传递都会导致非常大的GC和CPU占用,Pb的序列化反序列化也是不容易察觉的消耗。大量的同步实例化,可以考虑使用协程进行分帧进行实例化,我们也会尽快提供一个内置的异步实例化队列的功能。

如果我的UI打开时,没有掉帧,就是单纯的慢,那么可以通过以下方式优化:
贴图是不是可以先空,然后业务里延迟赋值
业务里会动态修改的贴图、Sprite是不是资源里给了没必要的初始资源
音频、动画、特效等等资源是不是直接被Prefab依赖,是不是可以用时延迟加载

事实上很少有遇到打开慢却不掉帧的情况,所以解决问题时,优先考虑同步异步,节点数量,实例化数量。
另外我们还有Preload的接口,可以提前进行加载,资源会进行缓存。如果有大量贴图需要显示,但是希望尽量平滑不要出现空贴图的情况,可以先进行Preload,Preload内部也会进入加载队列,单帧可以大量进行Preload不会立即执行加载。

另外分析加载卡顿问题时,一定要打AssetBundle,进入Ab模式测试。在编辑器默认下,是通过AssetDatabase同步加载资源,即使是异步接口,我们也只是模拟了一个异步过程,空等随机帧返回资源,本质还是主线程同步加载。而且加载的资源也是原始资源,没有经过Ab的压缩,特别是没经过打包的贴图,原始文件可能那个非常大。老版本的Unity,动画文件会有明显序列化过程导致卡顿,这些问题在Ab模式、真机上都是没有的。上面提到的Hack的方式,先关闭加载的资源节点再实例化,在非Ab模式下,也是没用的,实例化、业务逻辑、Mesh重构会在一帧进行。

另外,如果不能马上解决掉帧问题,可以考虑,在界面打开时不要立即播放动画,默认状态下UI是隐藏的,1帧后开始进行动画表现,这样平均帧数不会提升(甚至可能掉帧,因为后续可能连续Mesh重构),但是直观感受,会觉得很平滑。
立即播放动画,当帧如果非常卡就进行播放动画,下帧动画计算时,插值计算会出现非常不平滑的感觉,甚至可能压根没播放出来动画(例如一个1s的动画,当帧就卡顿超过1s,下帧动画插值直接到了最后1动画结束了播放)。

总结一下:
能用异步,就用异步
尽量减少节点数量,不要有大量的重复节点,把大UI拆小,把不需要立即显示节点考虑通过异步加载实例化来延迟进行
尽量减少单帧大量的实例化,我们后续也会提供一个异步实例化队列
在Ab模式下,分析加载卡顿问题
初始动画延迟1帧,UI初始状态保持动画第一帧,可以通过脚本采样第一帧进行制作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值