Verificación del comportamiento de la app en el tiempo de ejecución de Android (ART)

El tiempo de ejecución de Android (ART) es el tiempo de ejecución predeterminado para dispositivos con Android 5.0 (nivel de API 21) y versiones posteriores. Este tiempo de ejecución ofrece varias funciones que mejoran el rendimiento y la fluidez de la plataforma de y las apps de Android. Puedes encontrar más información sobre las nuevas funciones del ART en Introducción al ART.

Sin embargo, algunas técnicas que funcionan en Dalvik no sirven en ART. En este documento, verás aspectos que debes observar al realizar la migración de una app existente para que sea compatible con el ART. La mayoría de las apps solo deben funcionar cuando se ejecutan con el ART.

Tratamiento de problemas de recolección de elementos no usados (GC)

Con Dalvik, en las apps a menudo resulta útil llamar de manera explícita a System.gc() para solicitar la colección de elementos no usados (GC). Esto debe ser mucho menos necesario con el ART, en particular si invocas a una colección de elementos no usados para evitar casos del tipo GC_FOR_ALLOC o reducir la fragmentación. Puedes verificar el tiempo de ejecución en uso llamando a System.getProperty("java.vm.version"). Si el ART se encuentra en uso, el valor de la propiedad es "2.0.0" o posterior.

Además, a fin de mejorar la administración de memoria, se encuentra bajo desarrollo un recolector con compactación de elementos no usados en el proyecto de código abierto de Android (AOSP). Por lo tanto, debes evitar usar técnicas que no sean compatibles con recolector con compactación de elementos no usados (como guardar punteros en datos de instancias de objetos). Esto tiene particular importancia para las apps que usan la interfaz nativa de Java (JNI). Para obtener más información, consulta Prevención de problemas de la JNI.

Prevención de problemas de la JNI

La JNI del ART es un poco más estricta que la de Dalvik. Se recomienda en particular usar el modo CheckJNI para capturar problemas comunes. Si tu app usa código C/C++ , debes revisar el siguiente artículo:

Depuración de la JNI de Android con CheckJNI

Verificación del código de la JNI en busca de problemas de recolección de elementos no usados

En el caso del ART, se encuentra en desarrollo un recolector con compactación de elementos no usados en el proyecto de código abierto de Android (AOSP). Una vez que el recolector con compactación de elementos no usados se encuentra en uso, los objetos pueden quitarse de la memoria. Si usas código C/C++, no realices operaciones que no sean compatibles con el recolector con compactación de elementos no usados. Hemos mejorado CheckJNI para identificar algunos problemas potenciales (como se describe en Cambios de referencia local de JNI en ICS).

Un área que debe observarse en particular es el uso de Get...ArrayElements() y las funciones Release...ArrayElements(). En tiempos de ejecución con recolector sin compactación de elementos no usados, las funciones Get...ArrayElements() generalmente muestran una referencia para la memoria actual que respalda el objeto de matriz. Si realizas un cambio en uno de los elementos de matriz que se muestran, el objeto de matriz se cambia solo (y los argumentos para Release...ArrayElements() generalmente se ignoran). Sin embargo, si se usa un recolector con compactación de elementos no usados, las funciones Get...ArrayElements() pueden mostrar una copia de la memoria. Si no usas bien la referencia cuando se use el recolector con compactación de elementos no usados, se pueden producir daños en la memoria u otros problemas. Por ejemplo:

  • Si realizas un cambio en los elementos de matriz, debes llamar a la función correspondiente Release...ArrayElements() cuando finalices para asegurarte de que los cambios que realizaste se copien de manera correcta en el objeto de matriz subyacente.
  • Cuando lanzas los elementos de matriz de memoria, debes usar el modo correspondiente, según los cambios que hayas realizado:
    • Si no realizaste ningún cambio en los elementos de matriz, usa el modo JNI_ABORT, que libera la memoria sin copiar de nuevo los cambios en el objeto de matriz subyacente.
    • Si realizaste cambios en la matriz y ya no necesitas la referencia, usa el código 0 (que actualiza el objeto de matriz y libera la copia de la memoria).
    • Si realizaste cambios en la matriz que deseas confirmar, y deseas mantener la copia de la matriz, usa JNI_COMMIT (que actualiza el objeto de matriz subyacente y retiene la copia).
  • Cuando llamas a Release...ArrayElements(), se muestra el mismo puntero que Get...ArrayElements() mostró originalmente. Por ejemplo, no es seguro aumentar el puntero original (para recorrer los elementos de matriz mostrados) y luego pasa el puntero aumentado a Release...ArrayElements(). El paso de este puntero modificado puede hacer que se libere la memoria equivocada, con lo cual se producirán daños en la memoria.

Administración de errores

La JNI del ART genera errores en varios casos en los cuales no sucede lo mismo con Dalvik. (Una vez más, puedes capturar muchos casos realizando pruebas con CheckJNI).

Por ejemplo, si se llama RegisterNatives con un método que no existe (tal vez porque se quitó el método a través de una herramienta como ProGuard), ART generará NoSuchMethodErrorcorrectamente:

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

ART también carga un error (visible en logcat) si se llama a RegisterNatives sin métodos:

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

Además, las funciones de JNI GetFieldID() y GetStaticFieldID() generan NoSuchFieldError correctamente en lugar de simplemente mostrar null. De modo similar, GetMethodID() y GetStaticMethodID() ahora genera NoSuchMethodErrorcorrectamente. Esto puede ocasionar fallas en CheckJNI debido a las excepciones que no se manejan o las excepciones que se generan en emisores de Java de código nativo. Esto atribuye particular importancia a la prueba de apps compatibles con el ART a través del modo CheckJNI.

El ART supone que los usuarios de los métodos de JNI CallNonvirtual...Method() (como CallNonvirtualVoidMethod()) apliquen la clase de declaración de los métodos, no subclases, como lo requiere la especificación de JNI.

Prevención de problemas de tamaño de pila

Dalvik tenía pilas separadas para el código nativo y el código Java con un tamaño de pila predeterminado de 32 KB y un tamaño de pila nativa predeterminado de 1 MB. El ART tiene una pila unificada para una mejor localidad. Generalmente, el tamaño de la pila del ART Thread debe ser aproximadamente el mismo que para Dalvik. Sin embargo, si configuras los tamaños de las pilas de modo explícito, tal vez debas repasar esos valores para apps que se ejecutan en el ART.

  • En Java, llamadas de revisión al constructor Thread que especifican una tamaño de pila explícito. Por ejemplo, deberás aumentar el tamaño si tiene lugar StackOverflowError.
  • En C/C++, uso para revisión de pthread_attr_setstack() y pthread_attr_setstacksize() para subprocesos que también ejecutan código Java a través de JNI. A continuación, te mostramos un ejemplo del error que se registra cuando una app intenta llamar a JNI AttachCurrentThread() en casos en los cuales el tamaño de pthread es demasiado reducido:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Cambios de modelo de objeto

Dalvik permitió de modo incorrecto que las subclases anulen los métodos privados de paquetes. El ART emite una precaución en dichos casos:

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

Si intentas anular un método de la clase en un paquete diferente, declara el método como public o protected.

AhoraObject tiene campos privados. Las apps que se reflejan en campos en sus jerarquías de clases deben procurar no intentar observar los campos de Object. Por ejemplo, si realizas una iteración en una jerarquía de clase como parte de un framework de serialización, detente cuando

Class.getSuperclass() == java.lang.Object.class
en lugar de continuar hasta que el método muestre null.

El proxy InvocationHandler.invoke() recibe null si no hay argumentos en lugar una de matriz vacía. Este comportamiento se documentó previamente, pero no se manejó de modo correcto en Dalvik. En las versiones previas de Mockito existen complicaciones con esto. Por ello, usa una versión actualizada de Mockito cuando realices pruebas con el ART.

Solución de problemas de compilación de AOT

La compilación de Java por adelantado (AOT) de ART debe funcionar con todos los códigos estándares de Java. La compilación se lleva a cabo con la herramienta dex2oat del ART. Si encuentra algún problema relacionado con dex2oat en el momento de la instalación, notifícalo (consulta Informe de problemas) para que los podamos corregir lo más pronto posible. Algunos problemas que deben tenerse en cuenta:

  • Durante la instalación, el ART realiza una verificación de código de bytes más estricta que la de Dalvik. El código que producen las herramientas de compilación de Android debe funcionar bien. Sin embargo, algunas herramientas de procesamiento posterior (en especial las que generan ocultamientos) pueden producir archivos inválidos que Dalvik tolerará y ART rechazará. Hemos trabajado con proveedores de herramientas para encontrar y corregir esos problemas. En muchos casos, la corrección es posible obteniendo las últimas versiones de tus herramientas y regenerando los archivos DEX.
  • Entre algunos de los problemas típicos que marca el verificador del ART se incluyen los siguientes:
    • flujo de control no válido;
    • desequilibrio en moniterenter o moniterexit;
    • tamaño de lista del tipo de parámetro de longitud 0.
  • Algunas apps tienen dependencias en el formato de archivo instalado .odex en /system/framework, /data/dalvik-cache o el directorio de salida optimizado de DexClassLoader. Ahora, estos archivos son ELF y no formas extendidas de archivos DEX. Aunque el ART intenta seguir la mismas reglas de nombramiento y bloqueo de Dalvik, las apps no deberán depender del formato de archivo. El formato está sujeto a cambios sin previo aviso.
  • Informe de problemas

    Si encuentras algún problema que no se deba a problemas de la app de JNI, infórmalos a través del seguimiento de problemas del proyecto de código abierto de Android en https://code.google.com/p/android/issues/list. Incluye un "adb bugreport" y un vínculo con la app en la Google Play Store si se encuentra disponible. De lo contrario, si es posible, adjunta un APK que reproduzca el problema. Ten en cuenta que los problemas (incluso los archivos adjuntos) pueden verse públicamente.