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 y las aplicaciones 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 aplicación existente para que sea compatible con el ART. La mayoría de las aplicaciones solo deben funcionar cuando se ejecutan con el ART.
Cómo afrontar problemas de recolección de elementos no utilizados (GC)
Con Dalvik, en las aplicaciones, a menudo resulta útil llamar de manera explícita a System.gc()
para solicitar la recolección de elementos no utilizados (GC). Esto debe ser mucho menos necesario con el ART, en particular si invocas 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 superior.
Además, a fin de mejorar la administración de memoria, se encuentra en desarrollo un recolector con compactación de elementos no utilizados en el proyecto de código abierto de Android (AOSP). Por lo tanto, debes evitar usar técnicas que no sean compatibles con la compactación de recolección de elementos no utilizados (como guardar punteros en datos de instancias de objetos). Esto tiene particular importancia para las aplicaciones 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 detectar problemas comunes. Si tu aplicación 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 utilizados
En el caso del ART, se encuentra en desarrollo un recolector con compactación de elementos no utilizados en el proyecto de código abierto de Android (AOSP). Una vez que el recolector con compactación de elementos no utilizados 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 utilizados. 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 las funciones Get...ArrayElements()
y Release...ArrayElements()
. En tiempos de ejecución con recolección sin compactación de elementos no utilizados, 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 de Release...ArrayElements()
suelen ignorarse). Sin embargo, si se usa la recolección con compactación de elementos no utilizados, las funciones Get...ArrayElements()
pueden devolver una copia de la memoria. Si no usas bien la referencia cuando está en uso el recolector con compactación de elementos no utilizados, se pueden producir daños en la memoria u otros problemas. Por ejemplo:
- Si realizas un cambio en los elementos de matriz devueltos, 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 quieres mantener la copia de la matriz, usa
JNI_COMMIT
(que actualiza el objeto de matriz subyacente y retiene la copia).
- Si no realizaste ningún cambio en los elementos de matriz, usa el modo
- Cuando llamas a
Release...ArrayElements()
, se devuelve el mismo puntero queGet...ArrayElements()
devolvió originalmente. Por ejemplo, no es seguro aumentar el puntero original (para recorrer los elementos de matriz devueltos) y, luego, pasar el puntero aumentado aRelease...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 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 (tal vez porque una herramienta como ProGuard lo quitó), ART generará 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()
generan NoSuchFieldError
correctamente en lugar de simplemente mostrar un valor nulo. De modo similar, GetMethodID()
y GetStaticMethodID()
ahora generan NoSuchMethodError
correctamente. Esto puede ocasionar fallas en CheckJNI debido a las excepciones que no se administran o las excepciones que se generan en emisores de Java de código nativo. Esto hace que probar aplicaciones compatibles con el ART a través del modo CheckJNI sea de particular importancia.
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 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. El ART tiene una pila unificada para mejorar la 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 aplicaciones que se ejecutan en el ART.
- En Java, revisa las llamadas al constructor
Thread
que especifican un tamaño de pila explícito. Por ejemplo, debes aumentar el tamaño si tiene lugarStackOverflowError
. - En C/C++, revisa el uso de
pthread_attr_setstack()
ypthread_attr_setstacksize()
para subprocesos que también ejecuten código Java a través de JNI. A continuación, te mostramos un ejemplo del error que se registra cuando una aplicación intenta llamar aAttachCurrentThread()
de JNI 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
.
Ahora Object
tiene campos privados. Las aplicaciones 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 marco de trabajo de serialización, detente cuando
Class.getSuperclass() == java.lang.Object.class
en lugar de continuar hasta que el método devuelva null
.
Si no hay argumentos, el proxy InvocationHandler.invoke()
ahora recibe null
en lugar de una 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 encuentras algún problema relacionado con dex2oat
en el momento de la instalación, notifícalo (consulta Informe de problemas) para que lo 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 no vá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
omoniterexit
- tamaño de lista del tipo de parámetro de longitud 0
- Algunas aplicaciones tienen dependencias en el formato de archivo instalado
.odex
en/system/framework
,/data/dalvik-cache
o el directorio de salida optimizado deDexClassLoader
. Ahora, estos archivos son ELF y no formas extendidas de archivos DEX. Aunque el ART intenta seguir las mismas reglas de nombramiento y bloqueo de Dalvik, las aplicaciones no deberán depender del formato de archivo. El formato está sujeto a cambios sin previo aviso.Nota: En Android 8.0 (nivel de API 26) y versiones posteriores, el directorio de salida optimizado
DexClassLoader
es obsoleto. Para obtener más información, consulta la documentación del constructorDexClassLoader()
.
Informe de problemas
Si encuentras algún problema que no se deba a inconvenientes de la aplicación de JNI, infórmalo 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 enlace a la aplicación en la Google Play Store si se encuentra disponible. De lo contrario, adjunta un APK que reproduzca el problema (si es posible). Ten en cuenta que los problemas (incluso los archivos adjuntos) pueden verse públicamente.