Development Tip

Android Java-Joda Date가 느립니다.

yourdevel 2021. 1. 6. 20:30
반응형

Android Java-Joda Date가 느립니다.


Android에서 Joda 1.6.2 사용

다음 코드는 약 15 초 동안 멈 춥니 다.

DateTime dt = new DateTime();

원래이 게시물을 게시했습니다 Android Java-Joda Date is slow in Eclipse / Emulator-

다시 시도했지만 여전히 더 좋지는 않습니다. 다른 사람이이 문제가 있거나 해결 방법을 알고 있습니까?


나는 또한이 문제에 부딪쳤다. 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;
    }
}

나는 이것을 테스트했으며 joda 버전 1.6.2와 함께 Android SDK 2.1 이상에서 작동하는 것으로 보입니다. 물론 더 최적화 할 수 있지만 내 앱 ( mogwee ) 을 프로파일 링하는 동안 DateTimeZone 초기화 시간이 ~ 500ms에서 ~ 18ms로 감소했습니다.

proguard를 사용하여 앱을 빌드하는 경우 Joda는 클래스 이름이 지정한 것과 정확히 일치 할 것으로 예상하므로 proguard.cfg에이 줄을 추가해야합니다.

-keep class com.your.package.FastDateTimeZoneProvider

기본 시간대에 대한 ISO 연대기를 작성해야하므로 모든 시간대 정보를 읽어야하기 때문이라고 강력히 의심 합니다.

ISOChronology.getInstance()처음 으로 호출 한 다음에 대한 후속 호출 시간을 측정하여이를 확인할 수 new DateTime()있습니다. 나는 그것이 빠를 것이라고 생각 한다.

귀하의 애플리케이션에 어떤 시간대가 관련 될지 알고 있습니까? 당신은 할 수 있습니다 당신이 아주 많이 감소 시간대 데이터베이스와 Joda 시간을 재 구축하여 모든 것을 더 빨리 할 수 있습니다 찾을 수 있습니다. 또는 많은 작업을 수행하지 않는 DateTimeZone.setProvider()자체 구현으로 호출 하십시오 Provider.

물론 그것이 실제로 문제인지 먼저 확인하는 것이 좋습니다. :) UTC 시간대를 명시 적으로 전달해 볼 수도 있습니다. 시간대 데이터베이스에서 읽을 필요가 없습니다. 실수로 기본 시간대 필요한 통화를 트리거 하면 동일한 비용이 발생합니다.


내 응용 프로그램에는 UTC 만 필요합니다. 그래서 unchek의 조언에 따라

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

org.joda.time.tz.UTCProvider실제로 JodaTime에서 보조 백업으로 사용하므로 기본 용도로 사용하지 않는 이유는 무엇입니까? 여태까지는 그런대로 잘됐다. 빠르게로드됩니다.


날짜에 대한 정확한 시간대 계산이 필요한 경우 plowman이 제공하는 최고 답변은 신뢰할 수 없습니다. 다음은 발생할 수있는 문제의 예입니다.

DateTime그날 일광 절약이 시작된 지 한 시간 후인 오전 4시에 개체가 설정되어 있다고 가정합니다 . Joda가 FastDateTimeZoneProvider오전 3시 이전에 (즉, 일광 절약 시간 전에) 공급자를 확인하면 검사가 false를 반환 DateTimeZone하므로 오프셋이 잘못된 개체를 가져 tz.inDaylightTime(new Date())옵니다.

내 해결책은 최근에 게시 된 joda-time-android 라이브러리 를 채택하는 것이 었습니다 . Joda의 핵심을 사용하지만 원시 폴더에서 필요한 경우에만 시간대를로드합니다. Gradle을 사용하면 설정이 쉽습니다. 프로젝트에서 Application클래스를 확장 하고 다음을 추가하십시오 onCreate().

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

저자는 작년에 그것에 대한 블로그 게시물을 썼습니다 .


이 문제는 joda의 버전 1, 1.5 및 1.62에서 확인할 수 있습니다. Date4J는 대안으로 나를 위해 잘 작동합니다.

http://www.date4j.net/


방금 여러 장치에서 @ "Name is carl"이 게시 한 테스트를 수행했습니다. 테스트가 완전히 유효하지 않으며 결과가 오해의 소지가 있다는 점에 유의해야합니다 (DateTime의 단일 인스턴스 만 반영한다는 점에서).

  1. 그의 테스트에서 DateTime을 Date와 비교할 때 DateTime은 String ts를 구문 분석해야하는데, 여기서 Date는 아무것도 구문 분석하지 않습니다.

  2. DateTime의 초기 생성은 정확했지만 첫 번째 생성에만 그 정도의 시간이 걸립니다. 그 이후의 모든 인스턴스는 0ms (또는 거의 0ms)였습니다.

이를 확인하기 위해 다음 코드를 사용하고 OLD Android 2.3 기기에서 DateTime의 새 인스턴스 1000 개를 만들었습니다.

    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));

내 결과는 다음과 같습니다.

날짜 시간 : 264
날짜 시간 : 0
날짜 시간 : 0
날짜 시간 : 0
날짜 시간 : 0
날짜 시간 : 0
날짜 시간 : 0
...
날짜 시간 : 0
날짜 시간 : 0
날짜 시간 : 1
날짜 시간 : 0
...
날짜 시간 : 0
날짜 시간 : 0
날짜 시간 : 0

따라서 결과는 첫 번째 인스턴스가 264ms이고 다음 중 95 % 이상이 0ms였습니다 (때때로 1ms가 있었지만 1ms보다 큰 값은 없었습니다).

이것이 Joda 사용 비용에 대한 명확한 그림을 제공하기를 바랍니다.

참고 : joda-time 버전 2.1을 사용하고있었습니다.


dlew / joda-time-android gradle 종속성을 사용하면 22.82ms (밀리 초) 만 걸립니다. 그래서 나는 그것을 재정의하는 대신 사용하는 것이 좋습니다.


나는 나를 위해 해결책을 찾았다. 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;
    }
}

@Steven의 date4j에 대한 답변을 완성하는이 빠른 메모

나는 비교하는 신속하고 더러운 벤치 마크를 실행 java.util.Date, jodatime그리고 date4j가장 약한 안드로이드 장치에 나는 (HTC 드림 / 사파이어 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를 시도해 볼 것이라고 생각합니다.


또한 체크 아웃 할 수 제이크 와튼의 JSR-310 백 포트 java.time의를. * 패키지를.

이 라이브러리는 시간대 정보를 표준 Android 애셋으로 배치하고이를 효율적으로 파싱하기위한 커스텀 로더를 제공합니다. [It]은 바이너리 크기와 메서드 수뿐만 아니라 API 크기에서도 훨씬 작은 패키지로 Java 8의 표준 API를 제공합니다.

따라서이 솔루션은 Timezone 데이터를위한 효율적인 로더와 결합 된 더 작은 메서드 수 풋 프린트로 더 작은 바이너리 크기 라이브러리를 제공합니다.


  • 이미 언급했듯이 joda-time-android 라이브러리를 사용할 수 있습니다 .
  • FastDateTimeZoneProvider@ElijahSh 및 @plowman이 제안한 것을 사용하지 마십시오 . DST 오프셋을 선택한 시간대에 대한 표준 오프셋으로 취급하기 때문입니다. 다음 DST 전환이 발생하기 전 오늘과 나머지 반년 동안 "올바른"결과를 제공합니다. 그러나 DST 전환 전날과 다음 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는 "Old"와 동일하지만 android.icu.util.TimeZone대신에 기반 합니다.

  • I have created a fork of Joda Time especially for this. It loads for only ~29 ms in debug mode and ~2ms in release mode. Also it has less weight as it doesn't include timezone database.

ReferenceURL : https://stackoverflow.com/questions/5059663/android-java-joda-date-is-slow

반응형