【问题标题】:LocalDateTime.now() crashes on Sony BraviaLocalDateTime.now() 在 Sony Bravia 上崩溃
【发布时间】:2016-05-27 11:49:38
【问题描述】:

我在 AndroidTV 的应用中使用ThreeTen Android Backport

虽然在 Nexus Player 和所有经过测试的 Amazon Fire TV 设备上一切正常,但在 Sony Bravia 4K 2015 (KD-55x8509C) 上调用 LocalDateTime.now() 时始终会导致应用程序崩溃。

Caused by: org.threeten.bp.DateTimeException: Invalid ID for ZoneOffset, invalid format: -01:00GMT-02:00,J086/02:00,J176/02:00
at org.threeten.bp.ZoneOffset.of(ZoneOffset.java:221)
at org.threeten.bp.ZoneId.of(ZoneId.java:344)
at org.threeten.bp.ZoneId.of(ZoneId.java:285)
at org.threeten.bp.ZoneId.systemDefault(ZoneId.java:244)
at org.threeten.bp.Clock.systemDefaultZone(Clock.java:137)
at org.threeten.bp.LocalDateTime.now(LocalDateTime.java:152)

发生了什么,我能做些什么?

【问题讨论】:

  • 真的 - 否决票?需要解释一下吗?
  • 我不是反对者,但很明显,zone id 被破坏了,threeten-abp 只能处理/理解有效的 zone id,Java-8 也是如此。也许反对者认为这太清楚了,“不可避免”。无论如何,为了稳定我的库Time4A,我已经考虑到了这种奇怪的区域ID 的可能性。这个库将在这种特殊情况下包装 Android 平台的系统时区(围绕 java.util.TimeZone 进行包装),并且 - 这样 - 当此类区域 ID 映射到内部 tzdb 区域存储库不再可能时,防止中止。跨度>
  • @MenoHochschild:感谢您的评论!是的,也许我的问题听起来比我的意思更天真——当然,我阅读并理解了堆栈跟踪。但我认为问题已明确定义,并且要求合理的解决方法并不超出 SO 的范围。无论如何,感谢指向您的时间库的指针,我不知道。由于我在 Android 上存在时间库的特定问题(请参阅此处:github.com/JakeWharton/ThreeTenABP)我有点担心切换......但也许我可以使用你的示例并将我的调用包装到 now() 并进行一些错误更正.

标签: android sony android-tv threetenbp


【解决方案1】:

您的异常原因非常清楚,并记录在您的异常消息中:

损坏的区域 ID(“-01:00GMT-02:00,J086/02:00,J176/02:00”)。

同样清楚的是,Threeten-ABP(以及 Java-8)根本不允许构造无效的区域 id,请参阅以下示例,该示例尝试了语法上有效的格式:

String unsupported = "System/Unknown";
ZoneId zid = ZoneId.of(unsupported);
// org.threeten.bp.zone.ZoneRulesException: Unknown time-zone ID: System/Unknown

这与旧的 JDK 类 java.util.TimeZone 不同,您可以在其中设置任意 ID。所以问题出现了如何处理这样的 zone-id。它是如此糟糕,以至于您甚至无法猜测是哪个真正的时区标识符。

唯一合理的做法是使用底层平台时区,尽管它的 zone-id 不可用,但它仍然可以通过表达式TimeZone.getDefault() 获得。请注意,损坏的区域 id 会阻止您使用 Threeten-ABP 的 tz 数据或除平台数据之外的任何其他 tz 存储库。

基于平台时区数据的最佳解决方法/破解方法如下(仍仅使用 ThreetenABP):

LocalDateTime ldt;

try {
    ldt = LocalDateTime.now();
} catch (DateTimeException ex) {
    long now = System.currentTimeMillis();
    int offsetInMillis = TimeZone.getDefault().getOffset(now);
    ldt = 
        LocalDateTime.ofEpochSecond(
            now / 1000, 
            (int) (now % 1000) * 1_000_000, 
            ZoneOffset.ofTotalSeconds(offsetInMillis / 1000));
}

正如我在评论中提到的,我已将某些 Android 设备的这种奇怪行为考虑在内,以稳定我的时间库Time4A,所以我觉得很高兴提及以下更清洁、更安全的替代方案从 v3.16-2016a 版本开始:

PlainTimestamp tsp = SystemClock.inLocalView().now();

它更干净,因为它不依赖于任何丑陋的异常处理,甚至不依赖于内部。如果无法解析底层系统时区的区域 ID,则 Time4A 会自动切换到系统时区周围的包装器,而不是使用自己的 tz 存储库。无需用户操作。

请注意,Time4A 具有基于自己的 tz 数据以及基于 Android 平台 tz 数据的时区的统一外观。您甚至可以同时使用两个 tz 数据(Timezone.of("Europe/Berlin") 使用 Time4A 数据(最新),而Timezone.of("java.util.TimeZone~Europe/Berlin") 使用可能是旧的平台数据)。此功能对于解析 Android 设备上显示的用户输入的本地时间非常有用。

它也更安全,因为与 ThreetenABP 相比,奇异的设备时间戳得到了正确验证,另请参阅其他一些 SO 帖子,例如 thisthat

从 Time4A 到 ThreetenABP 的桥可能如下所示:

LocalDateTime threeten = 
    LocalDateTime.of(
       tsp.getYear(), tsp.getMonth(), tsp.getDayOfMonth(), 
       tsp.getHour(), tsp.getMinute(), tsp.getSecond(), 
       tsp.getInt(PlainTime.NANO_OF_SECOND));

但是,我不推荐它,因为

a) 可以很快达到 dex 限制(同时使用两个库),

b) Time4A 拥有如此many features 并提供更好的 i18n 体验和卓越的格式和解析引擎,它可以完全取代 ThreetenABP。

Time4A 唯一的问题就是:它并不为人所知。

【讨论】:

  • 非常感谢您展示完整的错误处理解决方法。此外,您几乎在 Time4A 上卖给了我(抱歉,我最初将它与您的其他库 Time4J 混淆,因此我的 Android 特定问题)。与 ThreeTenABP (peirr.com/…) 的 2827 种方法相比,您能否告诉我目前在方法计数方面的情况?
  • @david.mihola 我已经详细调查了各种 apk-method-counts 并且必须更正我之前的评论。在不应用 Proguard 的情况下,计数约为 6280。使用 Proguard,我们可以在典型场景中将其减少到 2750 左右(我过去只沟通过)。所以这里的 ThreetenABP 在大小方面显然更好。顺便说一句,您还可以将这样的计数解释为对受支持功能的丰富程度的衡量,因此最终由用户在尺寸要求和功能计数之间做出权衡。
【解决方案2】:

是的,比该死的 Sony Bravia 4K 2015

不仅 LocalDate.now() 可以抛出,事实上任何依赖于“ZoneId.systemDefault()”的方法。因此,每次都包含在 try-catch 中会导致......不愉快的编码体验。

LocalDate.now() 隐式调用ZoneId.systemDefault()

所以,作为解决方法,我正在构建万无一失的ZoneId 并将其提供给LocalDate.now() 等。

public final class Hack {

    private static @NonNull String fix_TimeZone_getDefault_getID(String offsetId) {
        /* todo */
        return fixed_offset_id;
    }

    public static @NonNull ZoneId ZoneId() {
        String mayBeWeirdZoneId = TimeZone.getDefault().getID();
        ZoneId id;
        try {
            id = ZoneId.of(mayBeWeirdZoneId, ZoneId.SHORT_IDS);
        } catch (DateTimeException ignore) {
            id = ZoneId.of(fix_TimeZone_getDefault_getID(mayBeWeirdZoneId));
        }
        return id;
    }
}

用途:

LocalDateTime.now(Hack.ZoneId() /* instead of ZoneId.systemDefault() */ );  

ZoneId systemZone = Hack.ZoneId(); // my timezone, instead of ZoneId.systemDefault()

显然,您必须记住随时随地都使用它。 该死的,索尼。
Kotlin 的扩展方法将在这里派上用场。类似ZoneId.systemDefaultAndNowSeriously()LocalDate.nowLikeAGoodGirl()
但是,该死的。

PS:
FWIW
好吧,我相信 Bravia 没有完全错误的 offsetId,所以我只是解析它:

private static @NonNull String fix_TimeZone_getDefault_getID(String offsetId) {
    if (offsetId == null) return ZoneOffset.UTC.getId();

    int gmt_pos = offsetId.indexOf("GMT");
    String off_fix = ZoneOffset.UTC.getId();
    if (gmt_pos > 0) {
        off_fix = offsetId.substring(0, gmt_pos);
    }
    return off_fix;
}

但是,我不确定最后一个,手头没有设备。可能会在了解更多信息时更新收据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-07
    • 1970-01-01
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 2011-05-10
    相关资源
    最近更新 更多