前段时间的项目中,分别实践了把 unity 项目导出到 android / ios 平台,并分别封装 aar 库和 framework 库,提供给各平台的 native 项目使用。期间遇到了各种问题,不过经过一些调试或者资料查找都解决了。

把这个单独记录下来,是因为问题解决的比较偶然,套不上通用的问题解决路径,作为一个解决问题的路子记录下来吧。

BUG现象

使用 unity 渲染了一个人物,会执行各种骨骼动画。在 android 平台上,activity在前台,或者浮窗使用,都没有任何问题,即使运行一天一夜,依然状态稳定。

产品实测过程中,发现偶尔使用近一个小时后,会出现人物卡住不动的情况。后续不断测试尝试复现,发现是以浮窗形式使用的时候,同时频繁切换导航软件并开启音乐软件听歌,大约10~20分钟后,人物会卡住不动。

怀疑方向一:unity & android 生命周期配合存在问题

image.png
unity导出到 android 平台后,需要通过 UnityPlayer 类初始化,初始化时需要传入一个 activity 的 context。

并且需要在 activity 的 onResume、onPause、onWindowFocusChanged 生命周期函数中分别调用 UnityPlayer 的 resume、pause、windowFocusChanged 函数。

亲测,这几个生命周期函数是必须全部绑定的。如果暂停时调用了 pause,然后恢复时调用 resume,但是没有调用 windowFocusChanged,此时渲染仍然不会恢复,画面卡住,必须调用 onWindowFocusChanged 后才会恢复渲染。

本地比较好复现这种情况,但是经过分析log,sdk的使用方对生命周期函数的使用并没有问题。

进一步添加 log 分析,发现甚至 unity 的主线程都没有暂停,monobehaviour 的 LateUpdate 函数都还在正常执行,更加说明了生命周期绑定这一块没有问题。

怀疑方向二:unity 动画状态机存在问题

另一种思路,既然 unity 本身的渲染没有卡住,那么有没有可能是人物在正常渲染,只是没有动了?

sdk 内部一个动画切换逻辑,有根据动画播放进度判断是否需要切换的逻辑。之前在另外一个项目有踩过一个坑:

image.png

通过 CrossFade 切换动画,然后通过获取当前播放进度的方式进行切换:

image.png
image.png

此时会偶现一个问题,获取的进度信息可能出现延迟。动画切换后,预期在下一帧获取的动画播放进度是 0,然而实际情况中,有时候会获取到上一个动画的播放进度,即 1.0(100%),这样就会导致一个后果:动画被不断地设置为从头开始,表现为人物卡住不动。

检查代码逻辑,该项目使用的动画切换并不是 CrossFade,而是在动画状态机中设置过渡选项,然后直接设置动画状态机的 Params 来切换动画。但是通过进度判断是否应该切换动画的逻辑仍然存在。给代码加上判断切换中状态的保护逻辑,然后加上关键日志,验证。

结论是并没有出现动画状态机卡住的情况,人物会根据用户调用或者闲时动作正常切换,输出正确的log。

怀疑方向三:频繁发送的动作指令存在问题

此时思路有一点停滞了,没有办法,继续分析log。

image.png

发现了一个比较有价值的信息,执行动作指令在一秒钟内被发送了多次。业务表示没有在短时间内调用这么多次指令,那么这里就出现了没对齐的地方了,会不会是某未知原因频繁的发送指令导致了频繁的动作切换,最终造成卡顿呢?

通过对代码的第一直觉,不会是这个原因,因为动作切换的第一步,就是清空之前的动画序列,然后才开始执行新的动作,即使高频调用切换动作方法,也不会造成计算的性能瓶颈。

那么为什么会有如此高频的调用呢?

我在本地模拟用户的使用场景 —— 频繁的暂停,恢复,各种操作,又暂停,如此循环。最终发现是 unity 的一个特点,引擎被 pause 后,渲染被暂停,通过 UnityPlayer.UnitySendMessage 调用引擎暴露的方法,也无法响应。但是这些调用并没有被丢弃,而是做了一个类似队列缓存的操作,一旦 unity 被 resmue 恢复之后,之前的所有调用会一起被触发,也就是上面的日志看到的情形。

通过在本地调试,可以很容易复现这种场景,但是应用也没有出现任何卡顿,CPU利用率也无任何波动。。。思路再次停滞。

破案:真实原因和解决

业务同学帮忙观察复现,并录制视频,人物并不是一下卡住不动的,而是慢慢的降低帧率,越来越卡顿,最终完全不动。

看起来还是比较符合第三种猜测,奈何本地怎么也无法复现。

不过这个现象最终比较明确的把问题指向了 CPU,而不是其它问题。所以再一次复现之后,让业务同学帮忙看一下进程的各线程的 CPU 占用。最终发现了这么一个不认识名字的线程,吃满了单核的计算性能:

image.png

业务在一般场景下,不会有这么一个线程刚好吃满单核性能,于是抱着试一试的心态,Google了一下:“unity filter0 thread”。第一条就蹦出来这个官方论坛的链接 Unity freeze on long run application after migrate to unity 2019.3。先不看内容,看标题就感觉症状非常像。

里边描述了 unity 在 android 平台上的一个 bug,在长时间运行后,应用会卡住,并且有一个叫 Filter0 的线程吃满一个核心的性能。

里边又有一个链接 Android build project freezes after 5 minutes with playerloop in profiler at 60,000 ms

有好几个用户遇到这个问题,版本分布在 2018.3、2019.3、2020.3,而我的版本是 2021.3。

image.png

通过官方论坛里边用户的说法,关闭 “Optimized Frame Pacing” 选项,再次尝试复现,发现关闭这个默认开启的 “帧同步优化” 选项后,确实有效,渲染不再卡住!确定是一个 unity 本身的 bug。

很扯蛋的是,贴子里官方开发人员说这个问题很难复现,先关闭 issue …

确实很难复现,我在非常多的设备和场景下调试,都不会有问题。就这一个业务的设备上同时使用导航和音乐时出现了问题。但是这个选项默认是打开的啊!可以预想到会有不少开发者被这个 bug 坑到,留此文,也许能帮到人。

☞ 参与评论