java.lang.VerifyError IllformedLocaleException [英] java.lang.VerifyError IllformedLocaleException
问题描述
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.Voice 和< a href =http://developer.android.com/reference/java/util/Locale.Builder.html =nofollow noreferrer> Locale.Builder
当我在设备上运行此代码时, API 21我收到以下错误:
W / dalvikvm:VFY:无法解析异常类6232
(Ljava / util / IllformedLocaleException;)W / dalvikvm:VFY:rejecting
opcode 0x0d at 0x0168 W / dalvikvm:VFY:rejected
Lcom / myapp / android / speech / MyTextToSpeech; .setVoice21
(Ljava / lang / String; Ljava / lang / String;)IW / dalvikvm:验证器被拒绝
class Lcom / myapp / android / speech / MyTextToSpeech;
< AndroidRuntime:FATAL EXCEPTION:main java.lang.VerifyError:
com / myapp / android / speech / MyTextToSpeech
如果我删除 IllformedLocaleException ,并将其替换为标准的异常应用程序运行正常,尽管许多其他参考方法> API21在 setVoice21
为了使我更加困惑, setVoice21
调用以下类
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private class TTSVoice {
public void buildVoice(){
try {
//做一些API 21的东西
} catch(final IllformedLocaleException e){
}
}
}
从 setVoice21
,但我不必在这里删除对IllformedLocaleException的引用 - 我可以离开它,应用程序运行正常.... Baffled。
任何人都可以帮助我了解为什么IllformedLocaleException导致此失败?是否有异常处理方式不同?
我提前感谢您。
注意 - 我不确定它是否相关,但我是将 TextToSpeech 以标准方式。我担心这可能会使问题复杂化,但是为了防止...
public class MyTextToSpeech extends TextToSpeech {
public MyTextToSpeech(final Context context,final OnInitListener listener){
super(context,listener);
}
}
编辑 - 解决方法由razzledazzle提供的以下,确实允许应用程序运行而不会崩溃,但是我仍然不是更聪明的为什么这样的一个步骤是必要的。
TL; DR:异常是例外的。无法捕获类型不知道的异常。
以下是我对Java /达尔维克和常识。
我发现了一种方法,吐出失败的日志行,并确认了大部分我提到的猜测,请看下面的添加链接。
您的问题似乎是类一次加载,或者是加载整个类,也可以不加载类。
验证首先完成我想防止一些运行时检查(记住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:找不到方法com.test引用的类java.util.Locale $ Builder。 TestFragment.setVoice21
加载 Locale.Builder
类将是一个 ClassNotFoundException
。
W / dalvikvm:VFY:无法解析新实例5241(Ljava / util / Locale $ Builder;)在Lcom / test / TestFragment中;
D / dalvikvm:VFY:将代码0x22替换为0x0000
然后在那个不存在的类上,它将尝试调用< init>
方法,通过替换 OP_NEW_INSTANCE
与 OP_NOP
。我认为这将是可以生存的,因为我在使用支持库时看到这些。我认为这里的假设是,如果没有找到类,那么它必须用 SDK_INT
检查来保护。另外,如果它经过dexing / proguard和其他的东西,它一定是有意的,一个 ClassNotFoundException
在运行时是可以接受的。
另一个有问题的类,注意这一次它是一个必须是特殊的异常类。如果您通过以下方式检查Java方法的字节码:
javap -verbose -l -private -c -s TestFragment.class> ; TestFragment.dis
public int setVoice21(java.lang.String,java.lang.String);
...
异常表:
从目标类型
0 14 17类java / util / IllformedLocaleException
LocalVariableTable:
开始长度插槽名称签名
18 11 3 ex Ljava / util / IllformedLocaleException;
0 31 0这个Lcom / test / TestFragment;
0 31 1语言Ljava / lang / String;
0 31 2区域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 * /
你可以看到异常表
和 StackMapTable
和 LocalVariableTable
都包含有问题的类,但不包含 Locale $ Builder
。这可能是因为构建器不存储在变量中,但是从这里可以看出,异常处理是特别处理的,并且比正常代码行得到更多的审查。
通过以下方式在APK上使用BakSmali:
apktool.bat d -r -f -o .\disassembledapp-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;)我
我想这意味着它不能重建什么时候调用哪个 catch
块从异常表
和 StackMapTable
,因为找不到类来确定父类。这在 getCaughtExceptionType
其中无法解析异常类直接导致无法找到异常处理程序,因为它找不到不存在的异常的常见超类,像} catch(?ex){
,所以它不知道要抓到什么。
W / dalvikvm:VFY:拒绝操作码0x0d在0x000e
W / dalvikvm:VFY:rejected Lcom / test / TestFragment; .setVoice21(Ljava / lang / String; Ljava / lang / String; )我
我认为在这一点上,验证者只是放弃了,因为它无法理解 OP_MOVE_EXCEPTION
。这被确认为 getCaughtExceptionType
方法仅在一个地方使用,一个开关。打破这个我们得到拒绝操作码,然后将 goto bail
将调用堆栈上传到被拒绝的类。在bailing之后,错误代码是 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:FATAL EXCEPTION:main
进程:com.bumptech.glide.supportapp.v3,PID:27649
java.lang.VerifyError :com / test / TestFragment
我猜这是类似于一个 ExceptionInInitializerError
在桌面Java中,当$ sta时抛出/> c $ c> $ / code>在类体中的tic {}
或静态字段初始化器抛出一个 RuntimeException
/
为什么 instanceof
工作
使用razzledazzle的变通方法更改这些表包含 java / lang / Exception
,并将依赖关系移动到 IllformedLocaleException
到要在运行时执行的代码:
0 14 17类java / lang /异常
19:instanceof#34 // class java / util / IllformedLocaleException
同样的Smali:
.catch Ljava / lang / Exception; {:try_start_0 ..:try_end_0}:catch_0
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:替换opcode 0x20在0x000f
更换 OP_INSTANCE_OF
与...有关的东西,不说:)
另一种可能的解决方法
如果你看您将注意到,所有版本都不使用所有这些类。在运行时选择正确的一个(在 ViewCompat.java
中搜索 static final ViewCompatImpl IMPL
),只有加载。这确保即使在类加载的时候,由于缺少类也不会有任何奇怪的情况,并且是性能的。您可以执行类似的架构,以防止该方法加载到较早的API级别。
I have the following parent method, that is used in all cases by various API levels:
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);
}
}
and setVoice21
does something like this:
@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
contains other code that requires API 21+ specifically TextToSpeech.Voice and Locale.Builder
When I run this code on a device < API 21 I'm getting the following error:
W/dalvikvm: VFY: unable to resolve exception class 6232 (Ljava/util/IllformedLocaleException;) W/dalvikvm: VFY: rejecting 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
If I remove the IllformedLocaleException and just replace it with a standard Exception, the app runs fine, despite the many other references to methods > API21 within setVoice21
To confuse me yet further, setVoice21
invokes the following class
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private class TTSVoice {
public void buildVoice() {
try {
// Do some API 21 stuff
} catch (final IllformedLocaleException e) {
}
}
}
This class is only referenced from setVoice21
, but I do not have to remove the reference to IllformedLocaleException here - I can leave it and the app runs fine.... Baffled.
Can anyone help me out as to why the IllformedLocaleException is causing this failure? Are Exceptions somehow handled differently?
I thank you in advance.
Note - I'm not sure that it is relevant, but I'm subclassing TextToSpeech in a standard way. I fear this may convolute the question, but just in case...
public class MyTextToSpeech extends TextToSpeech {
public MyTextToSpeech(final Context context, final OnInitListener listener) {
super(context, listener);
}
}
EDIT - The workaround provided by razzledazzle below, does allow the app to run without crashing, but I still remain non-the-wiser as to why such a step is necessary. I've never had to take such measures before when dealing with API versioning.
TL;DR: Exceptions are exceptional. Can't catch an exception whose type is not known.
The following is most speculation based on my limited knowledge about Java/Dalvik and common sense. Take it with a grain of salt.
I found the method that spits out the failing log line and confirmed most of my mentioned speculations, see added links below.
Your problem seems to be that the classes are loaded at once, either the whole class is loaded or none of it. Verification is done first I guess to prevent some runtime checks (remember Android is resource constrained).
I used the following code:
@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;
}
When the system was trying to create an instance of the class containing this method the following happened:
E/dalvikvm: Could not find class 'java.util.Locale$Builder', referenced from method com.test.TestFragment.setVoice21
Loading the Locale.Builder
class would be a ClassNotFoundException
.
W/dalvikvm: VFY: unable to resolve new-instance 5241 (Ljava/util/Locale$Builder;) in Lcom/test/TestFragment;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0000
Then on that non-existent class it would try to call the <init>
method which is prevented by replacing the OP_NEW_INSTANCE
with a OP_NOP
. I think this would have been survivable, as I see these all the time when using the support library. I think the assumption here is that if the class is not found then it must have been guarded with an SDK_INT
check. Also if it went through dexing/proguard and other stuff it must've been intentional and a ClassNotFoundException
is acceptable at runtime.
W/dalvikvm: VFY: unable to resolve exception class 5234 (Ljava/util/IllformedLocaleException;)
Another problematic class, notice this time it's an "exception class" which must be special. If you check the Java bytecode for this method via:
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 */
You can indeed see that the Exception table
and StackMapTable
and LocalVariableTable
all contain the problematic class, but not Locale$Builder
. This may be because the builder is not stored in a variable, but the point to take from here is that exceptions are handled specially and get more scrutiny than normal lines of code.
Using BakSmali on the APK via:
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
seems to reveal a similar pattern, here we can actually see the op-codes mentioned in the log. Notice that .catch
seems to be a special instruction, not an operation because it's preceded by a dot. I think this reinforces the scrutiny mentioned above: it's not a runtime operation, but it is required for the class to load the code contained within the methods.
W/dalvikvm: VFY: unable to find exception handler at addr 0xe
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I
I guess this means that it was not able to reconstruct when to call which catch
block from the Exception table
and StackMapTable
because it couldn't find the class to determine the parent classes. This is confirmed in getCaughtExceptionType
where "unable to resolve exception class" directly leads to "unable to find exception handler" because it finds no common super-class for a non-existent exception, something like } catch (? ex) {
so it doesn't know what to catch.
W/dalvikvm: VFY: rejecting opcode 0x0d at 0x000e
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I
I think at this point the verifier just gave up because it couldn't make sense of the OP_MOVE_EXCEPTION
. This is confirmed as that the getCaughtExceptionType
method is only used in one place, a switch. Breaking out of that we get "rejecting opcode" then it goto bail
s up the call stack to "rejected class". After bailing the error code was VERIFY_ERROR_GENERIC
which is mapped to VerifyError
. Couldn't find where the actual JNI Exception is thrown if it even works that way.
W/dalvikvm: Verifier rejected class Lcom/test/TestFragment;
Multiple rejections were filed against the setVoice21
method and hence the whole class must be rejected (this seems harsh to me, it's possible ART is different in this regard).
W/dalvikvm: Class init failed in newInstance call (Lcom/test/TestFragment;)
D/AndroidRuntime: Shutting down VM
W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x41869da0)
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bumptech.glide.supportapp.v3, PID: 27649
java.lang.VerifyError: com/test/TestFragment
I guess this is similar to an ExceptionInInitializerError
in desktop Java which is thrown when a static { }
in class body or an static field initializer throws a RuntimeException
/Error
.
Why instanceof
works
Using razzledazzle's workaround changes those tables to include java/lang/Exception
, and moves the dependency to IllformedLocaleException
into the code to be executed at runtime:
0 14 17 Class java/lang/Exception
19: instanceof #34 // class java/util/IllformedLocaleException
and similarly the Smali:
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
instance-of v1, v0, Ljava/util/IllformedLocaleException;
E/dalvikvm: Could not find class 'java.util.IllformedLocaleException', referenced from method com.test.TestFragment.setVoice21
W/dalvikvm: VFY: unable to resolve instanceof 5234 (Ljava/util/IllformedLocaleException;) in Lcom/test/TestFragment;
Now, it's the same complaint as for Locale$Builder
above
D/dalvikvm: VFY: replacing opcode 0x20 at 0x000f
Replacing OP_INSTANCE_OF
with ?something?, it doesn't say :)
Another possible workaround
If you look at android.support.v4.view.ViewCompat*
classes you will notice that not all those classes are used on all versions. The correct one is chosen at runtime (search for static final ViewCompatImpl IMPL
in ViewCompat.java
) and only that is loaded. This ensures that even at class load time there won't be any weirdness due to missing classes and is performant. You can do a similar architecture to prevent that method from loading on earlier API levels.
这篇关于java.lang.VerifyError IllformedLocaleException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!