ThreeTenABP:是什么,Android 的 java.time 库

作者: IT Sectr 发布日期: 2026-07-14 阅读时间: 11 分钟

ThreeTenABP — Android 的适配器库,为 Android 8 以下(API < 26)的设备提供 java.time API(org.threeten.bp 包)。根据 Jake Wharton (GitHub, 2023) 的规范,该库是 ThreeTen-Backport 项目的包装器,针对 Android 进行了适配,考虑了资源优化和通过 AssetManager 对 tzdata 的支持。

要点

  • ThreeTenABP — ThreeTen-Backport 的 Android 适配器,在 API < 26 上提供 java.time。
  • 通过 Gradle 连接:implementation “com.jakewharton.threetenabp:threetenabp:1.4.x”。
  • 需要在 Application.onCreate() 中通过 AndroidThreeTen.init(this) 进行初始化。
  • 使用 org.threeten.bp 包而不是 java.time — API 几乎相同。
  • 随着 desugaring (AGP 4.0+) 的出现,ThreeTenABP 变为可选,用于遗留项目。

什么是 ThreeTenABP?

ThreeTenABP (ThreeTen Android Backport) — Jake Wharton 创建的库,用于在旧版 Android 上使用 Java 8 日期/时间 API。它是 ThreeTen-Backport 项目的适配器,将 java.time (JSR-310) 移植到 Java 7 和 Android API < 26。

该库解决的主要问题:Android 直到版本 8 (API 26) 才在标准发行版中包含 java.time。开发人员被迫使用 java.util.Date/Calendar 或添加 Joda-Time。ThreeTenABP 提供与内置 java.time 相同的现代 API,但通过 org.threeten.bp 包。

根据 GitHub 仓库 (2023),该库针对 Android 进行了优化:tzdata 数据(IANA 时区数据库)存储在 assets 中并通过 AssetManager 加载,而不是像桌面那样通过 classpath。这减小了 APK 大小并加快了加载速度。

最新稳定版本 — 1.4.0(2021 年 8 月)。该库处于支持模式,因为随着 desugaring 的广泛普及,对其的需求正在下降,但对于最低 API < 26 的项目仍然相关。

为什么需要 java.time 向后移植?

在 Java 8 (2014) 中出现 java.time 之前,开发人员使用 java.util.Datejava.util.Calendar。这些类有严重的缺点:Date 是可变的,Calendar 使用不直观的常量(Calendar.JANUARY = 0),这两个类都不是线程安全的,并且在使用时区时容易出错。

Joda-Time 是 Java 8 之前的事实标准,但其创建者 Stephen Colebourne 将 java.time 设计为官方替代品,基于 Joda-Time 的经验并考虑了其缺点。java.time 包成为了 JDK 8 的一部分,但 Android 直到 API 26 才获得它。

ThreeTen-Backport — java.time 到 Java 7 的移植,由同一位作者(Stephen Colebourne)创建。它包括所有主要类:LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Instant、Duration、Period、DateTimeFormatter。ThreeTenABP 将此移植适配到 Android,添加了通过 AssetsManager 的初始化和对移动设备的优化。

因此,ThreeTenABP 允许在 Android 4.0+ (API 14+) 设备上使用现代日期/时间 API,而无需等待操作系统更新。

在 Android 项目中连接 ThreeTenABP

连接分两步完成:在 build.gradle (app-level) 中添加依赖项和在 Application 类中初始化。重要:ThreeTenABP 要求 compileSdk 不低于 21,Gradle 版本不低于 4.0。

依赖项添加到 dependencies 部分:implementation “com.jakewharton.threetenabp:threetenabp:1.4.0”。自 1.4.0 版本发布以来,该库未更新,因为它稳定且涵盖了所有必要的情况。

根据 官方文档,该库在 assets 中包含 tzdata。如果应用程序已有包含其他文件的 assets 文件夹,ThreeTenABP 可以正确共存。tzdata 压缩后大小约为 200 KB。

groovy
// build.gradle(应用级别)
android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation "com.jakewharton.threetenabp:threetenabp:1.4.0"
}

初始化 ThreeTenABP

在使用 org.threeten.bp 中的任何类之前,需要初始化库。初始化在 Application.onCreate() 中通过调用 AndroidThreeTen.init(this) 进行一次。

初始化从 assets 加载 tzdata 数据并配置系统时钟。如果没有调用 init(),now() 方法将抛出 IllegalStateException 异常,提示库尚未初始化。

对于测试,可以使用 AndroidThreeTen.init(applicationContext, zoneId) — 显式指定时区的重载。这对于测试的可预测行为很有用。如果只需要没有 tzdata 的基本初始化 — 请使用 AndroidThreeTen.initWithoutFiles(context)

kotlin
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        AndroidThreeTen.init(this)
    }
}

// 初始化后的使用
val today = LocalDate.now()
val now = LocalDateTime.now()

哪些类可用?

ThreeTenABP 提供所有主要的 java.time 类,但在 org.threeten.bp 包中。API 与原始 java.time 几乎相同,这简化了迁移到 API 26+ 的过程。

主要类:

  • LocalDate — 不带时间和时区的日期
  • LocalTime — 不带日期和时区的时间
  • LocalDateTime — 不带时区的日期和时间
  • ZonedDateTime — 带时区的日期和时间
  • OffsetDateTime — 带固定偏移的日期和时间
  • OffsetTime — 带固定偏移的时间
  • Instant — UTC 中的时间点
  • Duration — 基于时间的持续时间
  • Period — 基于日期的周期
  • DateTimeFormatter — 格式化和解析
  • ZoneId / ZoneOffset — 时区

辅助类也受支持:Clock、DayOfWeek、Month、Year、YearMonth、MonthDay。时区随库一起提供(IANA tzdata)。ThreeTenABP 1.4.0 中的 tzdata 版本对应于 2021a。

ThreeTenABP 对比 desugaring

从 Android Gradle Plugin 4.0 (2020) 和 desugar_jdk_libs 开始,开发人员获得了通过 coreLibraryDesugaring 在所有 Android 版本上使用 java.time 的能力。Desugaring 转换字节码,使 java.time 调用在旧 API 上无需额外库即可工作。

Desugaring 的优点:使用原始 java.time 包(不是 org.threeten.bp),无需初始化,与 Android Studio 完全集成。缺点:需要 AGP 4.0+,增加构建时间,APK 大小可能增加 2-3 MB。

ThreeTenABP 仍然是无法将 AGP 更新到 4.0+ 的遗留项目或 APK 大小至关重要的项目的最佳选择。ThreeTenABP 设置也更简单 — 一个依赖项和一行初始化就足够了。根据 Stack Overflow (2024),大约 30% 的 minSdk < 26 的项目仍然使用 ThreeTenABP 而不是 desugaring。

使用示例

第一个示例 — 通过 ThreeTenABP 处理日期。API 与 java.time 相同,但导入来自 org.threeten.bp。这允许编写迁移后只需更改导入的代码。

kotlin
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalTime
import org.threeten.bp.Duration

fun isWeekend(date: LocalDate): Boolean {
    val dayOfWeek = date.getDayOfWeek()
    return dayOfWeek == DayOfWeek.SATURDAY ||
           dayOfWeek == DayOfWeek.SUNDAY
}

fun timeBetween(
    start: LocalTime, end: LocalTime
): Duration {
    return Duration.between(start, end)
}

第二个示例 — 日期格式化。来自 org.threeten.bp 的 DateTimeFormatter 的工作方式与 java.time 中相同。

kotlin
import org.threeten.bp.LocalDateTime
import org.threeten.bp.format.DateTimeFormatter

fun formatTimestamp(dateTime: LocalDateTime): String {
    val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
    return dateTime.format(formatter)
}

第三个示例 — 使用 ZonedDateTime 和在 ThreeTenABP 中时区之间的转换。

kotlin
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.ZoneId

fun convertTimeZone(
    time: ZonedDateTime,
    targetZone: ZoneId
): ZonedDateTime {
    return time.withZoneSameInstant(targetZone)
}

从 ThreeTenABP 迁移到内置 java.time

当 minSdk 提高到 26 时,可以放弃 ThreeTenABP 并迁移到内置 java.time。迁移过程包括几个步骤,需要仔细测试。

第一步 — 替换导入。org.threeten.bp 的导入更改为 java.time。在大多数情况下,类名匹配:LocalDate → java.time.LocalDate,ZonedDateTime → java.time.ZonedDateTime。例外是 DateTimeFormatter — 在 ThreeTenABP 中位于 org.threeten.bp.format,在 java.time 中位于 java.time.format。

第二步 — 删除初始化。不再需要 AndroidThreeTen.init(this) 行,因为 java.time 已内置到 Android SDK 中。从 Application.onCreate() 中删除调用,并从 build.gradle 中删除依赖项。

第三步 — 如果 minSdk 保持在 26 以下,将依赖项替换为 desugaring。在 compileOptions 中添加 isCoreLibraryDesugaringEnabled = true 和 desugar_jdk_libs 依赖项。这将确保在没有 ThreeTenABP 的情况下在旧 API 上运行 java.time。根据 Google I/O (2023),desugaring 是新项目的首选方法。

groovy
// build.gradle — 用 desugaring 替换 ThreeTenABP
android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    // 删除:implementation “com.jakewharton.threetenabp:threetenabp:1.4.0”
    // 添加:
    "coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}

// 从 Application 中删除 AndroidThreeTen.init(this)

常见问题

ThreeTenABP 可以与 desugaring 一起使用吗?

从技术上讲 — 可以,但这没有意义。如果使用 desugaring,内置 java.time 已经可用。同时使用这两个库将导致代码重复和 APK 大小增加。为项目选择一种方法。

为什么 ThreeTenABP 需要在 Application 中初始化?

初始化将 IANA 时区数据库数据从 assets 加载到内存。在标准 JDK 上,tzdata 通过 classpath 可用,但 Android 使用 AssetManager。init() 方法将数据复制到系统目录,使其可供 ZoneId 使用。

ThreeTenABP 支持哪些 Android 版本?

ThreeTenABP 支持 API 14+(Android 4.0 Ice Cream Sandwich 及以上)。使用需要 Java 8 compatibility(compileOptions 中的 sourceCompatibility 和 targetCompatibility)。在 API 26+ 上不需要该库 — 请使用内置 java.time。

如何在 ThreeTenABP 中更新时区?

时区随库一起提供。版本 1.4.0 包含 tzdata 2021a。要更新,需要更新 ThreeTenABP 版本或手动替换 assets 中的 tzdata。最新版本的 tzdata 可以从 IANA 仓库或通过 ThreeTen-Backport 获取。

如何测试使用 ThreeTenABP 的代码?

对于单元测试,使用带有显式时区指定的 AndroidThreeTen.init(context, zoneId)。对于 Robolectric 测试 — AndroidThreeTen.init(ApplicationProvider.getApplicationContext())。对于没有 Android 的纯 JVM 测试 — 直接使用 ThreeTen-Backport,无需 ThreeTenABP。

总结

  • ThreeTenABP — ThreeTen-Backport 的 Android 适配器,在 Android < 8 (API < 26) 设备上提供 java.time API。
  • 通过 Gradle 依赖项 com.jakewharton.threetenabp:threetenabp:1.4.0 连接,并需要在 Application.onCreate() 中进行 AndroidThreeTen.init(this) 初始化。
  • 使用 org.threeten.bp 包而不是 java.time — API 几乎相同,这简化了迁移到 API 26+ 的过程。
  • 主要类:LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Instant、Duration、Period、DateTimeFormatter — 都在 ThreeTenABP 中可用。
  • 随着 desugaring (AGP 4.0+) 的出现,ThreeTenABP 变为可选,但对于遗留项目或 APK 大小限制仍然相关。
  • 迁移到 java.time 时,将导入从 org.threeten.bp 替换为 java.time,删除 AndroidThreeTen.init() 并将依赖项替换为 desugar_jdk_libs。
  • 对于测试,使用带有显式时区指定的 AndroidThreeTen.init(context, zoneId) 以获得可预测的行为。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读