Jetpack Compose animateFloatAsState结合tween动画出现NaN导致崩溃,无法本地复现但Firebase上报大量异常
看起来你碰到了个挺闹心的问题——本地怎么测都复现不了,但Firebase上却一堆因为动画NaN触发的崩溃,这种线上独有的问题确实头疼!
先帮你捋清楚问题根源:从栈追踪和你贴的代码来看,崩溃的直接原因是animateFloatAsState收到了NaN作为目标值,Compose的动画系统没法处理NaN的动画向量,所以抛出了IllegalStateException。而这个NaN的来源,就是你计算progressValue的那行代码:
(someData.value1.toFloat() / someData.value2.toFloat()) * 100
当someData.value2为0的时候,除以0就会得到NaN,刚好线上用户的某些数据触发了这个场景,而你本地测试的数据集里没有这种情况,所以复现不了。
接下来给你几个针对性的解决方案,帮你彻底解决这个崩溃:
1. 给进度值计算添加除以0的安全防护
这是最直接的办法,在计算progressValue时先判断分母是否为0,避免产生NaN:
val progressValue = remember(someData) { val value2 = someData.value2.toFloat() if (value2 == 0f) { // 分母为0时的兜底值,比如0或者100,根据业务需求来 0f } else { (someData.value1.toFloat() / value2) * 100 } }
或者用更简洁的写法:
val progressValue = remember(someData) { someData.value2.toFloat() .takeIf { it != 0f } ?.let { (someData.value1.toFloat() / it) * 100 } ?: 0f // 兜底值 }
2. 给动画目标值加兜底校验
就算前面的计算漏了,也可以在传给animateFloatAsState之前再做一层防护,确保不会传入NaN:
val safeProgressValue = if (progressValue.isNaN()) 0f else progressValue val someDataAnimation by animateFloatAsState( targetValue = safeProgressValue, animationSpec = tween(durationMillis = 700) )
甚至可以再加个范围限制,确保进度值在合理区间内:
val safeProgressValue = progressValue.takeIf { !it.isNaN() }?.coerceIn(0f, 100f) ?: 0f
3. 线上日志排查触发场景
为了搞清楚为什么会出现value2=0的情况(比如后端数据异常、用户操作触发的边界场景),可以在代码里添加日志,当触发分母为0的情况时记录相关信息,方便后续排查:
val progressValue = remember(someData) { val value2 = someData.value2.toFloat() if (value2 == 0f) { Log.w("SomeComposable", "Detected value2=0! someData details: ${someData}") 0f } else { (someData.value1.toFloat() / value2) * 100 } }
这样你就能在Firebase的日志里看到触发崩溃的具体数据,从根源上避免类似数据出现。
你之前尝试设置NaN或0没效果,大概率是因为本地测试时没有模拟value2=0的场景,导致代码里的问题没被触发。按照上面的方案添加防护后,就能彻底拦截NaN进入动画系统,解决线上的崩溃问题了。
备注:内容来源于stack exchange,提问作者Sk1droW

