web-dev-qa-db-ja.com

Android Java-Joda Dateが遅い

AndroidでJoda 1.6.2を使用する

次のコードは約15秒間ハングします。

DateTime dt = new DateTime();

最初にこの投稿を投稿しました Android Java-Joda DateがEclipse/Emulatorで遅い-

もう一度試してみましたが、それでもまだ良くはありません。他の誰かがこの問題を抱えているか、それを修正する方法を知っていますか?

56
Mark Worsnop

私もこの問題に遭遇しました。 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+ with jodaバージョン1.6.2で動作するようです。もちろん、さらに最適化できますが、アプリのプロファイリング中に( mogwee )、これにより、DateTimeZoneの初期化時間が500ミリ秒から18ミリ秒に短縮されました。

アプリのビルドにproguardを使用している場合、Jodaはクラス名が指定したとおりであることを想定しているため、この行をproguard.cfgに追加する必要があります。

-keep class com.your.package.FastDateTimeZoneProvider
63
plowman

私は強くsuspectこれは、デフォルトのタイムゾーンのISOクロノロジーを構築する必要があるためです。

これを確認するには、最初にISOChronology.getInstance()を呼び出してから、次にnew DateTime()を呼び出して時間を計測します。私容疑者速くなるでしょう。

アプリケーションに関連するタイムゾーンを知っていますか?あなたmay非常に短縮されたタイムゾーンデータベースを使用してJoda Timeを再構築することで、全体をより迅速に行えることがわかります。または、独自のProviderを実装してDateTimeZone.setProvider()を呼び出すと、あまり効果がありません。

もちろん、それが実際に最初に問題であるかどうかを確認することは価値があります。もちろん、:または UTCタイムゾーンを明示的に渡してみることもできます。タイムゾーンデータベースを読み取る必要はありません... doesがデフォルトのタイムゾーンを必要とする呼び出しを誤ってトリガーするタイミングはわかりません。その時点で同じコストが発生します。

23
Jon Skeet

アプリケーションにはUTCのみが必要です。だから、unchekのアドバイスに従って、私は

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

org.joda.time.tz.UTCProviderは実際にはJodaTimeが二次バックアップとして使用しているので、一次使用に使用しないのはなぜだと思いましたか?ここまでは順調ですね。それは速くロードされます。

10
Tyler Collier

日付の正確なタイムゾーン計算が必要な場合、plowmanが提供する一番上の答えは信頼できません。発生する可能性のある問題の例を次に示します。

DateTimeオブジェクトが、夏時間が開始されてから1時間後の4:00 amに設定されているとします。 JodaがFastDateTimeZoneProviderプロバイダーを午前3時前(つまり夏時間の前)にチェックすると、tz.inDaylightTime(new Date())チェックがfalseを返すため、間違ったオフセットでDateTimeZoneオブジェクトを取得します。 。

私の解決策は、最近公開された joda-time-Androidライブラリ を採用することでした。 Jodaのコアを使用しますが、rawフォルダーから必要な場合にのみタイムゾーンをロードします。セットアップはgradleで簡単です。プロジェクトで、Applicationクラスを拡張し、そのonCreate()に以下を追加します。

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

著者は昨年それについて ブログ投稿 と書いた。

8
Ricardo

この問題はjodaのバージョン1、1.5、1.62で確認できます。代わりに、Date4Jがうまく機能しています。

http://www.date4j.net/

3
Steven Elliott

@ "Name is carl"が投稿したテストをいくつかのデバイスで実行しました。テストは完全に有効ではなく、結果は誤解を招く(DateTimeの単一のインスタンスのみを反映するため)ことに注意する必要があります。

  1. 彼のテストから、DateTimeをDateと比較すると、DateTimeは文字列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));

私の結果は示しました:

 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-timeバージョン2.1を使用していました

3
Jeff Campbell

dlew/joda-time-Android gradle依存関係を使用すると、22.82 ms(ミリ秒)しかかかりません。したがって、何もオーバーライドするのではなく、それを使用することをお勧めします。

3
Vito Valov

解決策を見つけました。 UTCとデフォルトのタイムゾーンをロードします。したがって、ロードは非常に高速です。この場合、ブロードキャストタイムゾーンの変更をキャッチし、デフォルトのタイムゾーンをリロードする必要があると思います。

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;
    }
}
2
ElijahSh

Java.time。*パッケージの Jake WhartonのJSR-310バックポート をチェックアウトすることもできます。

このライブラリは、タイムゾーン情報を標準のAndroidアセットとして配置し、効率的に解析するためのカスタムローダーを提供します。[It]は、Java 8 asバイナリサイズとメソッド数だけでなく、APIサイズも非常に小さいパッケージ。

したがって、このソリューションでは、タイムゾーンデータ用の効率的なローダーと組み合わせて、メソッド数の少ないフットプリントを備えたバイナリサイズの小さいライブラリを提供します。

1
Chris Knight

@Stevenからのdate4jに関する回答を完了するためのこの簡単なメモ

私はJava.util.Datejodatimeおよびdate4j最も弱いAndroid私が持っているデバイス(HTC Dream/Sapphire 2.3.5)で)。

詳細:通常のビルド(プロガードなし)、jodatimeのFastDateTimeZoneProviderの実装。

これがコードです:

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) 

最後に、jarのサイズ:

jodatime 2.1 : 558 kb 
date4j       : 35 kb

私はdate4jを試してみると思います。

1
Name is carl
  • すでに述べたように、 joda-time-Android library を使用できます。
  • @ElijahShおよび@plowmanによって提案されたFastDateTimeZoneProviderを使用しないでください。これは、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は「古い」ものと同じですが、代わりにAndroid.icu.util.TimeZoneに基づいています。

  • 特にこのために Joda Timeのフォーク を作成しました。デバッグモードでは約29ミリ秒、リリースモードでは約2ミリ秒でロードされます。また、タイムゾーンデータベースが含まれていないため、重みが小さくなります。
0
Oleksandr Albul