From d1314a1b32fc5a8ae427f355a03f4601d88439f7 Mon Sep 17 00:00:00 2001 From: PoliEcho Date: Wed, 28 Jan 2026 21:55:22 +0100 Subject: [PATCH] fix ANR --- .../pupes/mhdrunpathfinder/MainActivity.kt | 93 +++++++++++++------ 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt b/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt index 8ed6d06..776d847 100644 --- a/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt +++ b/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt @@ -50,8 +50,14 @@ import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.net.URL +import kotlinx.coroutines.* +import java.util.concurrent.Executors +import android.os.StrictMode +// Thread pool for tile downloading to prevent blocking UI thread +private val tileDownloadExecutor = Executors.newFixedThreadPool(4) + // MapState configuration for OpenStreetMap // Max zoom level is 18, tile size is 256x256 // At zoom level 18, there are 2^18 = 262144 tiles in each dimension @@ -67,6 +73,7 @@ val mapState = MapState( * Creates a cached tile stream provider for OpenStreetMap tiles * Tiles are cached in the app's cache directory for offline access and faster loading * Cache expires after 30 days to ensure map updates are fetched + * Uses background thread pool to prevent ANR (Application Not Responding) */ fun createCachedTileStreamProvider(context: Context, cacheExpiryDays: Int = 30): TileStreamProvider { val cacheDir = File(context.cacheDir, "map_tiles") @@ -89,40 +96,57 @@ fun createCachedTileStreamProvider(context: Context, cacheExpiryDays: Int = 30): // Use cached tile FileInputStream(tileFile) } else { - // Download tile from OpenStreetMap (either new or expired) - val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png") - val connection = url.openConnection() - // Set User-Agent header as required by OSM tile usage policy - val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)" - connection.setRequestProperty("User-Agent", userAgent) + // Download tile from OpenStreetMap in background thread + val future = tileDownloadExecutor.submit { + try { + val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png") + val connection = url.openConnection() + // Set User-Agent header as required by OSM tile usage policy + val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)" + connection.setRequestProperty("User-Agent", userAgent) + connection.connectTimeout = 5000 // 5 second timeout + connection.readTimeout = 10000 // 10 second timeout - val inputStream = connection.getInputStream() + val inputStream = connection.getInputStream() - // Save to cache (overwrites if expired) - val outputStream = FileOutputStream(tileFile) - inputStream.use { input -> - outputStream.use { output -> - input.copyTo(output) + // Save to cache (overwrites if expired) + val outputStream = FileOutputStream(tileFile) + inputStream.use { input -> + outputStream.use { output -> + input.copyTo(output) + } + } + + // Return the cached file's input stream + FileInputStream(tileFile) + } catch (e: Exception) { + e.printStackTrace() + null } } - // Return the cached file's input stream - FileInputStream(tileFile) + // Wait for download with timeout + try { + future.get() ?: run { + // If download failed but we have a cached tile (even if expired), use it + if (tileFile.exists()) { + FileInputStream(tileFile) + } else { + null + } + } + } catch (e: Exception) { + // If download timed out or failed, try using expired cache + if (tileFile.exists()) { + FileInputStream(tileFile) + } else { + null + } + } } } catch (e: Exception) { - // If download fails but we have a cached tile (even if expired), use it - val tileFile = File(cacheDir, "tile_${zoomLvl}_${col}_${row}.png") - if (tileFile.exists()) { - try { - FileInputStream(tileFile) - } catch (fallbackError: Exception) { - e.printStackTrace() - null - } - } else { - e.printStackTrace() - null - } + e.printStackTrace() + null } } } @@ -132,6 +156,21 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + // Configure StrictMode to detect threading violations (debug mode only) + if (BuildConfig.DEBUG) { + StrictMode.setThreadPolicy( + StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() // Log violations instead of crashing + .build() + ) + StrictMode.setVmPolicy( + StrictMode.VmPolicy.Builder() + .detectAll() + .penaltyLog() + .build() + ) + } setContent { MaterialTheme {