From abcf2f770372146ad537aa076a737d993f4d0f9d Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 19 Aug 2026 11:55:14 -0300 Subject: [PATCH] Warm up tzdata and cache DateTimeFormatters off the main thread The first ZoneId.systemDefault() call lazily mmaps the tzdata file (ZoneInfoDb class init), which tripped StrictMode DiskReadViolation (~169 ms) on the main thread during LazyColumn composition in ApplicationsScreen. Mirror the Coil ImageLoader pre-warm: force the load on applicationIOScope in Amber.onCreate, and cache the two DateTimeFormatter instances instead of re-parsing the pattern per item. --- .../java/com/greenart7c3/nostrsigner/Amber.kt | 8 ++++++++ .../nostrsigner/models/TimeUtils.kt | 20 ++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt b/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt index 3ae554ca..9dd0f868 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/Amber.kt @@ -40,6 +40,7 @@ import com.greenart7c3.nostrsigner.database.LogDatabase import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.AmberSettings import com.greenart7c3.nostrsigner.models.FeedbackType +import com.greenart7c3.nostrsigner.models.TimeUtils as AmberTimeUtils import com.greenart7c3.nostrsigner.models.TorMode import com.greenart7c3.nostrsigner.models.UpdateCheckFrequency import com.greenart7c3.nostrsigner.okhttp.HttpClientManager @@ -354,6 +355,13 @@ class Amber : applicationIOScope.launch { SingletonImageLoader.get(this@Amber) } + + // The first ZoneId.systemDefault() call lazily mmaps tzdata from disk. + // Force it here so it never happens on the main thread during the first + // timestamp formatted in composition (StrictMode DiskReadViolation). + applicationIOScope.launch { + AmberTimeUtils.warmUp() + } } private fun enableStrictMode() { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt index 9d26fe92..c0422f56 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/TimeUtils.kt @@ -6,15 +6,29 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter object TimeUtils { + // DateTimeFormatter is immutable and thread-safe; building it parses the + // pattern on every call, so cache the two instances used during composition. + private val formatter = DateTimeFormatter.ofPattern("HH:mm - dd MMM") + private val formatterWithSeconds = DateTimeFormatter.ofPattern("HH:mm:ss - dd MMM") + + /** + * The first ZoneId.systemDefault() call in the process lazily loads the + * tzdata file from disk (ZoneInfoDb's class initializer). Calling this from + * a background thread at app start keeps that disk read off the main + * thread, where it would otherwise trip StrictMode and jank the frame that + * first formats a timestamp. + */ + fun warmUp() { + LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneId.systemDefault()).format(formatter) + } + fun formatLongToCustomDateTime(longValue: Long): String { val dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(longValue), ZoneId.systemDefault()) - val formatter = DateTimeFormatter.ofPattern("HH:mm - dd MMM") return dateTime.format(formatter) } fun formatLongToCustomDateTimeWithSeconds(longValue: Long): String { val dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(longValue), ZoneId.systemDefault()) - val formatter = DateTimeFormatter.ofPattern("HH:mm:ss - dd MMM") - return dateTime.format(formatter) + return dateTime.format(formatterWithSeconds) } }