Android Java - Joda Date работает медленно
Использование Joda 1.6.2 с Android
Следующий код висит около 15 секунд.
DateTime dt = new DateTime();
Первоначально опубликовано это сообщение
Android Java - Joda Date медленно работает в Eclipse/Emulator -
Просто попробовал еще раз, и все еще не лучше. У кого-нибудь еще есть эта проблема или знаете, как ее исправить?
Ответы
Ответ 1
Я также столкнулся с этой проблемой. Подозреваемые Jon Skeet были правильными, проблема в том, что часовые пояса загружаются действительно неэффективно, открывая файл jar и затем просматривая манифест, чтобы попытаться получить эту информацию.
Однако просто вызов DateTimeZone.setProvider([custom provider instance ...])
недостаточен, потому что по причинам, которые для меня не имеют смысла, DateTimeZone имеет статический инициализатор, где он вызывает getDefaultProvider()
.
Чтобы быть полностью безопасным, вы можете переопределить это значение по умолчанию, установив это системное свойство, прежде чем вы когда-нибудь вызовете что-нибудь в joda.
В вашей деятельности, например, добавьте следующее:
@Override
public void onCreate(Bundle savedInstanceState) {
System.setProperty("org.joda.time.DateTimeZone.Provider",
"com.your.package.FastDateTimeZoneProvider");
}
Тогда вам нужно всего лишь определить FastDateTimeZoneProvider
. Я написал следующее:
package com.your.package;
public class FastDateTimeZoneProvider implements Provider {
public static final Set<String> AVAILABLE_IDS = new HashSet<String>();
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;
}
}
Я тестировал это и, похоже, работает на Android SDK 2.1+ с версией joda версии 1.6.2. Конечно, его можно оптимизировать, но при профилировании моего приложения (mogwee) это уменьшило время инициализации DateTimeZone с ~ 500 мс до ~ 18 мс.
Если вы используете proguard для создания своего приложения, вам придется добавить эту строку в proguard.cfg, потому что Joda ожидает, что имя класса будет точно таким, как вы указали:
-keep class com.your.package.FastDateTimeZoneProvider
Ответ 2
Я сильно подозреваю, потому что ему приходится создавать хронологию ISO для часового пояса по умолчанию, что, вероятно, связано с чтением всей информации о часовом поясе.
Вы можете проверить это, вызвав ISOChronology.getInstance()
в первый раз, а затем время последующего вызова new DateTime()
. Я подозреваю, что это будет быстро.
Знаете ли вы, какие часовые пояса будут иметь отношение к вашему приложению? Вы можете обнаружить, что вы можете сделать все это быстрее, восстановив Joda Time с очень сокращенной базой данных часовых поясов. В качестве альтернативы вызовите DateTimeZone.setProvider()
с вашей собственной реализацией Provider
, которая не выполняет столько работы.
Стоит проверить, действительно ли это проблема, конечно:) Вы также можете попробовать явно пропустить в часовой пояс UTC, что не потребует чтения в базе данных часовых поясов... хотя вы никогда не знаете, когда вы случайно вызовете вызов, который требует часовой пояс по умолчанию, и в этот момент вы понесете такую же стоимость.
Ответ 3
Мне нужно только UTC в моем приложении. Итак, после совета unchek, я использовал
System.setProperty("org.joda.time.DateTimeZone.Provider", "org.joda.time.tz.UTCProvider");
org.joda.time.tz.UTCProvider
фактически используется JodaTime как вторичная резервная копия, поэтому я подумал, почему не использовать его для первичного использования? Все идет нормально. Он быстро загружается.
Ответ 4
Верхний ответ, предоставленный пахарьем, не является надежным, если у вас должны быть точные вычисления часовых поясов для ваших дат. Вот пример проблемы, которая может произойти:
Предположим, что ваш объект DateTime
установлен на 4:00 утра, через час после перехода на летнее время. Когда Джода проверяет поставщика FastDateTimeZoneProvider
до 3:00 утра (то есть до перехода на летнее время), он получит объект DateTimeZone
с неправильным смещением, потому что проверка tz.inDaylightTime(new Date())
вернет false.
Моим решением было принять недавно опубликованную joda-time-android library. Он использует ядро Joda, но обязательно загружает часовой пояс только по мере необходимости из исходной папки. Настройка осуществляется с помощью gradle. В своем проекте расширьте класс Application
и добавьте следующее на onCreate()
:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
JodaTimeAndroid.init(this);
}
}
Автор написал сообщение в блоге об этом в прошлом году.
Ответ 5
Я могу подтвердить эту проблему с версией 1, 1.5 и 1.62 от joda. Date4J работает для меня как альтернатива.
http://www.date4j.net/
Ответ 6
Я только что выполнил тест, который @ "Name is carl" размещен на нескольких устройствах. Я должен отметить, что тест не является полностью допустимым и результаты вводят в заблуждение (поскольку он отражает только один экземпляр DateTime).
-
Из его теста, при сравнении DateTime с Date, DateTime вынуждается анализировать строки String, где Date ничего не анализирует.
-
В то время как первоначальное создание DateTime было точным, оно ТОЛЬКО занимает много времени на самом FIRST-создании... каждый экземпляр после этого был 0 мс (или очень близко к 0 мс)
Чтобы убедиться в этом, я использовал следующий код и создал 1000 новых экземпляров DateTime на устройстве 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));
Мои результаты показали:
datetime : 264
datetime : 0
datetime : 0
datetime : 0
datetime : 0
datetime : 0
datetime : 0
...
datetime : 0
datetime : 0
datetime : 1
datetime : 0
...
datetime : 0
datetime : 0
datetime : 0
Итак, результат был в том, что первый экземпляр составлял 264 мс, и более 95% из них были равны 0 мс (иногда я имел 1 мс, но никогда не имел значения больше 1 мс).
Надеюсь, что это даст более четкое представление о стоимости использования Joda.
ПРИМЕЧАНИЕ. Я использовал версию joda-версии 2.1
Ответ 7
Используя dlew/joda-time-android gradle зависимость, она принимает только 22.82 мс (миллисекунды). Поэтому я рекомендую вам использовать его вместо того, чтобы переопределять что-либо.
Ответ 8
Я нашел решение для меня. Я загружаю UTC и часовой пояс по умолчанию. Так что он загружается очень быстро. И я думаю, что в этом случае мне нужно поймать трансляцию TIME ZONE CHANGE и перезагрузить часовой пояс по умолчанию.
public class FastDateTimeZoneProvider implements Provider {
public static final Set<String> AVAILABLE_IDS = new HashSet<String>();
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;
}
}
Ответ 9
Эта краткая заметка, чтобы ответить на вопрос date4j от @Steven
Я провел быстрый и грязный тест, сравнивающий java.util.Date
, jodatime
и date4j
с самым слабым устройством Android (HTC Dream/Sapphire 2.3.5).
Подробности: нормальная сборка (без програды), реализация FastDateTimeZoneProvider
для jodatime.
Здесь код:
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));
Вот результаты:
debug | normal
joda : 3s (3577ms) | 0s (284ms)
date : 0s (0) | 0s (0s)
date4j : 0s (55ms) | 0s (2ms)
Последнее, размеры банки:
jodatime 2.1 : 558 kb
date4j : 35 kb
Я думаю, что дам date4j попробовать.
Ответ 10
Вы также можете проверить бэкпорт Jake Wharton JSR-310 пакетов java.time. *.
Эта библиотека помещает информацию о часовом поясе в качестве стандартного ресурса Android и предоставляет специальный загрузчик для эффективного анализа. [Это] предлагает стандартные API в Java 8 в виде гораздо меньшего пакета не только по размеру двоичного файла и количеству методов, но и по размеру API.
Таким образом, это решение предоставляет библиотеку меньшего двоичного размера с меньшим количеством счетчиков методов в сочетании с эффективным загрузчиком данных часового пояса.
Ответ 11
- Как уже упоминалось, вы можете использовать библиотеку joda-time-android.
- Не используйте
FastDateTimeZoneProvider
предложенный @ElijahSh и @plowman. Потому что это обрабатывать смещение DST как стандартное смещение для выбранного часового пояса. Так как это даст "правильные" результаты на сегодня и на оставшуюся половину года, прежде чем произойдет следующий переход на летнее время. Но это определенно даст неверный результат за день до перехода на летнее время и на следующий день после следующего перехода на летнее время. -
Правильный способ использования системных часовых поясов с JodaTime:
открытый класс AndroidDateTimeZoneProvider реализует org.joda.time.tz.Provider {
@Override
public Set<String> getAvailableIDs() {
return new HashSet<>(Arrays.asList(TimeZone.getAvailableIDs()));
}
@Override
public DateTimeZone getZone(String id) {
return id == null
? null
: id.equals("UTC")
? DateTimeZone.UTC
: Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? new AndroidNewDateTimeZone(id)
: new AndroidOldDateTimeZone(id);
}
}
Где AndroidOldDateTimeZone:
public class AndroidOldDateTimeZone extends DateTimeZone {
private final TimeZone mTz;
private final Calendar mCalendar;
private long[] mTransition;
public AndroidOldDateTimeZone(final String id) {
super(id);
mTz = TimeZone.getTimeZone(id);
mCalendar = GregorianCalendar.getInstance(mTz);
mTransition = new long[0];
try {
final Class tzClass = mTz.getClass();
final Field field = tzClass.getDeclaredField("mTransitions");
field.setAccessible(true);
final Object transitions = field.get(mTz);
if (transitions instanceof long[]) {
mTransition = (long[]) transitions;
} else if (transitions instanceof int[]) {
final int[] intArray = (int[]) transitions;
final int size = intArray.length;
mTransition = new long[size];
for (int i = 0; i < size; i++) {
mTransition[i] = intArray[i];
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public TimeZone getTz() {
return mTz;
}
@Override
public long previousTransition(final long instant) {
if (mTransition.length == 0) {
return instant;
}
final int index = findTransitionIndex(instant, false);
if (index <= 0) {
return instant;
}
return mTransition[index - 1] * 1000;
}
@Override
public long nextTransition(final long instant) {
if (mTransition.length == 0) {
return instant;
}
final int index = findTransitionIndex(instant, true);
if (index > mTransition.length - 2) {
return instant;
}
return mTransition[index + 1] * 1000;
}
@Override
public boolean isFixed() {
return mTransition.length > 0 &&
mCalendar.getMinimum(Calendar.DST_OFFSET) == mCalendar.getMaximum(Calendar.DST_OFFSET) &&
mCalendar.getMinimum(Calendar.ZONE_OFFSET) == mCalendar.getMaximum(Calendar.ZONE_OFFSET);
}
@Override
public boolean isStandardOffset(final long instant) {
mCalendar.setTimeInMillis(instant);
return mCalendar.get(Calendar.DST_OFFSET) == 0;
}
@Override
public int getStandardOffset(final long instant) {
mCalendar.setTimeInMillis(instant);
return mCalendar.get(Calendar.ZONE_OFFSET);
}
@Override
public int getOffset(final long instant) {
return mTz.getOffset(instant);
}
@Override
public String getShortName(final long instant, final Locale locale) {
return getName(instant, locale, true);
}
@Override
public String getName(final long instant, final Locale locale) {
return getName(instant, locale, false);
}
private String getName(final long instant, final Locale locale, final boolean isShort) {
return mTz.getDisplayName(!isStandardOffset(instant),
isShort ? TimeZone.SHORT : TimeZone.LONG,
locale == null ? Locale.getDefault() : locale);
}
@Override
public String getNameKey(final long instant) {
return null;
}
@Override
public TimeZone toTimeZone() {
return (TimeZone) mTz.clone();
}
@Override
public String toString() {
return mTz.getClass().getSimpleName();
}
@Override
public boolean equals(final Object o) {
return (o instanceof AndroidOldDateTimeZone) && mTz == ((AndroidOldDateTimeZone) o).getTz();
}
@Override
public int hashCode() {
return 31 * super.hashCode() + mTz.hashCode();
}
private long roundDownMillisToSeconds(final long millis) {
return millis < 0 ? (millis - 999) / 1000 : millis / 1000;
}
private int findTransitionIndex(final long millis, final boolean isNext) {
final long seconds = roundDownMillisToSeconds(millis);
int index = isNext ? mTransition.length : -1;
for (int i = 0; i < mTransition.length; i++) {
if (mTransition[i] == seconds) {
index = i;
}
}
return index;
}
}
AndroidNewDateTimeZone.java такой же, как "старый", но вместо этого основан на android.icu.util.TimeZone
.
- Специально для этого я создал форк Joda Time. Он загружается всего ~ 29 мс в режиме отладки и ~ 2 мс в режиме выпуска. Также он имеет меньший вес, поскольку не включает базу данных часовых поясов.