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.
This commit is contained in:
greenart7c3
2026-08-19 11:55:14 -03:00
parent 33221752e2
commit abcf2f7703
2 changed files with 25 additions and 3 deletions
@@ -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() {
@@ -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)
}
}