Harden the thumbnail path: versioned cache keys, the icon as the placeholder, and a decided cache

Three things, each a failure the thumbnail path could have caused. First, correctness: Coil keys caches by the request, and a request built from a File is keyed by its path, so a picture replaced or edited outside the app kept showing its previous self - in a file manager that reads as data loss. core/ThumbnailKey now carries path, size and modification time, is built from the numbers the listing already has (no stat, so it stays off the main thread's disk work), and has four tests that name the two ways a file changes. The request carries it as the memory and disk cache key.

Second, the placeholder: the row drew an empty square while a thumbnail decoded, which a thousand-row folder makes very visible. The kind icon is now drawn first and the picture paints over it, so every row shows what it is immediately and the thumbnail replaces it without a flicker.

Third, the cache is decided rather than inherited: memory only, at 15% of the heap, and no disk cache at all - the sources are local files that decode cheaply, and a disk copy is a second copy that survives the file changing. Coil's default would have kept up to 2% of free space of stale thumbnails.

Measured on the phone and recorded in EDGE_CASE_TESTING: a 6200-file folder holding 300 real JPEGs opened and scrolled five times with no crash, no OOM and text rows keeping their icons (PSS 86.6 MB), and a folder of 300 real JPEGs where every row drew its own picture and eight swipes down and one back left PSS at 149.6 MB with the cache full and stable. Stated as not measured: jank on this screen (this device reports no frame durations) and a 20000-image folder.
This commit is contained in:
2026-10-17 18:29:59 +02:00
parent 4846454645
commit 1371407435
7 changed files with 178 additions and 26 deletions
+31
View File
@@ -256,6 +256,37 @@ Deliberately not done on the device: a favourite whose folder disappears. The ca
JVM tests, and staging it would have meant deleting a folder on someone else's phone; the missing
row's wording and the message are the same strings those tests assert.
## The thumbnail hardening session
The one screen that had never been pushed past what a photo album happens to contain: a folder that is
mostly *not* pictures, next to a folder that is nothing but pictures. Both built on the phone, both driven
by hand through `adb`.
| pass | result |
|---|---|
| **6 200 files, 300 of them real JPEGs** (`AAThumbs`) — opened and scrolled five times | **Correct and stable.** No crash, no `OutOfMemoryError`, no decode failure that left an empty square; text rows kept their document icon and the JPEGs further down would have had their frames. PSS 86.6 MB while scrolling the text rows. |
| **300 real JPEGs** (`AAImages`) — opened, scrolled eight swipes down and one back | **Correct.** Every row drew its own picture, not an icon and not a neighbour's picture — 300 copies of one photo, so a cache-key collision would have been invisible here and is covered by `ThumbnailKeyTest` instead. No crash, no OOM. PSS grew 83.9 MB → 149.6 MB as the memory cache filled, then stayed. |
Two things that pass is *not* evidence of:
- **It is not a jank measurement.** This device reports no frame durations at all (`docs/PERFORMANCE.md`),
so "it scrolled" is all that can be said here. The emulator path reports `frameDurationCpuMs`, so the
number that would matter can be taken there or on a newer phone.
- **It is not a 20 000 *image* folder.** The 6 200-file folder held 300 images, and 300 is where the memory
cache tops out. A folder of 20 000 pictures is the case that would test the cache bound rather than the
listing, and it is not staged — writing 20 000 files through MIUI's storage layer took five minutes for
6 000, so it is a fixture to build once and keep, not something to improvise.
What the pass did change, before it was run:
- thumbnails are keyed by path **and size and modification time** (`core/ThumbnailKey`), so a file that is
replaced or edited outside the app re-decodes instead of showing the previous picture — the one failure
a cache can cause that a user would mistake for data loss;
- the icon is drawn *under* the thumbnail rather than the row showing an empty square while decoding;
- the image loader's caches are decided explicitly: **memory only**, at 15 % of the heap, and no disk
cache — a disk cache would hold a second copy of pictures that are already on the device, and survive
the file changing.
## Not verified, and why
- **Long-press multi-select via adb: it works, but not every time.** `input swipe x y x y 900`