Try Finally blocks to ensure cleanup and prevent OOM crashes
This commit is contained in:
parent
f5c90f0e5c
commit
578e77421a
@ -91,49 +91,73 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
val maxPayloadBytes = 5 * 1024 * 1024
|
try {
|
||||||
// Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
|
val maxPayloadBytes = 5 * 1024 * 1024
|
||||||
val maxEncodedBytes = (maxPayloadBytes / 4) * 3
|
// Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
|
||||||
val result =
|
val maxEncodedBytes = (maxPayloadBytes / 4) * 3
|
||||||
JpegSizeLimiter.compressToLimit(
|
val result =
|
||||||
initialWidth = scaled.width,
|
JpegSizeLimiter.compressToLimit(
|
||||||
initialHeight = scaled.height,
|
initialWidth = scaled.width,
|
||||||
startQuality = (quality * 100.0).roundToInt().coerceIn(10, 100),
|
initialHeight = scaled.height,
|
||||||
maxBytes = maxEncodedBytes,
|
startQuality = (quality * 100.0).roundToInt().coerceIn(10, 100),
|
||||||
encode = { width, height, q ->
|
maxBytes = maxEncodedBytes,
|
||||||
val bitmap =
|
encode = { width, height, q ->
|
||||||
if (width == scaled.width && height == scaled.height) {
|
val bitmap =
|
||||||
scaled
|
if (width == scaled.width && height == scaled.height) {
|
||||||
} else {
|
scaled
|
||||||
scaled.scale(width, height)
|
} else {
|
||||||
|
scaled.scale(width, height)
|
||||||
|
}
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, q, out)) {
|
||||||
|
if (bitmap !== scaled) bitmap.recycle()
|
||||||
|
throw IllegalStateException("UNAVAILABLE: failed to encode JPEG")
|
||||||
}
|
}
|
||||||
val out = ByteArrayOutputStream()
|
if (bitmap !== scaled) {
|
||||||
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, q, out)) {
|
bitmap.recycle()
|
||||||
if (bitmap !== scaled) bitmap.recycle()
|
}
|
||||||
throw IllegalStateException("UNAVAILABLE: failed to encode JPEG")
|
out.toByteArray()
|
||||||
}
|
},
|
||||||
if (bitmap !== scaled) {
|
)
|
||||||
bitmap.recycle()
|
val base64 = Base64.encodeToString(result.bytes, Base64.NO_WRAP)
|
||||||
}
|
Payload(
|
||||||
out.toByteArray()
|
"""{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""",
|
||||||
},
|
|
||||||
)
|
)
|
||||||
val base64 = Base64.encodeToString(result.bytes, Base64.NO_WRAP)
|
} finally {
|
||||||
Payload(
|
scaled.recycle()
|
||||||
"""{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""",
|
if (recycleRotatedSeparately) {
|
||||||
)
|
rotated.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user