最近在后台日志监控系统中,发现了一个奇怪的 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。)

E/AndroidRuntime: FATAL EXCEPTION: main android.view.InflateException: Binary XML file line #13: Error inflating class at android.view.LayoutInflater.createView(LayoutInflater.java:613) at com.android.internal.policy.impl.P 这个问题排查了,好一段时间,无果。 百度一下,发现遇到这个问题的人还挺多的。大家的解决方案都是括号不匹配,应该是多了或少了吧。 我就仔细检查了一下代码。由于编辑器自带了检查括号是否配对功能。所以,咋一看,没问题。 最后,发现是一处,调用系统的宏定义的毛病。这个宏定义包含了左括号 “{”,而我在使用的时候,我又添加了一个左括号。 去掉后,编译通过。 Error, operation time out. RESULT_OPERATION_TIMEOUT! [rplidarNode-2] process has died [pid 22745, exit code 255, cmd /home/likeyu/catkin_ws/devel/lib/rplidar_ros/rplidarNode __name:=rplidarNode __log:=/home/zhaokai/.ros/log/2e561d84-4ae0-11e9-b7 使用pycharm安装第三方库:   打开pycharm工具 -> 点击File -> 点击Settings -> 点击 Project:xxx -> Project Interpreter -> 点击右侧边框右上角的 “+” -> 在弹出的Available Packages的输入框中输入需要安装的库名 在安装gmpy2这个库时,没能安装成功,查... Android12编译报错解决方法,compileDebugJavaWithJavac FAILED、java.lang.AssertionError: annotationType() 文章目录一、问题二、解决方法1. 方法一2.方法二 mysql-5.6.20主从同步错误之Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND或(主从复制,当主从同步状态异常时,通过对比检查二进制文件和中继日志文件,发现master 和slave 节点之间同步的位置点发生了偏移,请问这可能时什么情况引起的,如何解决) 二、解决方法 1. 方法一 1.Error_code: 1032; handler error HA_ERR_KEY_NOT_FO java.lang.AssertionError: use looper thread, must call Looper.prepare() first!在消息处理中必须先调用Looper类的prepare()方法。如下两段示例代码:一个是MainActivity,一个是由其开启的Activity。系统默认是给它创建了消息队列,而ActivityTwo由MainActivity创建和开启,公用Ma JSONTokener的方法 public JSONTokener(InputStream inputStream) 使用输入流创建JSONTokener JSONObject的方法 public JSONObject(JSONTokener x) throws JSONException 使用JSonTokener对象创建JSONObject对象 public Writer write(Writer wr