Android Java – Joda Date es lento

Usando Joda 1.6.2 con Android

El siguiente código se cuelga durante aproximadamente 15 segundos.

DateTime dt = new DateTime(); 

Originalmente publicado este post Android Java – Joda Date es lento en Eclipse / Emulator –

Lo intenté de nuevo y todavía no es mejor. ¿Alguien más tiene este problema o sabe cómo solucionarlo?

También encontré este problema. Las sospechas de Jon Skeet eran correctas, el problema es que las zonas horarias se están cargando de manera muy ineficiente, abriendo un archivo jar y luego leyendo el manifiesto para tratar de obtener esta información.

Sin embargo, simplemente llamar a DateTimeZone.setProvider([custom provider instance ...]) no es suficiente porque, por razones que no tienen sentido para mí, DateTimeZone tiene un inicializador estático donde llama a getDefaultProvider() .

Para estar completamente seguro, puede anular este valor predeterminado configurando esta propiedad del sistema antes de llamar cualquier cosa en el joda.

En su actividad, por ejemplo, agregue esto:

 @Override public void onCreate(Bundle savedInstanceState) { System.setProperty("org.joda.time.DateTimeZone.Provider", "com.your.package.FastDateTimeZoneProvider"); } 

Entonces, todo lo que tiene que hacer es definir FastDateTimeZoneProvider . Escribí lo siguiente:

 package com.your.package; public class FastDateTimeZoneProvider implements Provider { public static final Set AVAILABLE_IDS = new HashSet(); static { AVAILABLE_IDS.addAll(Arrays.asList(TimeZone.getAvailableIDs())); } public DateTimeZone getZone(String id) { if (id == null) { return DateTimeZone.UTC; } TimeZone tz = TimeZone.getTimeZone(id); if (tz == null) { return DateTimeZone.UTC; } int rawOffset = tz.getRawOffset(); //sub-optimal. could be improved to only create a new Date every few minutes if (tz.inDaylightTime(new Date())) { rawOffset += tz.getDSTSavings(); } return DateTimeZone.forOffsetMillis(rawOffset); } public Set getAvailableIDs() { return AVAILABLE_IDS; } } 

Probé esto y parece funcionar en Android SDK 2.1+ con joda versión 1.6.2. Por supuesto, se puede optimizar aún más, pero al perfilar mi aplicación ( mogwee ), esto disminuyó el tiempo de inicialización de DateTimeZone de ~ 500 ms a ~ 18 ms.

Si está usando proguard para comstackr su aplicación, tendrá que agregar esta línea a proguard.cfg porque Joda espera que el nombre de la clase sea exactamente como usted especifique:

 -keep class com.your.package.FastDateTimeZoneProvider 

Sospecho que es porque tiene que crear la cronología ISO para la zona horaria predeterminada, que probablemente implica leer toda la información de la zona horaria.

Puede verificar esto llamando a ISOChronology.getInstance() primera vez, y luego ISOChronology.getInstance() una llamada posterior al new DateTime() . Sospecho que será rápido.

¿Sabes qué zonas horarias van a ser relevantes en tu aplicación? Puede encontrar que puede hacer todo mucho más rápido reconstruyendo Joda Time con una base de datos de zona horaria muy reducida. Alternativamente, llame a DateTimeZone.setProvider() con su propia implementación de Provider que no hace tanto trabajo.

Vale la pena verificar si ese es realmente el problema primero, por supuesto 🙂 También puede intentar pasar explícitamente en la zona horaria UTC, que no requerirá lectura en la base de datos de la zona horaria … aunque nunca se sabe cuándo activar accidentalmente una llamada que requiere la zona horaria predeterminada, en cuyo caso incurrirá en el mismo costo.

Solo necesito UTC en mi aplicación. Entonces, siguiendo el consejo de unchek, utilicé

 System.setProperty("org.joda.time.DateTimeZone.Provider", "org.joda.time.tz.UTCProvider"); 

org.joda.time.tz.UTCProvider es utilizado por JodaTime como copia de seguridad secundaria, así que pensé ¿por qué no usarlo para uso primario? Hasta aquí todo bien. Se carga rápido.

La mejor respuesta provista por Plowman no es confiable si debe tener cálculos precisos de la zona horaria para sus fechas. Aquí hay un ejemplo del problema que puede suceder:

Supongamos que su objeto DateTime está configurado para las 4:00 a.m., una hora después de que el horario de verano haya comenzado ese día. Cuando Joda compruebe el proveedor FastDateTimeZoneProvider antes de las 3:00 a.m. (es decir, antes del horario de verano) obtendrá un objeto DateTimeZone con el desplazamiento incorrecto porque la tz.inDaylightTime(new Date()) devolverá false.

Mi solución fue adoptar la biblioteca joda-time-android recientemente publicada. Utiliza el núcleo de Joda, pero se asegura de cargar una zona horaria solo según sea necesario desde la carpeta sin formato. La configuración es fácil con Gradle. En su proyecto, amplíe la clase de Application y agregue lo siguiente en su onCreate() :

 public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); JodaTimeAndroid.init(this); } } 

El autor escribió una publicación en el blog el año pasado.

Puedo confirmar este problema con la versión 1, 1.5 y 1.62 de joda. Date4J funciona bien para mí como alternativa.

http://www.date4j.net/

Acabo de realizar la prueba que se publicó @ “Name is carl” en varios dispositivos. Debo señalar que la prueba no es completamente válida y los resultados son engañosos (en el sentido de que solo refleja una sola instancia de DateTime).

  1. De su prueba, al comparar DateTime con Date, DateTime se ve forzado a analizar los String ts, donde Date no analiza nada.

  2. Si bien la creación inicial de DateTime fue precisa, SOLO lleva mucho tiempo en la creación FIRST … cada instancia después de eso fue 0ms (o muy cerca de 0ms)

Para verificar esto, utilicé el siguiente código y creé 1000 nuevas instancias de DateTime en un dispositivo OLD Android 2.3

  int iterations = 1000; long totalTime = 0; // Test Joda Date for (int i = 0; i < iterations; i++) { long d1 = System.currentTimeMillis(); DateTime d = new DateTime(); long d2 = System.currentTimeMillis(); long duration = (d2 - d1); totalTime += duration; log.i(TAG, "datetime : " + duration); } log.i(TAG, "Average datetime : " + ((double) totalTime/ (double) iterations)); 

Mis resultados mostraron:

 fecha y hora: 264
 fecha y hora: 0
 fecha y hora: 0
 fecha y hora: 0
 fecha y hora: 0
 fecha y hora: 0
 fecha y hora: 0
 ...
 fecha y hora: 0
 fecha y hora: 0
 fecha y hora: 1
 fecha y hora: 0
 ...
 fecha y hora: 0
 fecha y hora: 0
 fecha y hora: 0

Entonces, el resultado fue que la primera instancia fue 264ms y más del 95% de los siguientes fueron 0ms (ocasionalmente tuve 1ms, pero nunca tuve un valor mayor a 1ms).

Espero que esto dé una idea más clara del costo de usar Joda.

NOTA: Estaba usando la versión 2.1 de joda-time

Encontré una solución para mí. Cargué UTC y la zona horaria predeterminada. Entonces, es muy rápido. Y creo que en este caso necesito capturar la transmisión TIME ZONE CHANGE y volver a cargar la zona horaria predeterminada.

 public class FastDateTimeZoneProvider implements Provider { public static final Set AVAILABLE_IDS = new HashSet(); static { AVAILABLE_IDS.add("UTC"); AVAILABLE_IDS.add(TimeZone.getDefault().getID()); } public DateTimeZone getZone(String id) { int rawOffset = 0; if (id == null) { return DateTimeZone.getDefault(); } TimeZone tz = TimeZone.getTimeZone(id); if (tz == null) { return DateTimeZone.getDefault(); } rawOffset = tz.getRawOffset(); //sub-optimal. could be improved to only create a new Date every few minutes if (tz.inDaylightTime(new Date())) { rawOffset += tz.getDSTSavings(); } return DateTimeZone.forOffsetMillis(rawOffset); } public Set getAvailableIDs() { return AVAILABLE_IDS; } } 

Usando dlew / joda-time-android gradle depende solo 22.82 ms (milisegundos). Así que te recomiendo que lo uses en lugar de anular nada.

Esta nota rápida para completar la respuesta sobre date4j de @Steven

jodatime un benchmark rápido y sucio comparando java.util.Date , jodatime y date4j en el dispositivo android más débil que tengo (HTC Dream / Sapphire 2.3.5).

Detalles: comstackción normal (no proguard), implementando FastDateTimeZoneProvider para jodatime.

Aquí está el código:

 String ts = "2010-01-19T23:59:59.123456789"; long d1 = System.currentTimeMillis(); DateTime d = new DateTime(ts); long d2 = System.currentTimeMillis(); System.err.println("datetime : " + dateUtils.durationtoString(d2 - d1)); d1 = System.currentTimeMillis(); Date dd = new Date(); d2 = System.currentTimeMillis(); System.err.println("date : " + dateUtils.durationtoString(d2 - d1)); d1 = System.currentTimeMillis(); hirondelle.date4j.DateTime ddd = new hirondelle.date4j.DateTime(ts); d2 = System.currentTimeMillis(); System.err.println("date4j : " + dateUtils.durationtoString(d2 - d1)); 

Aquí están los resultados :

  debug | normal joda : 3s (3577ms) | 0s (284ms) date : 0s (0) | 0s (0s) date4j : 0s (55ms) | 0s (2ms) 

Una última cosa, los tamaños de jarras:

 jodatime 2.1 : 558 kb date4j : 35 kb 

Creo que voy a probar date4j.

También puede consultar el respaldo JSR-310 de Jake Wharton de los paquetes java.time. *.

Esta biblioteca coloca la información de la zona horaria como un activo estándar de Android y proporciona un cargador personalizado para analizarlo de manera eficiente. [It] ofrece las API estándar en Java 8 como un paquete mucho más pequeño no solo en tamaño binario y método, sino también en tamaño de API.

Por lo tanto, esta solución proporciona una biblioteca de tamaño binario más pequeña con una huella de recuento de métodos más pequeña, combinada con un cargador eficiente para datos de zona horaria.