Java.lang.VerifyError IllformedLocaleException

Tengo el siguiente método padre, que se utiliza en todos los casos por varios niveles de 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); } } 

Y setVoice21 hace algo como esto:

 @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 contiene otro código que requiere API 21+ específicamente TextToSpeech.Voice y Locale.Builder

Cuando ejecuto este código en un dispositivo <API 21 Tengo el siguiente error:

W / dalvikvm: VFY: no se puede resolver la clase de excepción 6232 (Ljava / util / IllformedLocaleException;) W / dalvikvm: VFY: rechazar el código de operación 0x0d en 0x0168 W / dalvikvm: VFY: rechazado Lcom / myapp / android / speech / MyTextToSpeech ;.setVoice21 IW / dalvikvm: Verificador rechazado clase Lcom / myapp / android / speech / MyTextToSpeech;

E / AndroidRuntime: EXCEPCIÓN FATAL: main java.lang.VerifyError: com / myapp / android / speech / MyTextToSpeech

Si elimino la excepción IllformedLocaleException y solo la reemplazo con una excepción estándar, la aplicación se ejecuta correctamente, a pesar de las muchas otras referencias a métodos> API21 dentro de setVoice21

Para confundirme aún más, setVoice21 invoca la siguiente clase

 @TargetApi(Build.VERSION_CODES.LOLLIPOP) private class TTSVoice { public void buildVoice() { try { // Do some API 21 stuff } catch (final IllformedLocaleException e) { } } } 

Esta clase sólo se hace referencia desde setVoice21 , pero no tengo que eliminar la referencia a IllformedLocaleException aquí – puedo dejarlo y la aplicación corre muy bien …. Baffled.

¿Puede alguien ayudarme a saber por qué la IllformedLocaleException está causando este error? ¿Las excepciones se manejan de alguna manera de manera diferente?

Os doy las gracias de antemano.

Nota – No estoy seguro de que sea relevante, pero estoy subclasificando TextToSpeech de una manera estándar. Me temo que esto puede convolute la pregunta, pero por si acaso …

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

EDIT – La solución proporcionada por razzledazzle a continuación , permite que la aplicación se ejecute sin estrellarse, pero sigo siendo no-el-sabio de por qué tal paso es necesario. Nunca he tenido que tomar esas medidas antes cuando se trata de versión de API.

TL: DR: Las excepciones son excepcionales. No se puede detectar una excepción cuyo tipo no se conoce.

Lo que sigue es la mayoría de la especulación basada en mi conocimiento limitado sobre Java / Dalvik y sentido común. Tómelo con un grano de sal. Encontré el método que escupe la línea de registro fallando y confirmó la mayor parte de mis especulaciones mencionadas, vea los acoplamientos agregados abajo.

Su problema parece ser que las clases se cargan a la vez, ya sea toda la clase está cargada o nada de ella. La verificación se hace primero, supongo que para evitar algunas comprobaciones de tiempo de ejecución (recuerde que Android está limitado por recursos).

He utilizado el siguiente código:

 @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; } 

Cuando el sistema intentaba crear una instancia de la clase que contenía este método, sucedió lo siguiente:

E / dalvikvm: No se pudo encontrar la clase 'java.util.Locale $ Builder', referenciada desde el método com.test.TestFragment.setVoice21

Cargar la clase Locale.Builder sería un ClassNotFoundException .

W / dalvikvm: VFY: no puede resolver la nueva instancia 5241 (Ljava / util / Locale $ Builder;) en Lcom / test / TestFragment;
D / dalvikvm: VFY: reemplazar opcode 0x22 en 0x0000

Entonces en esa clase inexistente intentaría llamar al método <init> que se evita reemplazando el OP_NEW_INSTANCE por un OP_NOP . Creo que esto habría sido superviviente, como lo veo todo el tiempo cuando se utiliza la biblioteca de soporte. Creo que la suposición aquí es que si la clase no se encuentra, entonces debe haber sido guardado con una comprobación SDK_INT . Además, si pasó por dexing / proguard y otras cosas, debe haber sido intencional y una ClassNotFoundException es aceptable en tiempo de ejecución.

W / dalvikvm: VFY: no puede resolver clase de excepción 5234 (Ljava / util / IllformedLocaleException;)

Otra clase problemática, nota esta vez es una "clase de excepción" que debe ser especial. Si comprueba el bytecode de Java para este método mediante:

 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 */ 

De hecho, puede ver que la Exception table y StackMapTable y LocalVariableTable contienen la clase problemática, pero no Locale$Builder . Esto puede deberse a que el constructor no está almacenado en una variable, pero el punto a tomar de aquí es que las excepciones se manejan especialmente y obtienen más escrutinio que las líneas normales de código.

Usando BakSmali en el APK vía:

 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 

Parece revelar un patrón similar, aquí podemos ver realmente los op-códigos mencionados en el registro. Observe que .catch parece ser una instrucción especial, no una operación porque está precedida por un punto. Creo que esto refuerza el escrutinio mencionado anteriormente: no es una operación en tiempo de ejecución, pero se requiere para que la clase cargue el código contenido dentro de los métodos.

W / dalvikvm: VFY: no puede encontrar el controlador de excepción en addr 0xe
W / dalvikvm: VFY: Lcom / test / TestFragment rechazado; .setVoice21 (Ljava / lang / String; Ljava / lang / String;) I

Supongo que esto significa que no fue capaz de reconstruir cuándo llamar al bloque catch de la Exception table y StackMapTable porque no pudo encontrar la clase para determinar las clases padre. Esto se confirma en getCaughtExceptionType donde "no se puede resolver la clase de excepción" conduce directamente a "no se puede encontrar el manejador de excepciones" porque no encuentra ninguna superclase común para una excepción inexistente, algo como } catch (? ex) { así que doesn No sé qué atrapar.

W / dalvikvm: VFY: rechazar el código de operación 0x0d en 0x000e
W / dalvikvm: VFY: Lcom / test / TestFragment rechazado; .setVoice21 (Ljava / lang / String; Ljava / lang / String;) I

Creo que en este momento el verificador acaba de renunciar porque no podía tener sentido de la OP_MOVE_EXCEPTION . Esto se confirma que el método getCaughtExceptionType sólo se utiliza en un lugar, un conmutador . Rompiendo de que tenemos "rechazar opcode", entonces goto bail hasta la pila de llamadas a "clase rechazada" . Después de rescatar el código de error fue VERIFY_ERROR_GENERIC que se asigna a VerifyError . No se pudo encontrar dónde se lanza la excepción JNI real si funciona de esa manera.

W / dalvikvm: Verificador rechazado clase Lcom / test / TestFragment;

Los rechazos múltiples fueron archivados contra el método de setVoice21 y por lo tanto la clase entera debe ser rechazada (esto me parece áspero, es posible que el ART sea diferente a este respecto).

W / dalvikvm: La clase init falló en la llamada newInstance (Lcom / test / TestFragment;)
D / AndroidRuntime: Apagado de VM
W / dalvikvm: threadid = 1: hilo saliendo con excepción no captada (grupo = 0x41869da0)
E / AndroidRuntime: FATAL EXCEPTION: principal
Proceso: com.bumptech.glide.supportapp.v3, PID: 27649
Java.lang.VerifyError: com / test / TestFragment

Supongo que esto es similar a un ExceptionInInitializerError en Java de escritorio que se lanza cuando un static { } en el cuerpo de la clase o un inicializador de campo estático lanza RuntimeException / Error .

Por qué la instanceof obras

El uso de la solución de razzledazzle cambia las tablas para incluir java/lang/Exception y mueve la dependencia a IllformedLocaleException en el código que se ejecutará en tiempo de ejecución:

  0 14 17 Class java/lang/Exception 19: instanceof #34 // class java/util/IllformedLocaleException 

Y similarmente el Smali:

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

E / dalvikvm: No se pudo encontrar la clase 'java.util.IllformedLocaleException', referenciada desde el método com.test.TestFragment.setVoice21
W / dalvikvm: VFY: no puede resolver instanceof 5234 (Ljava / util / IllformedLocaleException;) en Lcom / test / TestFragment;

Ahora, es la misma queja de Locale$Builder anterior

D / dalvikvm: VFY: reemplazar opcode 0x20 en 0x000f

Reemplazando OP_INSTANCE_OF con "algo", no dice 🙂

Otra posible solución alternativa

Si observa las clases android.support.v4.view.ViewCompat* , notará que no todas esas clases se utilizan en todas las versiones. La correcta es escogida en tiempo de ejecución (buscar static final ViewCompatImpl IMPL en ViewCompat.java ) y sólo eso se carga. Esto asegura que incluso en el tiempo de carga de clase no habrá ninguna rareza debido a las clases que faltan y es performant. Puede hacer una arquitectura similar para evitar que dicho método se cargue en niveles anteriores de la API.

Resolvió el problema eliminando la clase IllformedLocaleException del argumento catch. Esto todavía le permitirá comprobar para 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) { ... } } ... } 
  • Cómo depurar la aplicación de Android construida con maven
  • ¿Cómo puedo obtener una variable de otra clase en Java?
  • Android: Uso de AUTO-CANCEL en una notificación cuando la aplicación se está ejecutando en segundo plano
  • "Error al exportar la aplicación" en Eclipse 4.4.1 para Android con ADT 23.0.4
  • ¿Cómo desactivar y activar el desplazamiento en Android ScrollView?
  • ¿Hacer saltar un sprite cuando el usuario teclea en la pantalla?
  • Cuánta de las principales API de Java están presentes en la API de Android
  • Androide, socket, programación, atrás, router
  • Manteniendo las cosas descentralizadas en Android
  • Android MediaRecorder stop () no se llama
  • Tamaño de la página RAM de Android?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.