Try Finally blocks to ensure cleanup and prevent OOM crashes

This commit is contained in:
ca-ke 2026-01-26 20:18:06 -04:00
parent f5c90f0e5c
commit 578e77421a

View File

@ -91,18 +91,36 @@ class CameraCaptureManager(private val context: Context) {
val (bytes, orientation) = capture.takeJpegWithExif(context.mainExecutor()) val (bytes, orientation) = capture.takeJpegWithExif(context.mainExecutor())
val decoded = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) val decoded = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
?: throw IllegalStateException("UNAVAILABLE: failed to decode captured image") ?: throw IllegalStateException("UNAVAILABLE: failed to decode captured image")
val rotated = rotateBitmapByExif(decoded, orientation)
val scaled = // Wrap bitmap operations in try-finally to guarantee cleanup on all paths
val rotated = try {
rotateBitmapByExif(decoded, orientation)
} catch (e: Exception) {
decoded.recycle()
throw e
}
// Note: if rotation was applied, decoded is already recycled; otherwise rotated === decoded
val scaled: Bitmap
val recycleRotatedSeparately: Boolean
try {
if (maxWidth != null && maxWidth > 0 && rotated.width > maxWidth) { if (maxWidth != null && maxWidth > 0 && rotated.width > maxWidth) {
val h = val h =
(rotated.height.toDouble() * (maxWidth.toDouble() / rotated.width.toDouble())) (rotated.height.toDouble() * (maxWidth.toDouble() / rotated.width.toDouble()))
.toInt() .toInt()
.coerceAtLeast(1) .coerceAtLeast(1)
rotated.scale(maxWidth, h) scaled = rotated.scale(maxWidth, h)
recycleRotatedSeparately = true
} else { } else {
rotated scaled = rotated
recycleRotatedSeparately = false
}
} catch (e: Exception) {
rotated.recycle()
throw e
} }
try {
val maxPayloadBytes = 5 * 1024 * 1024 val maxPayloadBytes = 5 * 1024 * 1024
// Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit). // Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
val maxEncodedBytes = (maxPayloadBytes / 4) * 3 val maxEncodedBytes = (maxPayloadBytes / 4) * 3
@ -134,6 +152,12 @@ class CameraCaptureManager(private val context: Context) {
Payload( Payload(
"""{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""", """{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""",
) )
} finally {
scaled.recycle()
if (recycleRotatedSeparately) {
rotated.recycle()
}
}
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")