Verificar el comportamiento de la aplicación en el tiempo de ejecución de Android (ART)

Además, es el entorno 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 y las apps de Android. Puedes encontrar más información sobre las nuevas funciones de ART en Introducción al ART.

Sin embargo, algunas técnicas que funcionan en Dalvik no sirven en ART. En este documento, encontrarás información sobre qué debes tener en cuenta cuando migres una app existente para que sea compatible con ART. La mayoría de las apps solo deben funcionar cuando se ejecutan con ART.

Cómo afrontar problemas de recolección de elementos no utilizados (GC)

Con Dalvik, en las apps a menudo resulta útil llamar explícitamente a System.gc() para solicitar la recolección de elementos no utilizados (GC). Esto debería ser mucho menos necesario con ART, en especial si invocas la recolección de elementos no utilizados para evitar casos de tipo GC_FOR_ALLOC o reducir la fragmentación. Puedes verificar qué entorno de ejecución está en uso llamando a System.getProperty("java.vm.version"). Si ART está en uso, el valor de la propiedad es "2.0.0" o superior.

El ART usa el recolector de copias simultáneas (CC), que compacta de forma simultánea el montón de Java. Debido a esto, debes evitar usar técnicas que sean incompatibles con la compactación de recolección de elementos no utilizados (como guardar punteros en datos de instancias de objetos). Esto es muy importante para las apps que usan la interfaz nativa de Java (JNI). Para obtener más información, consulta Cómo prevenir 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 especial, usar el modo CheckJNI para detectar problemas comunes. Si tu app usa código C/C++, debes revisar el siguiente artículo:

Cómo depurar la JNI de Android con CheckJNI

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

El recolector de copia simultánea (CC) puede mover objetos en la memoria para su compactación. Si usas código C/C++, no realices operaciones que no sean compatibles con la recolección con compactación de elementos no utilizados. Mejoramos CheckJNI para identificar algunos problemas potenciales (como se describe en Cambios de referencia local de JNI en ICS).

Un área que se debe tener en cuenta en particular es el uso de las funciones Get...ArrayElements() y Release...ArrayElements(). En entornos de ejecución con recolección sin compactación de elementos no utilizados, las funciones Get...ArrayElements() suelen mostrar una referencia a la memoria real que respalda el objeto de array. Si realizas un cambio en uno de los elementos de array que se muestran, el objeto de array se cambia solo (y los argumentos de Release...ArrayElements() suelen ignorarse). Sin embargo, si se usa una recolección de elementos no utilizados compacta, las funciones Get...ArrayElements() pueden mostrar una copia de la memoria. Si no usas bien la referencia cuando se usa la recolección de compactación de elementos no utilizados, se pueden producir daños en la memoria u otros problemas. Por ejemplo:

  • Si realizas algún cambio en los elementos de arreglo que se muestran, debes llamar a la función Release...ArrayElements() adecuada cuando termines para asegurarte de que los cambios que realizaste se copien de forma correcta en el objeto de arreglo subyacente.
  • Cuando lances los elementos de array de memoria, debes usar el modo correspondiente, según los cambios que hayas realizado:
    • Si no realizaste ningún cambio en los elementos del array, usa el modo JNI_ABORT, que libera la memoria sin copiar de nuevo los cambios en el objeto de array subyacente.
    • Si hiciste cambios en el array y ya no necesitas la referencia, usa el código 0 (que actualiza el objeto de array y libera la copia de la memoria).
    • Si realizaste cambios en el arreglo que deseas confirmar y deseas conservar la copia del arreglo, usa JNI_COMMIT (que actualiza el objeto de arreglo subyacente y retiene la copia).
  • Cuando llames a Release...ArrayElements(), muestra el mismo puntero que Get...ArrayElements() mostró originalmente. Por ejemplo, no es seguro aumentar el puntero original (para escanear los elementos del array que se muestran) y, luego, pasar el puntero aumentado a Release...ArrayElements(). El paso de este puntero modificado puede hacer que se libere la memoria incorrecta, lo que puede dañar la memoria.

Manejo de errores

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

Por ejemplo, si se llama a RegisterNatives con un método que no existe (quizás porque una herramienta como ProGuard lo quitó), ART genera NoSuchMethodError correctamente:

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 registra 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() ahora muestran correctamente NoSuchFieldError en lugar de simplemente mostrar un valor nulo. De manera similar, GetMethodID() y GetStaticMethodID() ahora arrojan NoSuchMethodError correctamente. Esto puede generar fallas en CheckJNI debido a las excepciones no controladas o las excepciones que se producen en emisores de Java de código nativo. Por lo tanto, es muy importante probar apps compatibles con el ART a través del modo CheckJNI.

El ART espera que los usuarios de los métodos CallNonvirtual...Method() de JNI (como CallNonvirtualVoidMethod()) usen la clase de declaración del método, no una subclase, 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. ART tiene una pila unificada para mejorar la localidad. Por lo general, el tamaño de la pila del Thread de ART debe ser aproximadamente el mismo que el de Dalvik. Sin embargo, si configuras explícitamente los tamaños de pila, es posible que debas revisar esos valores para las apps que se ejecutan en ART.

  • En Java, revisa las llamadas al constructor Thread que especifican un tamaño de pila explícito. Por ejemplo, deberás aumentar el tamaño si tiene lugar StackOverflowError.
  • En C/C++, revisa el uso 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, se muestra un ejemplo del error que se registra cuando una app intenta llamar a AttachCurrentThread() de JNI cuando el tamaño de pthread es demasiado pequeño:
    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 quieres anular el método de una clase en un paquete diferente, declara el método como public o protected.

Object ahora tiene campos privados. Las apps que se reflejan en los campos de sus jerarquías de clases deben tener cuidado de no intentar ver los campos de Object. Por ejemplo, si realizas una iteración en una jerarquía de clases 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() ahora recibe null si no hay argumentos, en lugar de un arreglo vacío. Este comportamiento se documentó anteriormente, pero no se manejó de forma correcta en Dalvik. Las versiones anteriores de Mockito tienen dificultades con esto. Por lo tanto, usa una versión actualizada de Mockito cuando realices pruebas con ART.

Solución de problemas de compilación de AOT

La compilación de Java por adelantado (AOT) de ART debería funcionar con todos los códigos de Java estándar. La compilación se realiza con la herramienta dex2oat de ART. Si tienes algún problema relacionado con dex2oat durante la instalación, comunícate con nosotros (consulta Cómo informar problemas) para que podamos solucionarlos lo más rápido 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 ofuscación) pueden producir archivos no válidos que Dalvik tolerará y ART rechazará. Trabajamos con proveedores de herramientas para encontrar y solucionar esos problemas. En muchos casos, puedes solucionar estos problemas si obtienes las versiones más recientes de tus herramientas y regeneras 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 monitorenter/monitorexit
    • tamaño de lista del tipo de parámetro de longitud 0
  • Algunas apps tienen dependencias en el formato de archivo .odex instalado en /system/framework, /data/dalvik-cache o en el directorio de salida optimizado de DexClassLoader. Ahora, estos archivos son ELF y no formas extendidas de archivos DEX. Si bien ART intenta seguir las mismas reglas de nomenclatura y bloqueo que Dalvik, las apps no deberán depender del formato de archivo. Este formato está sujeto a cambios sin previo aviso.

    Nota: En Android 8.0 (API nivel 26) y versiones posteriores, el directorio de salida optimizado DexClassLoader dejó de estar disponible. Para obtener más información, consulta la documentación del constructor DexClassLoader().

Cómo informar problemas

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