Fatal Exception: java.lang.AssertionErrorNo NameTypeIndex match for SHORT_DAYLIGHT

最近在后台日志监控系统中,发现了一个奇怪的 bug,只在 android 8.1.0 中出现,crash 日志如下,也可能会报 Fatal Exception: java.lang.AssertionError: No NameTypeIndex match for SHORT_STANDARD 这个错误,本质还是一样的。

Fatal Exception: java.lang.AssertionError: No NameTypeIndex match for SHORT_DAYLIGHT
       at android.icu.impl.TimeZoneNamesImpl$ZNames.getNameTypeIndex(TimeZoneNamesImpl.java:724)
       at android.icu.impl.TimeZoneNamesImpl$ZNames.getName(TimeZoneNamesImpl.java:790)
       at android.icu.impl.TimeZoneNamesImpl.getTimeZoneDisplayName(TimeZoneNamesImpl.java:183)
       at android.icu.text.TimeZoneNames.getDisplayName(TimeZoneNames.java:261)
       at java.util.TimeZone.getDisplayName(TimeZone.java:405)
       at java.util.Date.toString(Date.java:1066)
       at org.json.JSONStringer.value(JSONStringer.java:252)
       at org.json.JSONObject.writeTo(JSONObject.java:723)
       at org.json.JSONObject.toString(JSONObject.java:692)

跟踪源代码,发现的确是在 TimeZoneNamesImpl.java 的 getNameTypeIndex() 方法中抛出了该异常,代码如下:

707        private static int getNameTypeIndex(NameType type) {
708            switch (type) {
709            case EXEMPLAR_LOCATION:
710                return NameTypeIndex.EXEMPLAR_LOCATION.ordinal();
711            case LONG_GENERIC:
712                return NameTypeIndex.LONG_GENERIC.ordinal();
713            case LONG_STANDARD:
714                return NameTypeIndex.LONG_STANDARD.ordinal();
715            case LONG_DAYLIGHT:
716                return NameTypeIndex.LONG_DAYLIGHT.ordinal();
717            case SHORT_GENERIC:
718                return NameTypeIndex.SHORT_GENERIC.ordinal();
719            case SHORT_STANDARD:
720                return NameTypeIndex.SHORT_STANDARD.ordinal();
721            case SHORT_DAYLIGHT:
722                return NameTypeIndex.SHORT_DAYLIGHT.ordinal();
723            default:
724                throw new AssertionError("No NameTypeIndex match for " + type);
725            }
726        }

看日志的路径,都是系统的调用,初步评估有可能是系统的 bug,在 stackoverflow 上也有人反馈这样的 bug,有些是在使用 facebook sdk ,有些是在使用 volley sdk 时出现,facebook 的做法是在触发这个 crash 的地方 catch 住 AssertionError,示例代码处理如下:

private fun testAssertionError() {
    try {
       throw AssertionError("No NameTypeIndex match for " + "type")
    } catch (e: AssertionError) {
       Log.e("ERROR", "testAssertionError: AssertionError---" + e.message )
    }
}

注意,如果你写 catch 代码块里是 Exception 的话,是捕获不了的,因为 AssertionError 是继承自 Error 的。

大家也可以查看一下 Android sdk 的 chang-log ,在 2020 年 1月 20 日已经修复了这个 bug,如下:

Apptimize SDK for Android Change Log 3.5.3 - 20 January 2020
    * Work-around for an Android 8.x bug, where it can generate a
      AssertionError for "No NameTypeIndex match for SHORT_DAYLIGHT"

具体怎么修复,还不知道在哪里可以查到,但可以从 issuetracker 中大概了解下这个问题,具体链接如下:

https://issuetracker.google.com/issues/110848122

看下 comment 17

This is a bug in "jack", the Java compiler used in Android M, N and O, where enum switch (in TimeZoneNamesImpl$ZNames.getNameTypeIndex() in this case) is compiled into racy code. (We're using javac in Android P.)

The implementation of the enum switch contains a lot of synthetic code, including the initialization of an int[] array using the enum ordinals and storing it in a synthetic static field for future use. The bug is that the synthetic field is not volatile and therefore there is no synchronization between threads. Thus thread T1 can initialize the int[] array values and store the int[] reference in the field and thread T2 can read the int[] reference from the field but does not yet see the values in that array; T2 loads 0 from the array and hits the fall-through path in the switch leading to the AssertionError.

As a workaround, you could modify an app to exercise the code path (for example, "new Date().toString()") while there is only a single thread. (But fixing this for the SetupWizard that you see after a factory reset would require a system update, preferably putting the workaround into the zygote.)

Google 翻译如下:

这是“jack”中的一个错误,它是 Android M、N 和 O 中使用的 Java 编译器,其中枚举开关(在本例中为 TimeZoneNamesImpl$ZNames.getNameTypeIndex())被编译成活泼的代码。 (我们在 Android P 中使用 javac。)

枚举开关的实现包含大量合成代码,包括使用枚举序数初始化 int[] 数组并将其存储在合成静态字段中以供将来使用。错误是合成字段不是易失性的,因此线程之间没有同步。因此线程 T1 可以初始化 int[] 数组值并将 int[] 引用存储在字段中,线程 T2 可以从字段中读取 int[] 引用但还没有看到该数组中的值; T2 从数组中加载 0 并命中导致 AssertionError 的开关中的直通路径。

作为一种解决方法,您可以修改应用程序以在只有一个线程时执行代码路径(例如,“new Date().toString()”)。 (但为您在出厂重置后看到的 SetupWizard 修复此问题需要系统更新,最好将解决方法放入 zygote。)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值