java.lang.VerifyError IllformedLocaleException

时间:2016-04-13 22:52:24

标签: 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);
    }
}

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:拒绝   操作码0x0d在0x0168 W / dalvikvm:VFY:被拒绝   LCOM / MYAPP /安卓/语音/ MyTextToSpeech; .setVoice21   (Ljava / lang / String; Ljava / lang / String;)I W / dalvikvm:Verifier被拒绝   class Lcom / myapp / android / speech / MyTextToSpeech;

     

E / AndroidRuntime:FATAL EXCEPTION:main java.lang.VerifyError:   COM / MyApp的/机器人/语音/ MyTextToSpeech

如果我删除IllformedLocaleException并将其替换为标准异常,那么应用程序运行正常,尽管有很多其他方法参考> API 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版本控制之前,我从未采取过这些措施。

2 个答案:

答案 0 :(得分:7)

通过从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) {
            ...
        }
    }

    ...
}

答案 1 :(得分: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;
}

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

  

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:将操作码0x22替换为0x0000

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

  

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

另一个有问题的课程,请注意这次它是一个&#34;异常课程&#34;这一定是特别的。如果通过以下方法检查此方法的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 tableStackMapTable以及LocalVariableTable都包含有问题的类,但不包含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

我想这意味着它无法重建何时调用来自catchException table的{​​{1}}阻止,因为它无法找到确定类的StackMapTable父类。这在getCaughtExceptionType中得到确认,其中&#34;无法解决异常类&#34;直接导致&#34;无法找到异常处理程序&#34;因为它找不到不存在的异常的常见超类,例如} 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 bail调用堆栈到"rejected class"。躲避后,错误代码为VERIFY_ERROR_GENERIC,映射到VerifyError。如果它甚至以这种方式工作,那么无法找到实际的JNI异常被抛出的位置。

  

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

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

  

W / dalvikvm:newInstance调用中的类init失败(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

我猜这类似于桌面Java中的ExceptionInInitializerError,当类主体中的static { }或静态字段初始化程序抛出RuntimeException / Error时,会抛出该instanceof

为什么java/lang/Exception有效

使用razzledazzle的变通方法将这些表更改为包含IllformedLocaleException,并将依赖项移至 0 14 17 Class java/lang/Exception 19: instanceof #34 // class java/util/IllformedLocaleException 到要在运行时执行的代码中:

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

和Smali类似:

Locale$Builder
  

E / dalvikvm:找不到类&#39; java.util.IllformedLocaleException&#39;,从方法com.test.TestFragment.setVoice21中引用了
  W / dalvikvm:VFY:无法在Lcom / test / TestFragment中解析instanceof 5234(Ljava / util / IllformedLocaleException;);

现在,它与上面android.support.v4.view.ViewCompat*

的投诉相同
  

D / dalvikvm:VFY:将代码0x20替换为0x000f

用某些东西替换OP_INSTANCE_OF,它不会说:)

另一种可能的解决方法

如果查看static final ViewCompatImpl IMPL类,您会发现并非所有版本都使用这些类。在运行时选择正确的一个(在ViewCompat.java中搜索{{1}})并且只加载它。这确保即使在课堂加载时也不会因为缺少课程而产生任何怪异并且具有高效性。您可以执行类似的体系结构,以防止在早期的API级别上加载该方法。