【问题标题】:java.lang.VerifyError IllformedLocaleExceptionjava.lang.VerifyError IllformedLocaleException
【发布时间】:2016-08-05 06:56:47
【问题描述】:

我有以下父方法,在所有情况下都被各种 API 级别使用:

public int setVoice (@NonNull final String language, @NonNull final String region){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        return setVoice21(language, region);
    } else {
        return setVoiceDeprecated(language, region);
    }
}

setVoice21 做了这样的事情:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21 ( @NonNull final String language, @NonNull final String region){

    try {
        // try some API 21 stuff
    } catch (final IllformedLocaleException e) {
        e.printStackTrace();
        return setVoiceDeprecated(language, region);
    }

setVoice21 包含其他需要 API 21+ 的代码,特别是 TextToSpeech.VoiceLocale.Builder

当我在设备

W/dalvikvm: VFY: 无法解析异常类 6232 (Ljava/util/IllformedLocaleException;) W/dalvikvm: VFY: 拒绝 操作码 0x0d 在 0x0168 W/dalvikvm: VFY: 被拒绝 Lcom/myapp/android/speech/MyTextToSpeech;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I W/dalvikvm: 验证器被拒绝 类 Lcom/myapp/android/speech/MyTextToSpeech;

E/AndroidRuntime: 致命异常: main java.lang.VerifyError: com/myapp/android/speech/MyTextToSpeech

如果我删除IllformedLocaleException 并将其替换为标准异常,应用程序运行良好,尽管setVoice21 中有许多其他方法引用> API21

让我更困惑的是,setVoice21 调用了以下类

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private class TTSVoice {

    public void buildVoice() {

        try {
            // Do some API 21 stuff
        } catch (final IllformedLocaleException e) {
        }

    }
}

这个类只引用自 setVoice21,但我不必在这里删除对 IllformedLocaleException 的引用 - 我可以离开它,应用程序运行良好.... 困惑。

谁能帮我解释为什么 IllformedLocaleException 会导致这个失败?异常的处理方式是否有所不同?

提前谢谢你。

注意 - 我不确定它是否相关,但我正在以标准方式继承 TextToSpeech。我担心这可能会使问题复杂化,但以防万一......

public class MyTextToSpeech extends TextToSpeech {

    public MyTextToSpeech(final Context context, final OnInitListener listener) {
        super(context, listener);
    }
}

编辑 - 解决方法provided by razzledazzle below 确实允许应用程序运行而不会崩溃,但我仍然不知道为什么需要这样的步骤。在处理 API 版本控制之前,我从来没有采取过这样的措施。

【问题讨论】:

  • 你的意思是当你直接调用setVoice21() 时遇到了这个错误?
  • @razzledazzle 没有。只有setVoice。因此问题
  • 这很有趣。接听,期待直接电话。如果还没有,您也可以尝试进行干净的构建。
  • @razzledazzle 多次清理和重建。
  • 删除该答案以避免混淆。

标签: java android exception verifyerror


【解决方案1】:

通过从 catch 参数中删除 IllformedLocaleException 类解决了这个问题。这仍然允许您检查IllformedLocaleException

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21 (@NonNull final String language, @NonNull final String region) {
    try {
        // try some API 21 stuff
        ...
    } catch (final Exception e) {
        e.printStackTrace();
        if (e instanceof IllformedLocaleException) {
            ...
        }
    }

    ...
}

【讨论】:

  • 这行得通,谢谢!在 21 上测试,它在 21 上失败并正确捕获。要是我一开始就知道为什么会这样!
  • 具体原因对我来说仍然是个谜。谢谢你的这个问题,我自己学到了一些新东西。
  • @razzledazzle 我自己也不是很确定,但是stackoverflow.com/a/36814155/253468中有更多详细信息
  • 感谢您添加如此详细的答案。分析字节码绝对是要走的路。是的,Dalvik VM 似乎确实在加载类和所有严格处理 catch 块的地方进行了一些分析。在我怀疑 catch 块之前,我还期望 Activity 中的方法能够被异常处理,通过将该方法作为静态实用方法移走而很快结束。唯一剩下的嫌疑人是 catch 块。也有一些答案建议将反射作为一种解决方法,instanceof 就是其中之一。
  • 再次感谢您使用此解决方法解决了我的生产崩溃问题,我已为此发现奖励您。我已将@TWiStErRob 的答案标记为正确,以便找到原因。对于两个很好的答案,我能为你们找到最好的折衷方案。
【解决方案2】:

TL;DR:例外是例外。无法捕获类型未知的异常。

以下是基于我对 Java/Dalvik 的有限知识和常识的大多数推测。带上一粒盐。 我找到了吐出失败日志行的方法,并证实了我提到的大部分猜测,请参阅下面的附加链接。

您的问题似乎是类是一次加载的,要么加载整个类,要么不加载。 首先进行验证,我想是为了防止一些运行时检查(记住 Android 是资源受限的)。

我使用了以下代码:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public int setVoice21(@NonNull final String language, @NonNull final String region) {
    try {
        // try some API 21 stuff
        new Locale.Builder().build().getDisplayVariant();
    } catch (final IllformedLocaleException ex) {
        ex.printStackTrace();
    }
    return 0;
}

当系统试图创建包含此方法的类的实例时,发生了以下情况:

E/dalvikvm:找不到类 'java.util.Locale$Builder',引用自方法 com.test.TestFragment.setVoice21

加载Locale.Builder 类将是ClassNotFoundException

W/dalvikvm: VFY: 无法解析 Lcom/test/TestFragment 中的新实例 5241 (Ljava/util/Locale$Builder;);
D/dalvikvm: VFY: 在 0x0000 处替换操作码 0x22

然后在那个不存在的类上,它会尝试调用<init> 方法,通过将OP_NEW_INSTANCE 替换为OP_NOP 来阻止该方法。我认为这将是可以生存的,因为我在使用支持库时一直看到这些。我认为这里的假设是,如果找不到该类,那么它一定是用SDK_INT 检查来保护的。此外,如果它通过 dexing/proguard 和其他东西,它一定是故意的,ClassNotFoundException 在运行时是可以接受的。

W/dalvikvm: VFY: 无法解析异常类 5234 (Ljava/util/IllformedLocaleException;)

另一个有问题的类,注意这次它是一个“异常类”,它必须是特殊的。如果您通过以下方式检查此方法的 Java 字节码:

javap -verbose -l -private -c -s TestFragment.class > TestFragment.dis

public int setVoice21(java.lang.String, java.lang.String);
    ...
    Exception table:
       from    to  target type
           0    14    17   Class java/util/IllformedLocaleException
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
         18      11     3    ex   Ljava/util/IllformedLocaleException;
          0      31     0  this   Lcom/test/TestFragment;
          0      31     1 language   Ljava/lang/String;
          0      31     2 region   Ljava/lang/String;
    StackMapTable: number_of_entries = 2
      frame_type = 81 /* same_locals_1_stack_item */
        stack = [ class java/util/IllformedLocaleException ]
      frame_type = 11 /* same */

您确实可以看到Exception tableStackMapTableLocalVariableTable 都包含有问题的类,但不包含Locale$Builder。这可能是因为构建器没有存储在变量中,但从这里可以看出,异常是经过特殊处理的,并且比正常的代码行受到更多的审查。

通过以下方式在 APK 上使用 BakSmali:

apktool.bat d -r -f -o .\disassembled "app-debug.apk"

.method public setVoice21(Ljava/lang/String;Ljava/lang/String;)I
.prologue
:try_start_0
new-instance v1, Ljava/util/Locale$Builder;
invoke-direct {v1}, Ljava/util/Locale$Builder;-><init>()V
...
:try_end_0
.catch Ljava/util/IllformedLocaleException; {:try_start_0 .. :try_end_0} :catch_0
...
:catch_0
move-exception v0
.local v0, "ex":Ljava/util/IllformedLocaleException;
invoke-virtual {v0}, Ljava/util/IllformedLocaleException;->printStackTrace()V

似乎揭示了类似的模式,这里我们实际上可以看到日志中提到的操作码。请注意,.catch 似乎是一个特殊指令,而不是一个操作,因为它前面有一个点。我认为这加强了上面提到的审查:它不是运行时操作,但类需要加载方法中包含的代码。

W/dalvikvm: VFY: 无法在地址 0xe 找到异常处理程序
W/dalvikvm: VFY: 拒绝 Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I

我猜这意味着它无法重建何时从Exception tableStackMapTable 调用哪个catch 块,因为它找不到类来确定父类。这在getCaughtExceptionType 中得到了证实,其中“无法解析异常类”直接导致“无法找到异常处理程序”,因为它找不到不存在的异常的通用超类,例如 } catch (? ex) { 所以它不会知道要抓住什么。

W/dalvikvm: VFY: 在 0x000e 拒绝操作码 0x0d
W/dalvikvm: VFY: 拒绝 Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I

我认为此时验证者只是放弃了,因为它无法理解OP_MOVE_EXCEPTION。这被确认为getCaughtExceptionType 方法仅在一个地方使用,a switch。打破这个我们得到"rejecting opcode"然后它goto bails在调用堆栈上到达"rejected class"。保释后错误代码为VERIFY_ERROR_GENERIC,映射到VerifyError。即使以这种方式工作,也无法找到实际的 JNI 异常抛出的位置。

W/dalvikvm: Verifier 拒绝类 Lcom/test/TestFragment;

针对 setVoice21 方法提出了多次拒绝,因此必须拒绝整个班级(这对我来说似乎很苛刻,可能 ART 在这方面有所不同)。

W/dalvikvm:类初始化在 newInstance 调用中失败 (Lcom/test/TestFragment;)
D/AndroidRuntime:关闭虚拟机
W/dalvikvm:threadid=1:线程以未捕获的异常退出(组=0x41869da0)
E/AndroidRuntime: 致命异常: main
进程:com.bumptech.glide.supportapp.v3,PID:27649
java.lang.VerifyError: com/test/TestFragment

我猜这类似于桌面 Java 中的 ExceptionInInitializerError,当类主体中的 static { } 或静态字段初始值设定项抛出 RuntimeException/Error 时抛出。

为什么instanceof 有效

使用 razzledazzle 的解决方法将这些表更改为包含 java/lang/Exception,并将对 IllformedLocaleException 的依赖关系移动到要在运行时执行的代码中:

     0    14    17   Class java/lang/Exception
19: instanceof    #34                 // class java/util/IllformedLocaleException

同样是Smali:

.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
instance-of v1, v0, Ljava/util/IllformedLocaleException;

E/dalvikvm:找不到类“java.util.IllformedLocaleException”,引用自方法 com.test.TestFragment.setVoice21
W/dalvikvm: VFY: 无法解析 Lcom/test/TestFragment 中的 instanceof 5234 (Ljava/util/IllformedLocaleException;);

现在,和上面Locale$Builder的投诉一样

D/dalvikvm: VFY: 在 0x000f 处替换操作码 0x20

用 ?something? 替换 OP_INSTANCE_OF,它没有说 :)

另一种可能的解决方法

如果您查看android.support.v4.view.ViewCompat* 类,您会注意到并非所有这些类都用于所有版本。在运行时选择正确的一个(在ViewCompat.java 中搜索static final ViewCompatImpl IMPL)并且只加载那个。这确保了即使在类加载时也不会因为缺少类而出现任何奇怪现象并且是高性能的。您可以使用类似的架构来防止该方法在较早的 API 级别上加载。

【讨论】:

  • 所以它维护了一个 catch 块的集合,并检查分配/映射给它们的异常。如果异常类无法解析,则抛出VerifyError,这样对吗?代码的某些部分对我来说仍然很模糊,例如它提到的处理程序。
  • @razzledazzle 将我的发现添加到文本中,查找“已确认”。
  • 感谢您提供如此深入的回答。将其标记为正确发现原因。但是,我将赏金授予@razzledazzle,因为他提供了一种解决方法,使我免于生产崩溃。对于两个对我都有很大帮助的答案,这是我能找到的最佳折衷方案。
猜你喜欢
  • 2011-05-05
  • 1970-01-01
  • 2018-09-22
  • 2010-10-14
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多