Подтвердить что ты не робот

Android Java - Joda Date работает медленно

Использование Joda 1.6.2 с Android

Следующий код висит около 15 секунд.

DateTime dt = new DateTime();

Первоначально опубликовано это сообщение Android Java - Joda Date медленно работает в Eclipse/Emulator -

Просто попробовал еще раз, и все еще не лучше. У кого-нибудь еще есть эта проблема или знаете, как ее исправить?

4b9b3361

Ответ 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 мс в режиме выпуска. Также он имеет меньший вес, поскольку не включает базу данных часовых поясов.