Bạn muốn một khoảng chờ vẫn hết hạn khi điện thoại đã ngủ một lúc. Một phép trừ hai mốc uptime có thể cho thời lượng ngắn hơn kỳ vọng vì đồng hồ ấy không tính deep sleep. Chọn đúng clock cần bắt đầu từ định nghĩa khoảng thời gian, không chỉ từ đơn vị millisecond.
Bài đọc Android SystemClock và System ngày 07/11/2026. Mẫu Kotlin giả định minSdk từ 23; elapsedRealtime và uptimeMillis có từ API 1. Code được rà thủ công, chưa build Android hoặc chạy thử deep sleep trên thiết bị. Các số bên dưới là dữ liệu minh họa phép tính, không phải log đo pin hay thời gian ngủ thật.
Ba câu hỏi cho ba loại mốc
System.currentTimeMillis biểu diễn thời điểm theo lịch, tính từ epoch. Nó phù hợp khi bạn cần ghi timestamp để hiển thị hoặc đối chiếu thời điểm, nhưng đồng hồ lịch có thể được chỉnh.
SystemClock.elapsedRealtime đếm từ lúc boot và bao gồm deep sleep. uptimeMillis cũng đếm từ boot nhưng loại thời gian deep sleep. Với phép đo khoảng thời gian thông thường có tính lúc máy ngủ, elapsedRealtime là lựa chọn được tài liệu khuyến nghị.
Hai giá trị since-boot không phải timestamp UTC. Đừng đưa chúng trực tiếp vào trình định dạng ngày giờ rồi kỳ vọng nhận đúng ngày sự kiện.
1. Tách phép so thời lượng khỏi việc đọc clock
Trong file Kotlin của module Android, có thể định nghĩa helper thuần sau. Nó giả định start và now đến từ cùng một clock trong cùng lần boot:
fun hasElapsed(startMs: Long, nowMs: Long, durationMs: Long): Boolean {
require(startMs >= 0)
require(nowMs >= startMs)
require(durationMs >= 0)
return nowMs - startMs >= durationMs
}
Định nghĩa dùng dấu >= nên đúng tại biên cũng được coi là hết khoảng chờ. Duration bằng 0 hết ngay khi đọc cùng mốc start. Nếu nghiệp vụ muốn quy tắc khác, đổi hợp đồng và assertions thay vì sửa mơ hồ một dấu so sánh.
Helper không phát hiện đáng tin cậy một lần reboot từ mọi cặp số. Điều kiện now lớn hơn start không chứng minh hai mốc thuộc cùng lần boot. Người gọi phải bảo đảm nguồn và vòng đời của hai mốc.
2. Đọc elapsedRealtime trong một đối tượng giữ ở bộ nhớ
Ghép class sau vào cùng file, giữ import ở đầu file Kotlin:
import android.os.SystemClock
class InMemoryWaitWindow(private val durationMs: Long) {
private val startedAt = SystemClock.elapsedRealtime()
init {
require(durationMs >= 0)
}
fun hasExpired(): Boolean {
return hasElapsed(startedAt, SystemClock.elapsedRealtime(), durationMs)
}
}
Đối tượng chỉ giữ mốc trong bộ nhớ. Nó không đăng ký timer, không tự đánh thức app và không gọi callback khi hết hạn. hasExpired chỉ trả lời câu hỏi tại lúc bạn gọi nó; lịch thực thi một công việc là bài toán khác.
Mẫu cũng không lưu startedAt vào preferences để dùng qua reboot. Nếu cần trạng thái hết hạn tồn tại qua nhiều lần mở máy hoặc đồng bộ giữa thiết bị, thiết kế thêm nguồn thời gian và chính sách nghiệp vụ phù hợp.
Kiểm tra phép tính bằng đầu vào biết trước
Với duration 5000, start 1000 và now 5999 phải chưa hết hạn; now 6000 phải hết. Thêm ca duration 0, now nhỏ hơn start và duration âm để kiểm tra guard. Những ca này kiểm tra logic helper, chưa kiểm tra implementation SystemClock của Android.
Giả sử một kịch bản có 3000 ms máy hoạt động và 10000 ms deep sleep. Theo định nghĩa đồng hồ, mức tăng minh họa của elapsed là 13000 ms, còn uptime là 3000 ms. Không dùng thao tác tắt màn hình đơn thuần làm bằng chứng rằng thiết bị đã thật sự vào deep sleep.
Giới hạn cần giữ khi đo thực tế
Hai lần đọc clock có độ phân giải và chi phí của môi trường thực. Một ví dụ dùng đơn vị millisecond không chứng minh bạn đo chính xác đến từng millisecond cho mọi thao tác. Khi benchmark, xác định workload, warm-up và công cụ đo riêng; bài không đưa số liệu benchmark.
Không dùng mốc elapsed của máy này để trừ mốc của máy khác, hoặc trộn elapsed với currentTimeMillis. Các hệ quy chiếu khác nhau có thể tạo ra con số trông hợp lệ nhưng không đại diện khoảng thời gian cần đo.
Ghi rõ tên clock trong biến, loại mốc trong dữ liệu chẩn đoán và vòng đời mà mốc được phép dùng. Cách đặt hợp đồng ấy giúp bạn tránh đọc “năm giây” theo hai định nghĩa khác nhau khi app đi vào trạng thái ngủ.

