ThreeTenABP — адаптерна библиотека за Android, предоставяща API java.time (пакет org.threeten.bp) на устройства с Android под 8 (API < 26). Според спецификацията Jake Wharton (GitHub, 2023), библиотеката е обвивка над проекта ThreeTen-Backport, адаптирана за Android с оптимизация на ресурси и поддръжка на tzdata чрез AssetManager.
Основни моменти
ThreeTenABP (ThreeTen Android Backport) — библиотека, създадена от Jake Wharton за използване на Java 8 API за дата/час на по-стари версии на Android. Тя е адаптер за проекта ThreeTen-Backport, който пренася java.time (JSR-310) към Java 7 и Android API < 26.
Основният проблем, който библиотеката решава: Android до версия 8 (API 26) не включваше java.time в стандартната доставка. Разработчиците бяха принудени да използват java.util.Date/Calendar или да добавят Joda-Time. ThreeTenABP предоставя същия модерен API като вградения java.time, но чрез пакета org.threeten.bp.
Според GitHub хранилището (2023), библиотеката е оптимизирана за Android: данните tzdata (IANA Time Zone Database) се съхраняват в assets и се зареждат чрез AssetManager, а не чрез classpath както на десктоп. Това намалява размера на APK и ускорява зареждането.
Последната стабилна версия — 1.4.0 (август 2021). Библиотеката е в режим на поддръжка, тъй като с широкото разпространение на desugaring нуждата от нея намалява, но остава актуална за проекти с минимален API < 26.
Преди появата на java.time в Java 8 (2014) разработчиците използваха java.util.Date и java.util.Calendar. Тези класове имат сериозни недостатъци: Date е мутабилен, Calendar използва неинтуитивни константи (Calendar.JANUARY = 0), и двата класа не са thread-safe и са склонни към грешки при работа с часови зони.
Joda-Time беше de facto стандарт преди 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 позволява използването на модерен API за дата/час на устройства с Android 4.0+ (API 14+) без да се чака актуализация на операционната система.
Свързването се извършва в две стъпки: добавяне на зависимост в build.gradle (app-level) и инициализация в Application класа. Важно: ThreeTenABP изисква compileSdk не по-малко от 21 и Gradle версия не по-малка от 4.0.
Зависимостта се добавя в секцията dependencies: implementation „com.jakewharton.threetenabp:threetenabp:1.4.0”. От изданието 1.4.0 библиотеката не е актуализирана, тъй като е стабилна и покрива всички необходими случаи.
Според официалната документация, библиотеката включва tzdata в assets. Ако приложението вече има папка assets с други файлове, ThreeTenABP коректно съществува заедно с тях. Размерът на tzdata е около 200 KB в компресиран вид.
// build.gradle (ниво на приложението)
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "com.jakewharton.threetenabp:threetenabp:1.4.0"
}
Преди използване на който и да е клас от org.threeten.bp трябва да инициализирате библиотеката. Инициализацията се извършва веднъж в Application.onCreate() чрез извикване на AndroidThreeTen.init(this).
Инициализацията зарежда данните tzdata от assets и конфигурира системния часовник. Без извикване на init() методите now() ще хвърлят изключение IllegalStateException с съобщение, че библиотеката не е инициализирана.
За тестове може да се използва AndroidThreeTen.init(applicationContext, zoneId) — претоварване с изрично посочване на часова зона. Това е полезно за предвидимо поведение на тестовете. Ако е необходима само основна инициализация без tzdata — използвайте AndroidThreeTen.initWithoutFiles(context).
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+.
Основни класове:
Поддържат се и помощни класове: Clock, DayOfWeek, Month, Year, YearMonth, MonthDay. Часовите зони се доставят с библиотеката (IANA tzdata). Версията tzdata в ThreeTenABP 1.4.0 съответства на 2021a.
От Android Gradle Plugin 4.0 (2020) и desugar_jdk_libs, разработчиците получиха възможност да използват java.time на всички версии на Android чрез coreLibraryDesugaring. Desugaring трансформира байткода така, че извикванията на java.time работят на стари API без допълнителни библиотеки.
Предимства на desugaring: използва се оригиналният пакет java.time (не org.threeten.bp), не изисква инициализация, пълна интеграция с Android Studio. Недостатъци: изисква AGP 4.0+, добавя време за компилиране, размерът на APK може да се увеличи с 2-3 MB.
ThreeTenABP остава най-добрият избор за legacy проекти, които не могат да актуализират AGP до 4.0+, или когато размерът на APK е критичен. ThreeTenABP е и по-прост за настройка — една зависимост и един ред инициализация са достатъчни. Според Stack Overflow (2024), около 30% от проектите с minSdk < 26 все още използват ThreeTenABP вместо desugaring.
Първи пример — работа с дати чрез ThreeTenABP. API е идентичен с java.time, но импортите идват от org.threeten.bp. Това позволява писане на код, който след миграция изисква само смяна на импорти.
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)
}
Втори пример — форматиране на дата. DateTimeFormatter от org.threeten.bp работи както в java.time.
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.
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.ZoneId
fun convertTimeZone(
time: ZonedDateTime,
targetZone: ZoneId
): ZonedDateTime {
return time.withZoneSameInstant(targetZone)
}
При повишаване на 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.
Трета стъпка — замяна на зависимостта с desugaring, ако minSdk остава под 26. Добавете isCoreLibraryDesugaringEnabled = true в compileOptions и зависимостта desugar_jdk_libs. Това ще осигури работа на java.time на стари API без ThreeTenABP. Според Google I/O (2023), desugaring е предпочитаният метод за нови проекти.
// build.gradle — заменете ThreeTenABP с desugaring
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
// Премахнете: implementation „com.jakewharton.threetenabp:threetenabp:1.4.0”
// Добавете:
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
// Премахнете AndroidThreeTen.init(this) от Application
Често задавани въпроси
Технически — да, но няма смисъл. Ако се използва desugaring, вграденият java.time вече е наличен. Използването на двете библиотеки ще доведе до дублиране на код и увеличаване на размера на APK. Изберете един подход за проекта.
Инициализацията зарежда данните IANA Time Zone Database от assets в паметта. На стандартен JDK tzdata е достъпна чрез classpath, но Android използва AssetManager. Методът init() копира данните в системната директория, правейки ги достъпни за ZoneId.
ThreeTenABP поддържа API 14+ (Android 4.0 Ice Cream Sandwich и по-нови). За използване е необходима Java 8 compatibility (sourceCompatibility и targetCompatibility в compileOptions). На API 26+ библиотеката не е необходима — използвайте вграден java.time.
Часовите зони се доставят с библиотеката. Версия 1.4.0 включва tzdata 2021a. За актуализация трябва да актуализирате версията на ThreeTenABP или ръчно да замените tzdata в assets. Най-новите версии на tzdata могат да бъдат получени от хранилището на IANA или чрез ThreeTen-Backport.
За unit тестове използвайте AndroidThreeTen.init(context, zoneId) с изрично посочване на зона. За Robolectric тестове — AndroidThreeTen.init(ApplicationProvider.getApplicationContext()). За чисти JVM тестове без Android — използвайте директно ThreeTen-Backport без ThreeTenABP.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също