ThreeTenABP là một thư viện adapter cho Android cung cấp API java.time (gói org.threeten.bp) trên các thiết bị chạy Android dưới 8 (API < 26). Theo đặc tả của Jake Wharton (GitHub, 2023), thư viện này là một wrapper bao quanh dự án ThreeTen-Backport, được thích ứng cho Android với tối ưu hóa tài nguyên và hỗ trợ tzdata thông qua AssetManager.
Điểm chính
ThreeTenABP (ThreeTen Android Backport) là một thư viện được tạo bởi Jake Wharton để sử dụng API ngày/giờ Java 8 trên các phiên bản Android cũ. Nó là một adapter cho dự án ThreeTen-Backport, dự án này port java.time (JSR-310) sang Java 7 và Android API < 26.
Vấn đề chính mà thư viện giải quyết: Android trước phiên bản 8 (API 26) không bao gồm java.time trong bản phân phối tiêu chuẩn. Các nhà phát triển buộc phải sử dụng java.util.Date/Calendar hoặc thêm Joda-Time. ThreeTenABP cung cấp cùng API hiện đại như java.time tích hợp sẵn, nhưng thông qua gói org.threeten.bp.
Theo kho lưu trữ GitHub (2023), thư viện được tối ưu hóa cho Android: dữ liệu tzdata (IANA Time Zone Database) được lưu trữ trong assets và tải qua AssetManager, thay vì classpath như trên desktop. Điều này giảm kích thước APK và tăng tốc độ tải.
Phiên bản ổn định mới nhất là 1.4.0 (tháng 8 năm 2021). Thư viện đang trong chế độ bảo trì, vì với sự chấp nhận rộng rãi của desugaring, nhu cầu về nó đang giảm, nhưng nó vẫn phù hợp cho các dự án có API tối thiểu < 26.
Trước khi java.time được giới thiệu trong Java 8 (2014), các nhà phát triển đã sử dụng java.util.Date và java.util.Calendar. Các lớp này có những nhược điểm nghiêm trọng: Date có thể thay đổi, Calendar sử dụng các hằng số không trực quan (Calendar.JANUARY = 0), cả hai lớp đều không an toàn luồng và dễ xảy ra lỗi khi làm việc với múi giờ.
Joda-Time là tiêu chuẩn thực tế trước Java 8, nhưng người tạo ra nó Stephen Colebourne đã thiết kế java.time như là sự thay thế chính thức, dựa trên kinh nghiệm của Joda-Time và khắc phục những thiếu sót của nó. Gói java.time được bao gồm trong JDK 8, nhưng Android không nhận được nó cho đến API 26.
ThreeTen-Backport là một port của java.time sang Java 7, được tạo bởi cùng tác giả (Stephen Colebourne). Nó bao gồm tất cả các lớp chính: LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period, DateTimeFormatter. ThreeTenABP thích ứng port này cho Android, thêm khởi tạo thông qua AssetsManager và tối ưu hóa cho thiết bị di động.
Do đó, ThreeTenABP cho phép bạn sử dụng API ngày/giờ hiện đại trên các thiết bị chạy Android 4.0+ (API 14+) mà không cần chờ đợi cập nhật hệ điều hành.
Việc thêm được thực hiện trong hai bước: thêm phụ thuộc trong build.gradle (cấp ứng dụng) và khởi tạo trong lớp Application. Quan trọng: ThreeTenABP yêu cầu compileSdk ít nhất 21 và phiên bản Gradle ít nhất 4.0.
Phụ thuộc được thêm trong phần dependencies: implementation "com.jakewharton.threetenabp:threetenabp:1.4.0". Kể từ khi phát hành 1.4.0, thư viện không được cập nhật, vì nó ổn định và bao phủ tất cả các trường hợp cần thiết.
Theo tài liệu chính thức, thư viện bao gồm tzdata trong assets. Nếu ứng dụng đã có thư mục assets với các tệp khác, ThreeTenABP sẽ cộng tồn chính xác với chúng. Kích thước của tzdata là khoảng 200 KB khi nén.
// build.gradle (cấp ứng dụng)
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "com.jakewharton.threetenabp:threetenabp:1.4.0"
}
Trước khi sử dụng bất kỳ lớp nào từ org.threeten.bp, thư viện phải được khởi tạo. Việc khởi tạo được thực hiện một lần trong Application.onCreate() bằng cách gọi AndroidThreeTen.init(this).
Khởi tạo tải dữ liệu tzdata từ assets và cấu hình đồng hồ hệ thống. Nếu không gọi init(), các phương thức now() sẽ ném IllegalStateException với thông báo rằng thư viện chưa được khởi tạo.
Để kiểm thử, bạn có thể sử dụng AndroidThreeTen.init(applicationContext, zoneId) — một overload với chỉ định múi giờ rõ ràng. Điều này hữu ích cho hành vi có thể dự đoán trong các bài kiểm thử. Nếu chỉ cần khởi tạo cơ bản mà không có tzdata, hãy sử dụng AndroidThreeTen.initWithoutFiles(context).
class App : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
}
}
// Sử dụng sau khi khởi tạo
val today = LocalDate.now()
val now = LocalDateTime.now()
ThreeTenABP cung cấp tất cả các lớp chính của java.time, nhưng trong gói org.threeten.bp. API gần như giống hệt với java.time gốc, giúp việc di chuyển sang API 26+ dễ dàng hơn.
Các lớp chính:
Các lớp trợ giúp cũng được hỗ trợ: Clock, DayOfWeek, Month, Year, YearMonth, MonthDay. Các múi giờ được đóng gói cùng thư viện (IANA tzdata). Phiên bản tzdata trong ThreeTenABP 1.4.0 tương ứng với 2021a.
Bắt đầu từ Android Gradle Plugin 4.0 (2020) và desugar_jdk_libs, các nhà phát triển có khả năng sử dụng java.time trên tất cả các phiên bản Android thông qua coreLibraryDesugaring. Desugaring chuyển đổi bytecode để các lời gọi java.time hoạt động trên các API cũ mà không cần thư viện bổ sung.
Ưu điểm của desugaring: sử dụng gói java.time gốc (không phải org.threeten.bp), không yêu cầu khởi tạo, tích hợp đầy đủ với Android Studio. Nhược điểm: yêu cầu AGP 4.0+, tăng thời gian biên dịch, kích thước APK có thể tăng 2-3 MB.
ThreeTenABP vẫn là lựa chọn tốt nhất cho các dự án legacy không thể cập nhật AGP lên 4.0+, hoặc khi kích thước APK là quan trọng. ThreeTenABP cũng dễ cài đặt hơn — chỉ một phụ thuộc và một dòng khởi tạo. Theo Stack Overflow (2024), khoảng 30% dự án có minSdk < 26 vẫn sử dụng ThreeTenABP thay vì desugaring.
Ví dụ đầu tiên thể hiện việc làm việc với ngày tháng sử dụng ThreeTenABP. API giống java.time, nhưng các import đến từ org.threeten.bp. Điều này cho phép bạn viết mã mà sau khi di chuyển chỉ cần thay thế các import.
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)
}
Ví dụ thứ hai thể hiện định dạng ngày. DateTimeFormatter từ org.threeten.bp hoạt động giống như trong 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)
}
Ví dụ thứ ba thể hiện việc làm việc với ZonedDateTime và chuyển đổi giữa các múi giờ trong ThreeTenABP.
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.ZoneId
fun convertTimeZone(
time: ZonedDateTime,
targetZone: ZoneId
): ZonedDateTime {
return time.withZoneSameInstant(targetZone)
}
Khi nâng minSdk lên 26, bạn có thể loại bỏ ThreeTenABP và chuyển sang java.time tích hợp sẵn. Quá trình di chuyển bao gồm một số bước và yêu cầu kiểm thử kỹ lưỡng.
Bước đầu tiên là thay thế các import. Các import từ org.threeten.bp được thay đổi thành java.time. Trong hầu hết các trường hợp, tên lớp trùng khớp: LocalDate → java.time.LocalDate, ZonedDateTime → java.time.ZonedDateTime. Ngoại lệ là DateTimeFormatter — trong ThreeTenABP nó ở org.threeten.bp.format, trong java.time nó ở java.time.format.
Bước thứ hai là xóa khởi tạo. Dòng AndroidThreeTen.init(this) không còn cần thiết, vì java.time đã được tích hợp trong Android SDK. Xóa lời gọi khỏi Application.onCreate() và phụ thuộc khỏi build.gradle.
Bước thứ ba là thay thế phụ thuộc bằng desugaring, nếu minSdk vẫn dưới 26. Thêm isCoreLibraryDesugaringEnabled = true trong compileOptions và phụ thuộc desugar_jdk_libs. Điều này sẽ đảm bảo java.time hoạt động trên các API cũ mà không cần ThreeTenABP. Theo Google I/O (2023), desugaring là phương pháp ưu tiên cho các dự án mới.
// build.gradle — thay thế ThreeTenABP bằng desugaring
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
// Xóa: implementation "com.jakewharton.threetenabp:threetenabp:1.4.0"
// Thêm:
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
// Xóa AndroidThreeTen.init(this) khỏi Application
Câu hỏi thường gặp
Về mặt kỹ thuật — có, nhưng không có ý nghĩa. Nếu desugaring được sử dụng, java.time tích hợp sẵn đã có sẵn. Sử dụng cả hai thư viện sẽ dẫn đến trùng lặp mã và tăng kích thước APK. Hãy chọn một cách tiếp cận cho dự án của bạn.
Khởi tạo tải IANA Time Zone Database từ assets vào bộ nhớ. Trên JDK tiêu chuẩn, tzdata có sẵn qua classpath, nhưng Android sử dụng AssetManager. Phương thức init() sao chép dữ liệu vào thư mục hệ thống, làm cho chúng có sẵn cho ZoneId.
ThreeTenABP hỗ trợ API 14+ (Android 4.0 Ice Cream Sandwich trở lên). Yêu cầu tương thích Java 8 (sourceCompatibility và targetCompatibility trong compileOptions). Trên API 26+, thư viện không cần thiết — hãy sử dụng java.time tích hợp sẵn.
Các múi giờ được đóng gói cùng thư viện. Phiên bản 1.4.0 bao gồm tzdata 2021a. Để cập nhật, bạn cần cập nhật phiên bản ThreeTenABP hoặc thay thế thủ công tzdata trong assets. Các phiên bản tzdata mới nhất có thể được lấy từ kho lưu trữ IANA hoặc thông qua ThreeTen-Backport.
Đối với kiểm thử đơn vị, hãy sử dụng AndroidThreeTen.init(context, zoneId) với chỉ định múi giờ rõ ràng. Đối với kiểm thử Robolectric — AndroidThreeTen.init(ApplicationProvider.getApplicationContext()). Đối với kiểm thử JVM thuần túy không có Android — hãy sử dụng ThreeTen-Backport trực tiếp mà không cần ThreeTenABP.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm