java.lang.VerifyError IllformedLocaleException

bra*_*all 14 java android exception verifyerror

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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;)IW/dalvikvm:Verifier拒绝了类Lcom/myapp/android/speech/MyTextToSpeech;

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

如果我删除了IllformedLocaleException并且只用标准的Exception替换它,那么应用程序运行正常,尽管其他许多方法都引用了方法> API21setVoice21

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

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

    public void buildVoice() {

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

    }
}
Run Code Online (Sandbox Code Playgroud)

这个类只是引用setVoice21,但我不必在这里删除对IllformedLocaleException的引用 - 我可以保留它并且应用程序运行正常....困惑.

任何人都可以帮助我解决IllformedLocaleException导致此失败的原因吗?异常是以某种方式处理不同的吗?

我提前谢谢你.

注意 - 我不确定它是否相关,但我是以标准方式继承TextToSpeech.我担心这可能会使问题卷入其中,但以防万一......

public class MyTextToSpeech extends TextToSpeech {

    public MyTextToSpeech(final Context context, final OnInitListener listener) {
        super(context, listener);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑 - 下面的razzledazzle提供的解决方法确实允许应用程序运行而不会崩溃,但我仍然不明白为什么这样的步骤是必要的.在处理API版本控制之前,我从未采取过这样的措施.

raz*_*zle 7

通过IllformedLocaleException从catch参数中删除类来解决此问题.这仍然允许您检查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) {
            ...
        }
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)


TWi*_*Rob 4

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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>阻止的方法。我认为这是可以生存的,因为我在使用支持库时一直看到这些。我认为这里的假设是,如果找不到该类,那么它一定是通过检查来保护的。另外,如果它经历了 deshing/proguard 和其他东西,那么它一定是故意的,并且 a在运行时是可以接受的。OP_NEW_INSTANCEOP_NOPSDK_INTClassNotFoundException

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 */
Run Code Online (Sandbox Code Playgroud)

您确实可以看到 andException tableStackMapTableallLocalVariableTable包含有问题的类,但不是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
Run Code Online (Sandbox Code Playgroud)

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

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

我想这意味着它无法重建何时catchException tableand调用哪个块StackMapTable,因为它找不到类来确定父类。这在 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方法只用在一个地方,即switch。打破这一点,我们得到“拒绝操作码”,然后它goto bail在调用堆栈中上升到“拒绝类”。保存后,错误代码被VERIFY_ERROR_GENERIC映射到VerifyError. 即使以这种方式工作,也无法找到抛出实际 JNI 异常的位置。

W/dalvikvm:验证程序拒绝了 Lcom/test/TestFragment 类;

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

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

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

为什么instanceof有效

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

     0    14    17   Class java/lang/Exception
19: instanceof    #34                 // class java/util/IllformedLocaleException
Run Code Online (Sandbox Code Playgroud)

和 Smali 类似:

.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
instance-of v1, v0, Ljava/util/IllformedLocaleException;
Run Code Online (Sandbox Code Playgroud)

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

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

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

替换OP_INSTANCE_OF为“某事”,它没有说:)

另一种可能的解决方法

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