🔄 Upstream Parity: Fix Bitmap memory leaks in CanvasController snapshots (#412)

Wraps snapshotPngBase64/snapshotBase64 in nested try/finally blocks to ensure Bitmap.recycle() is always called, preventing native memory leaks on each snapshot invocation.
This commit is contained in:
Yu-ga
2026-04-05 21:41:49 +09:00
committed by GitHub
parent dcd74fbe09
commit 9b308b3506
2 changed files with 63 additions and 24 deletions
@@ -236,39 +236,55 @@ class CanvasController {
withContext(Dispatchers.Main) {
val wv = webView ?: throw IllegalStateException("no webview")
val bmp = wv.captureBitmap()
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
bmp.scale(maxWidth, h)
} else {
bmp
}
try {
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
bmp.scale(maxWidth, h)
} else {
bmp
}
val out = ByteArrayOutputStream()
scaled.compress(Bitmap.CompressFormat.PNG, 100, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
try {
val out = ByteArrayOutputStream()
scaled.compress(Bitmap.CompressFormat.PNG, 100, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
} finally {
if (scaled !== bmp) scaled.recycle()
}
} finally {
bmp.recycle()
}
}
suspend fun snapshotBase64(format: SnapshotFormat, quality: Double?, maxWidth: Int?): String =
withContext(Dispatchers.Main) {
val wv = webView ?: throw IllegalStateException("no webview")
val bmp = wv.captureBitmap()
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
bmp.scale(maxWidth, h)
} else {
bmp
}
try {
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
bmp.scale(maxWidth, h)
} else {
bmp
}
val out = ByteArrayOutputStream()
val (compressFormat, compressQuality) =
when (format) {
SnapshotFormat.Png -> Bitmap.CompressFormat.PNG to 100
SnapshotFormat.Jpeg -> Bitmap.CompressFormat.JPEG to clampJpegQuality(quality)
try {
val out = ByteArrayOutputStream()
val (compressFormat, compressQuality) =
when (format) {
SnapshotFormat.Png -> Bitmap.CompressFormat.PNG to 100
SnapshotFormat.Jpeg -> Bitmap.CompressFormat.JPEG to clampJpegQuality(quality)
}
scaled.compress(compressFormat, compressQuality, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
} finally {
if (scaled !== bmp) scaled.recycle()
}
scaled.compress(compressFormat, compressQuality, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
} finally {
bmp.recycle()
}
}
private suspend fun WebView.captureBitmap(): Bitmap =
+23
View File
@@ -0,0 +1,23 @@
1. **Explore the upstream repo**
- Use `git clone` or fetch to investigate recent commits in `apps/android`.
- Find candidate commits for porting (e.g., bug fixes, memory leaks).
- *Exploration complete*.
2. **Choose a suitable improvement**
- Identified upstream commit `2909d8cd12` ("Android: fix Bitmap memory leaks in CanvasController snapshots").
- This prevents memory leaks caused by not recycling Bitmaps (`bmp` and `scaled`) during `snapshotPngBase64()` and `snapshotBase64()` in `CanvasController.kt`.
- Verified that `CanvasController.kt` in this repo has the exact same leak.
3. **Port the improvement**
- Modify `app/src/main/java/com/openclaw/assistant/node/CanvasController.kt`.
- Update `snapshotPngBase64()` and `snapshotBase64()` to wrap operations in `try...finally` blocks, ensuring `bmp` and `scaled` are correctly recycled if they differ.
- Use `replace_with_git_merge_diff` to modify the file.
4. **Run Native Validation Commands**
- Run `app:lintStandardDebug` and `app:testStandardDebugUnitTest` with the `./gradlew` command to verify compilation and prevent regressions.
5. **Complete pre-commit steps to ensure proper testing, verification, review, and reflection are done.**
- Run `pre_commit_instructions`.
6. **Submit PR**
- Use the `submit` tool to create a PR named `🔄 Upstream Parity: Fix Bitmap memory leaks in CanvasController snapshots` detailing the source, why, adaptation, and verification.