如何在Android项目中使用ThreeTenABP

232

我正在使用Android Studio 2.1.2,我的Java设置如下:

>java -version
> openjdk version "1.8.0_91"
> OpenJDK Runtime Environment (build 1.8.0_91-8u91-b14-3ubuntu1~15.10.1-b14)
> OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)

我花了数小时来尝试解决这个问题。答案来自一些相关答案的结合,所以我认为我会记录下我学到的东西,帮助其他可能正在苦苦挣扎的人。请参见答案。

3个回答

229

注意:此答案在技术上是正确的,但现在已过时

Java 8+ API desugaring支持现在可通过Android Gradle插件4.0.0+使用

(另请参见Basil Bourque下面的答案

ThreeTenABP库的开发正在逐渐结束。请考虑在未来几个月内切换到Android Gradle插件4.0、java.time.*和其核心库desugaring功能。

要在任何版本的Android平台上启用对这些语言API的支持,请将Android插件更新为4.0.0(或更高版本),并在您的模块的build.gradle文件中包含以下内容(从Android Developers网站上的Java 8支持页面的此部分中获取,该页面还提供有关desugaring的其他信息):

android {
  defaultConfig {
    // Required when setting minSdkVersion to 20 or lower
    multiDexEnabled true
  }

  compileOptions {
    // Flag to enable support for the new language APIs
    coreLibraryDesugaringEnabled true
    // Sets Java compatibility to Java 8
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5'
}

原始答案

第一个发现:为什么你必须使用ThreeTenABP而不是java.timeThreeTen-Backport或者甚至是Joda-Time

这只是定义新标准的非常漫长过程的简短版本。所有这些包基本上都是相同的东西:它们提供了Java中良好的、现代的时间处理功能。差异微妙但重要。

最明显的解决方案是使用内置的java.time包,因为这是Java中处理时间和日期的新标准方式。它是JSR 310的实现,这是一个基于Joda-Time库的新标准提案。

然而,java.time是在Java 8中引入的。Android直到Marshmallow运行在Java 7上("Android N"是第一个引入Java 8语言特性的版本)。因此,除非你只针对Android N Nougat及以上版本,否则不能依赖于Java 8语言特性(我不确定这是否完全正确,但这是我的理解)。所以java.time不适用。

下一个选择可能是Joda-Time,因为JSR 310是基于Joda-Time的。然而,正如ThreeTenABP readme所指出的那样,由于许多原因,Joda-Time并不是最佳选择。
接下来是ThreeTen-Backport,它将Java 8的大部分(但不是全部)java.time功能向后移植到Java 7。这对于大多数用例来说都很好,但正如ThreeTenABP readme中所指出的那样,它在Android上存在性能问题。

所以最后一个看起来正确的选项是ThreeTenABP

第二个发现:构建工具和依赖管理

由于编译程序(尤其是使用大量外部库的程序)非常复杂,Java几乎总是使用"构建工具"来管理该过程。MakeApache AntApache MavenGradle都是用于Java程序的构建工具(请参见this post进行比较)。正如下文所述,Gradle是Android项目选择的构建工具。

这些构建工具包括依赖管理。Apache Maven似乎是第一个引入集中式软件包仓库的工具。Maven引入了Maven中央仓库,它提供了与php的composer和Ruby的gem以rubygems.org相当的功能。换句话说,Maven中央仓库对于Maven(和Gradle)来说就像Packagist对于composer一样,是一个版本化软件包的权威和安全来源。
第三个发现:Gradle在Android项目中处理依赖关系
我计划阅读Gradle文档here,包括他们的免费电子书。如果我几周前在学习Android时已经阅读了这些文档,我肯定会知道Gradle可以使用Maven中央仓库来管理Android项目的依赖关系。此外,正如this StackOverflow答案中详细说明的那样,在Android Studio 0.8.9版本之后,Gradle通过Bintray的JCenter隐式使用Maven中央仓库,这意味着您不需要进行额外的配置来设置仓库,只需列出所需的依赖即可。

第四个发现:项目依赖在 [项目目录]/app/build.gradle 中列出

对于那些有使用过 Java Gradle 的经验的人来说,这一点是显而易见的,但我花了一些时间才弄明白。如果你看到有人说“哦,只需添加 compile 'this-or-that.jar'”或类似的话,那么要知道,compile是 build.gradle 文件中的指示编译时依赖关系的指令。这是官方Gradle页面上有关依赖管理的链接。

第五个发现:ThreeTenABP 由Jake Wharton管理,而不是由 ThreeTen 管理

又是一个让我花费了太多时间去解决的问题。如果你在 Maven Central 中搜索 ThreeTen,你只会看到 threetenbp 的包,而没有 threetenabp。如果你去 ThreeTenABP的 GitHub 代码库, 你会在 Readme 的下载部分中看到那行臭名昭著的compile 'this-or-that'代码。

当我第一次进入这个github仓库时,我不知道那个编译行是什么意思,于是我试图在我的终端中运行它(显然会失败)。我感到沮丧,直到我弄清楚了其他的东西,很久之后才回来看,最终意识到它是一个指向com.jakewharton.threetenabp仓库的Maven Repo行,而不是org.threeten仓库。这就是为什么我认为ThreeTenABP包不在Maven仓库中。
现在一切都变得非常容易。您可以通过确保您的[project folder]/app/build.gradle文件在其dependencies部分中具有implementation 'com.jakewharton.threetenabp:threetenabp:1.2.1'行来在Android项目中获得现代时间处理功能:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "me.ahuman.myapp"
        minSdkVersion 11
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    implementation 'com.android.support:appcompat-v7:23.4.0'
    implementation 'com.android.support:design:23.4.0'
    implementation 'com.jakewharton.threetenabp:threetenabp:1.2.1'
}

同时将此代码添加到Application类中:

public class App extends Application {    
    @Override
    public void onCreate() {
        super.onCreate();
        AndroidThreeTen.init(this);
        //...
    }
}

1
感谢您的精彩文章。不过,我想知道您是否也考虑了JodaTimeAndroid - Bob
2
@Bob,JodaTime基本上与JSR-310相同(甚至基本上由同一批人制作),只是JSR-310据说有更少的设计缺陷(例如,请参见此文章)。[...续上] - M. Prokhorov
@Bob,根据我使用这两个库的经验,我不认为我同意JSR-310与Joda之间的所有差异(例如,它如何在基本上完全不同的API分支中区分“人类”时间和“机器”时间,这使得当您需要与遗留API进行接口时非常烦人,因为它们中的大多数被认为是“机器”,但大多数逻辑要求将它们作为“人类”处理)。但我确实看到了为什么以及如何做出这些决定,而且从编写自己的年表的经验来看,JSR-310肯定更具可扩展性。 - M. Prokhorov
2
澄清一下评论...java.time框架(JSR 310)是Joda-Time项目的官方继任者。两个项目都由同一个人 Stephen Colebourne 领导。Joda-Time项目现在已经进入维护模式,团队建议迁移到java.time。 - Basil Bourque
7
在使用API之前,请确保在onCreate()中调用AndroidThreeTen.init(this)。参见ThreeTen-Backport error on Android - ZoneRulesException: No time-zone data files registered - Ole V.V.
显示剩余4条评论

18

接受的kael提供的答案是正确的。此外,我会提及一些事情,并提供有关获取java.time功能的图表。

java.time内置于Android 26+

如果目标是Android 26或更高版本,则会发现一个与Android捆绑的JSR 310java.time类)实现。无需添加ThreeTenABP

API几乎相同

仅为澄清起见,Android的ThreeTenABPThreeTen-Backport库的适配版,它将大多数java.time功能引入到Java 6Java 7中。该反向端口与java.time几乎具有相同的API。

假设您现在的目标是早于26的Android版本,因此使用ThreeTenABP。稍后,您可能最终会停止支持这些早期版本的Android,以使用与Android捆绑的java.time类。当发生这种情况时,您需要对代码库进行少量更改,而不是(a)切换import语句,以及(b)更改您对org.threeten.bp.DateTimeUtils所做的任何调用以使用添加到旧遗留日期时间类(DateGregorianCalendar)中的新转换方法。

ThreeTenABPjava.time的过渡过程应该是平稳且几乎无痛的。

何时使用哪个框架

以下是显示三个框架并指示在哪些场景下使用哪个框架的图表。

➥ 更新: Android Gradle插件4.0.0+带来了一个新的第四个选项,API去糖,使得原本没有集成在早期Android中的java.time功能子集变得可用。请参考kael的主要答案开头。

Table of which java.time library to use with which version of Java or Android


关于java.time

java.time框架内置于Java 8及以上版本。这些类替代了老旧的legacy日期时间类,如java.util.DateCalendarSimpleDateFormat

想要了解更多,请查看Oracle Tutorial。在Stack Overflow上搜索可以找到很多例子和解释。规范是JSR 310

Joda-Time项目现在处于maintenance mode,建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC driver兼容的驱动程序。不需要字符串,也不需要java.sql.*类。Hibernate 5和JPA 2.2支持java.time

如何获取java.time类?


我对在Kotlin中使用日期/时间感到非常困惑,因为java.time是要使用的内容,但它只适用于26+。然后说使用ThreeTen,但后来它就过时了。所以,请问您能告诉我在API 21项目中应该使用什么吗?谢谢! - Raw Hasan
@RawHasan 请查看我对"About java.time"部分的最新编辑。(A) 对于 Android 26 之前的版本,我建议您先尝试最新的 Android 工具中的 "API desugaring" 功能。这会在早期版本的 Android 中提供大部分 java.time 功能。(B) 如果缺少您需要的某些功能,则可以添加 ThreeTenABP 库到您的项目中,它是 ThreeTen-Backport Java 库的一个专门为 Android 设计的适配版,同样提供了大部分 java.time 功能。ThreeTen-Backport 库使用几乎相同的 API 与 java.time 相同。 - Basil Bourque
谢谢,@Basil Bourque!我是一名新的Kotlin开发者,所有这些日期时间的东西对我来说都变成了一个巨大的困惑!但我想我终于弄明白了。我刚刚根据Kael的最新编辑设置了依赖项,看起来java.time功能现在在我的21 API项目上正在工作。 - Raw Hasan

8
在Android Studio中的build.gradle(模块级)文件中添加以下内容:
implementation 'com.jakewharton.threetenabp:threetenabp:1.2.1'

创建Application类并按以下方式初始化:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接