Skip to content

Replace DiskLruCache with a small built-in tile cache - #58

Merged
afarber merged 2 commits into
mainfrom
replace-disklrucache
Oct 14, 2026
Merged

afarber merged 2 commits into
mainfrom
replace-disklrucache

Conversation

@afarber

@afarber afarber commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Fixes #57

DiskLruCache 2.0.2 is from 2013 and archived. This replaces it with DiskTileCache writing one file per tile (cacheDir/tiles2/<source>/<z>_<x>_<y>.tile), which also fixes how we used it.

Disk cache

  • Files hold a small header (format version, download time) and the tile bytes as sent by the server. No more decode and re-encode as PNG
  • Disk hits are decoded with RGB_565, like downloads. Before they came back as ARGB_8888 with twice the memory
  • 50MB limit, the least recently used tiles (by file modification time) are deleted down to 45MB
  • Writes go to a temp file that is renamed, leftovers are cleaned up on the first scan
  • The old cache/tiles directory of DiskLruCache is deleted on first use

Expiry and backoff

  • Tiles older than 7 days are downloaded again. The download time is in the file header, so reading a tile doesn't make it younger
  • If that download fails (offline, server error), the expired tile is shown instead and counts as a failure for TileRetryTracker, so it's retried with the existing 2s to 60s backoff and replaced once the network is back
  • A tile that is missing on disk and fails to download goes through the same backoff as before

MapController

  • The disk lookup no longer waits for one of the 4 download permits

TileCache.put(tile, bitmap) and DiskTileCache.put(tile, bitmap) still work and encode PNG, but the download path uses the new overloads with the original bytes.

Tests: 18 new tests (DiskTileCacheTest, TileCacheTest and a new MapControllerTileLoadingTest that drives the real download path with a mocked server and a fake clock). 479 tests pass, coverage 82%.

I couldn't do the manual offline check on an emulator, because TLS to tile.openstreetmap.org fails on my machine's network (on main too). The loading order is covered by MapControllerTileLoadingTest instead.

- Store one file per tile with the bytes as sent by the server,
  instead of re-encoding every tile as PNG
- Decode disk hits with RGB_565 like downloads, halving their memory
- Expire tiles after 7 days, but show the old tile when the download
  fails, retried with the existing backoff
- Look up the disk cache before taking a download permit
- Write through a temp file and rename, trim least recently used tiles
- Delete the old DiskLruCache directory on first use
- Remove the com.jakewharton:disklrucache dependency

Fixes #57
@afarber

afarber commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner Author

4 findings on commit 500606c

openmapview/src/main/kotlin/de/afarber/openmapview/MapController.kt:1338-1356

prefetchAdjacentTiles() never retries a stale (expired) tile sitting outside the visible viewport because its guard checks tileCache.getMemory(tile) == null instead of tileCache.isStale(tile)

openmapview/src/main/kotlin/de/afarber/openmapview/TileCache.kt:49-96

staleTiles set added for the new offline-fallback tracking is never pruned when memoryCache (an LruCache) evicts the same tile, so it grows without bound

openmapview/src/main/kotlin/de/afarber/openmapview/MapController.kt:1853

disk cache lookup (cache.get(tile)) now runs unconditionally before the cache !== tileCache stale-source check that used to guard it inside the semaphore

openmapview/src/main/kotlin/de/afarber/openmapview/DiskTileCache.kt:139-153

Store.trim() does a full recursive directory walk + sort while holding the single shared Store lock, run synchronously inside every put() once the cache is near its limit

- Prefetch now also downloads expired fallback tiles again, through
  needsDownload() shared with the visible tiles
- Forget the stale mark of a tile when the memory cache evicts it
- Check for a changed tile source before reading the disk cache
@afarber

afarber commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

Fixed 1 to 3 in f36f2a5. Prefetch and visible tiles now share needsDownload(), the stale mark is dropped on LRU eviction, and the source check runs before the disk read.

Leaving 4 as is: trim() returns right away unless the cache is over 50MB, and then deletes down to 45MB, so the full scan runs about once per 5MB of new tiles, not on every put. Keeping a sorted index instead would bring back the journal we just removed.

@afarber
afarber merged commit f017514 into main Oct 14, 2026
14 checks passed
@afarber
afarber deleted the replace-disklrucache branch October 14, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace DiskLruCache with a small built-in tile cache

1 participant