java.lang.VerifyError IllformedLocaleException
java.lang.VerifyError IllformedLocaleException
我有以下父方法,在所有情况下都被各种 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.Voice and Locale.Builder
当我 运行 设备上的此代码 < API 21 我收到以下错误:
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
如果我删除 IllformedLocaleException 并将其替换为标准异常,应用程序 运行 没问题,尽管 [=] 中有许多其他方法 > API21 的引用15=]
让我更加困惑的是,setVoice21
调用了以下 class
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private class TTSVoice {
public void buildVoice() {
try {
// Do some API 21 stuff
} catch (final IllformedLocaleException e) {
}
}
}
此 class 仅从 setVoice21
引用,但我不必在此处删除对 IllformedLocaleException 的引用 - 我可以保留它并且应用程序 运行 没问题.. ..莫名其妙。
任何人都可以帮助我了解为什么 IllformedLocaleException 会导致此失败吗?异常处理是否有所不同?
先谢谢你了。
注意 - 我不确定它是否相关,但我以标准方式 classing TextToSpeech。我担心这可能会使问题复杂化,但以防万一...
public class MyTextToSpeech extends TextToSpeech {
public MyTextToSpeech(final Context context, final OnInitListener listener) {
super(context, listener);
}
}
EDIT - 解决方法 确实允许应用程序 运行 而不会崩溃,但我仍然不明白为什么这样的步骤是必要的。在处理 API 版本控制之前,我从来没有采取过这样的措施。
通过从 catch 参数中删除 IllformedLocaleException
class 解决了该问题。这仍然允许您检查 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) {
...
}
}
...
}
TL;DR:例外是例外。无法捕获类型未知的异常。
以下是根据我对Java/Dalvik的有限了解和常识的大部分推测。持保留态度
我找到了吐出失败日志行的方法并证实了我提到的大部分推测,请参阅下面添加的链接。
您的问题似乎是 class 是一次加载的,要么加载整个 class 要么加载其中的 none。
我想首先进行验证是为了防止一些运行时检查(记住 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;
}
当系统尝试创建包含此方法的 class 实例时,发生了以下情况:
E/dalvikvm: Could not find class 'java.util.Locale$Builder', referenced from method com.test.TestFragment.setVoice21
加载 Locale.Builder
class 将是 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
然后在那个不存在的 class 上,它将尝试调用 <init>
方法,该方法通过替换 OP_NEW_INSTANCE
with a OP_NOP
来阻止。我认为这是可以生存的,因为我在使用支持库时一直看到这些。我认为这里的假设是,如果 class 没有找到,那么它一定受到了 SDK_INT
检查的保护。此外,如果它经历了 dexing/proguard 和其他东西,那一定是故意的,并且 ClassNotFoundException
在运行时是可以接受的。
W/dalvikvm: VFY: unable to resolve exception class 5234 (Ljava/util/IllformedLocaleException;)
另一个有问题的class,注意这次是一个"exception class",它一定是特殊的。如果您通过以下方式检查此方法的 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 table
和 StackMapTable
和 LocalVariableTable
都包含有问题的 class,而不是 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
似乎是一个特殊指令,而不是一个操作,因为它前面有一个点。我认为这加强了上面提到的审查:它不是运行时操作,但是 class 加载方法中包含的代码是必需的。
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
我想这意味着它无法重建何时从 Exception table
和 StackMapTable
调用哪个 catch
块,因为它找不到 class 来确定父 classes。这在 getCaughtExceptionType
中得到证实,其中 "unable to resolve exception class" 直接导致 "unable to find exception handler",因为它没有找到共同的超级 class 用于不存在的异常,例如 } catch (? ex) {
所以它不知道要捕捉什么。
W/dalvikvm: VFY: rejecting opcode 0x0d at 0x000e
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I
我认为此时验证者只是放弃了,因为它无法理解映射到 VerifyError
的 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
。找不到抛出实际 JNI 异常的位置,即使它以这种方式工作。
W/dalvikvm: Verifier rejected class Lcom/test/TestFragment;
多次拒绝 setVoice21
方法,因此整个 class 必须被拒绝(这对我来说似乎很苛刻,可能 ART 在这方面有所不同)。
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
我想这类似于桌面 Java 中的 ExceptionInInitializerError
,当 class 主体中的 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: 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;
现在,这与上面 Locale$Builder
的投诉相同
D/dalvikvm: VFY: replacing opcode 0x20 at 0x000f
将 OP_INSTANCE_OF
替换为 ?something?,它没有说 :)
另一种可能的解决方法
如果您查看 android.support.v4.view.ViewCompat*
classes,您会注意到并非所有这些 classes 都用于所有版本。正确的是在运行时选择的(在 ViewCompat.java
中搜索 static final ViewCompatImpl IMPL
)并且只有那个被加载。这确保即使在 class 加载时也不会因为缺少 classes 而出现任何异常并且是高性能的。您可以使用类似的架构来防止该方法加载到较早的 API 级别。
我有以下父方法,在所有情况下都被各种 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.Voice and Locale.Builder
当我 运行 设备上的此代码 < API 21 我收到以下错误:
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
如果我删除 IllformedLocaleException 并将其替换为标准异常,应用程序 运行 没问题,尽管 [=] 中有许多其他方法 > API21 的引用15=]
让我更加困惑的是,setVoice21
调用了以下 class
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private class TTSVoice {
public void buildVoice() {
try {
// Do some API 21 stuff
} catch (final IllformedLocaleException e) {
}
}
}
此 class 仅从 setVoice21
引用,但我不必在此处删除对 IllformedLocaleException 的引用 - 我可以保留它并且应用程序 运行 没问题.. ..莫名其妙。
任何人都可以帮助我了解为什么 IllformedLocaleException 会导致此失败吗?异常处理是否有所不同?
先谢谢你了。
注意 - 我不确定它是否相关,但我以标准方式 classing TextToSpeech。我担心这可能会使问题复杂化,但以防万一...
public class MyTextToSpeech extends TextToSpeech {
public MyTextToSpeech(final Context context, final OnInitListener listener) {
super(context, listener);
}
}
EDIT - 解决方法
通过从 catch 参数中删除 IllformedLocaleException
class 解决了该问题。这仍然允许您检查 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) {
...
}
}
...
}
TL;DR:例外是例外。无法捕获类型未知的异常。
以下是根据我对Java/Dalvik的有限了解和常识的大部分推测。持保留态度
我找到了吐出失败日志行的方法并证实了我提到的大部分推测,请参阅下面添加的链接。
您的问题似乎是 class 是一次加载的,要么加载整个 class 要么加载其中的 none。 我想首先进行验证是为了防止一些运行时检查(记住 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;
}
当系统尝试创建包含此方法的 class 实例时,发生了以下情况:
E/dalvikvm: Could not find class 'java.util.Locale$Builder', referenced from method com.test.TestFragment.setVoice21
加载 Locale.Builder
class 将是 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
然后在那个不存在的 class 上,它将尝试调用 <init>
方法,该方法通过替换 OP_NEW_INSTANCE
with a OP_NOP
来阻止。我认为这是可以生存的,因为我在使用支持库时一直看到这些。我认为这里的假设是,如果 class 没有找到,那么它一定受到了 SDK_INT
检查的保护。此外,如果它经历了 dexing/proguard 和其他东西,那一定是故意的,并且 ClassNotFoundException
在运行时是可以接受的。
W/dalvikvm: VFY: unable to resolve exception class 5234 (Ljava/util/IllformedLocaleException;)
另一个有问题的class,注意这次是一个"exception class",它一定是特殊的。如果您通过以下方式检查此方法的 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 table
和 StackMapTable
和 LocalVariableTable
都包含有问题的 class,而不是 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
似乎是一个特殊指令,而不是一个操作,因为它前面有一个点。我认为这加强了上面提到的审查:它不是运行时操作,但是 class 加载方法中包含的代码是必需的。
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
我想这意味着它无法重建何时从 Exception table
和 StackMapTable
调用哪个 catch
块,因为它找不到 class 来确定父 classes。这在 getCaughtExceptionType
中得到证实,其中 "unable to resolve exception class" 直接导致 "unable to find exception handler",因为它没有找到共同的超级 class 用于不存在的异常,例如 } catch (? ex) {
所以它不知道要捕捉什么。
W/dalvikvm: VFY: rejecting opcode 0x0d at 0x000e
W/dalvikvm: VFY: rejected Lcom/test/TestFragment;.setVoice21 (Ljava/lang/String;Ljava/lang/String;)I
我认为此时验证者只是放弃了,因为它无法理解映射到 VerifyError
的 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
。找不到抛出实际 JNI 异常的位置,即使它以这种方式工作。
W/dalvikvm: Verifier rejected class Lcom/test/TestFragment;
多次拒绝 setVoice21
方法,因此整个 class 必须被拒绝(这对我来说似乎很苛刻,可能 ART 在这方面有所不同)。
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
我想这类似于桌面 Java 中的 ExceptionInInitializerError
,当 class 主体中的 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: 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;
现在,这与上面 Locale$Builder
的投诉相同
D/dalvikvm: VFY: replacing opcode 0x20 at 0x000f
将 OP_INSTANCE_OF
替换为 ?something?,它没有说 :)
另一种可能的解决方法
如果您查看 android.support.v4.view.ViewCompat*
classes,您会注意到并非所有这些 classes 都用于所有版本。正确的是在运行时选择的(在 ViewCompat.java
中搜索 static final ViewCompatImpl IMPL
)并且只有那个被加载。这确保即使在 class 加载时也不会因为缺少 classes 而出现任何异常并且是高性能的。您可以使用类似的架构来防止该方法加载到较早的 API 级别。