java.lang.VerifyError IllformedLocaleException

2022-09-04 03:42:44

我有以下父方法,在所有情况下都由各种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

当我在< API 21 的设备上运行此代码时,我收到以下错误:

W/dalvikvm: VFY: 无法解析异常类 6232 (Ljava/util/IllformedLocaleException;)W/dalvikvm: VFY: reject opcode 0x0d at 0x0168 W/dalvikvm: VFY: rejected Lcom/myapp/android/speech/MyTextToSpeech;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I W/dalvikvm: Verifier rejected class Lcom/myapp/android/speech/MyTextToSpeech;

E/AndroidRuntime: FATAL EXCEPTION: main java.lang.VerifyError: com/myapp/android/speech/MyTextToSpeech

如果我删除IllformedLocaleException,只是用一个标准的 Exception替换它,那么应用程序运行良好,尽管在API21中>方法的许多其他引用。setVoice21

为了进一步混淆我,调用以下类setVoice21

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

    public void buildVoice() {

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

    }
}

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

任何人都可以帮助我解释为什么病态LocaleException导致这种失败?异常的处理方式是否有所不同?

我提前感谢你。

注意 - 我不确定它是否相关,但我以标准方式子类化TextToSpeech。我担心这可能会引起这个问题,但以防万一...

public class MyTextToSpeech extends TextToSpeech {

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

编辑 - 下面的razzledazzle提供的解决方法确实允许应用程序在不崩溃的情况下运行,但我仍然不知道为什么这样的步骤是必要的。在处理 API 版本控制时,我以前从未采取过此类措施。


答案 1

通过从 catch 参数中删除类解决了该问题。这仍将允许您检查 .IllformedLocaleExceptionIllformedLocaleException

@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) {
            ...
        }
    }

    ...
}

答案 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',引用自 method com.test.TestFragment.setVoice21

加载该类将是一个 .Locale.BuilderClassNotFoundException

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

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

W/dalvikvm: VFY: 無法解析 exception class 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 */

您确实可以看到 and 和 都包含有问题的类,但不包含 。这可能是因为生成器没有存储在变量中,但从这里开始的要点是,异常是专门处理的,并且比正常的代码行受到更多的审查。Exception tableStackMapTableLocalVariableTableLocale$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: 无法在 addr 0xe找到异常处理程序
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I

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

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

我认为在这一点上,验证者只是放弃了,因为它无法理解OP_MOVE_EXCEPTION。这得到了确认,因为该方法仅在一个地方使用,即开关。除此之外,我们得到“拒绝操作码”,然后它将调用堆栈上升到“拒绝类”。保释后,VERIFY_ERROR_GENERIC错误代码映射到 。找不到实际的 JNI 异常在哪里被抛出,如果它甚至以这种方式工作。getCaughtExceptionTypegoto bailVerifyError

W/dalvikvm: Verifier rejected class Lcom/test/TestFragment;

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

W/dalvikvm: Class init failed in newInstance call (Lcom/test/TestFragment;)
D/AndroidRuntime: Shutdown down VM
W/dalvikvm: threadid=1: thread exit with uncaught exception (group=0x41869da0)
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bumptech.glide.supportapp.v3, PID: 27649
java.lang.VerifyError: com/test/TestFragment

我想这类似于桌面Java中的Java,当一个在类体或静态字段初始值设定项抛出一个/时,就会被抛出。ExceptionInInitializerErrorstatic { }RuntimeExceptionError

为什么有效instanceof

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

     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', 引用自 method com.test.TestFragment.setVoice21
W/dalvikvm: VFY: 无法解析 instanceof 5234 (Ljava/util/IllformedLocaleException;)在Lcom/test/TestFragment中;

现在,它与上面的投诉相同Locale$Builder

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

用“某物”代替OP_INSTANCE_OF,它没有说:)

另一种可能的解决方法

如果您查看类,您会注意到并非所有这些类都用于所有版本。在运行时选择正确的一个(搜索 in ),并且仅加载该代码。这确保了即使在类加载时也不会由于缺少类而出现任何怪异,并且性能良好。您可以执行类似的体系结构,以防止在较早的 API 级别上加载该方法。android.support.v4.view.ViewCompat*static final ViewCompatImpl IMPLViewCompat.java