fix(flutter): Release replay JNI refs#3699
Conversation
Release Android replay JNI references when worker isolates shut down and when replay integration handles are replaced. This prevents replay bitmap/config/native replay references from being retained after use. Fixes GH-3633 Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Semver Impact of This PR🟢 Patch (bug fixes) 📋 Changelog PreviewThis is how your changes will appear in the changelog. Fixes
🤖 This preview updates automatically when you update the PR. |
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3699 +/- ##
==========================================
- Coverage 86.96% 86.94% -0.03%
==========================================
Files 335 335
Lines 11976 11977 +1
==========================================
- Hits 10415 10413 -2
- Misses 1561 1564 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Android Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| bfabaf2 | 408.04 ms | 444.38 ms | 36.34 ms |
| fec56a1 | 370.66 ms | 369.28 ms | -1.38 ms |
| 8541716 | 437.14 ms | 443.65 ms | 6.51 ms |
| 0fb45d0 | 482.79 ms | 554.02 ms | 71.23 ms |
| 0fb3800 | 465.64 ms | 536.77 ms | 71.13 ms |
| e04b24b | 504.72 ms | 516.43 ms | 11.71 ms |
| 944b773 | 470.54 ms | 480.18 ms | 9.64 ms |
| cdf371b | 367.64 ms | 377.02 ms | 9.38 ms |
| 0bea8d9 | 389.45 ms | 394.96 ms | 5.50 ms |
| 5b9a0da | 446.94 ms | 449.22 ms | 2.28 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| bfabaf2 | 13.93 MiB | 15.06 MiB | 1.13 MiB |
| fec56a1 | 14.31 MiB | 15.49 MiB | 1.19 MiB |
| 8541716 | 13.93 MiB | 15.00 MiB | 1.06 MiB |
| 0fb45d0 | 6.54 MiB | 7.70 MiB | 1.17 MiB |
| 0fb3800 | 6.54 MiB | 7.69 MiB | 1.15 MiB |
| e04b24b | 13.93 MiB | 15.00 MiB | 1.06 MiB |
| 944b773 | 13.93 MiB | 15.00 MiB | 1.06 MiB |
| cdf371b | 13.93 MiB | 15.18 MiB | 1.25 MiB |
| 0bea8d9 | 14.31 MiB | 15.56 MiB | 1.25 MiB |
| 5b9a0da | 13.93 MiB | 14.93 MiB | 1.00 MiB |
Previous results on branch: fix/android-replay-jni-leaks-clean
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 62ede5d | 387.53 ms | 391.07 ms | 3.53 ms |
| 99068d4 | 365.83 ms | 363.24 ms | -2.59 ms |
| 4a2cb99 | 398.11 ms | 387.70 ms | -10.41 ms |
| 3fd6cbd | 379.27 ms | 364.69 ms | -14.58 ms |
| e3c9090 | 364.16 ms | 353.79 ms | -10.37 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 62ede5d | 14.31 MiB | 15.56 MiB | 1.25 MiB |
| 99068d4 | 14.31 MiB | 15.56 MiB | 1.25 MiB |
| 4a2cb99 | 14.31 MiB | 15.56 MiB | 1.25 MiB |
| 3fd6cbd | 14.31 MiB | 15.56 MiB | 1.25 MiB |
| e3c9090 | 14.31 MiB | 15.56 MiB | 1.25 MiB |
iOS Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1adf565 | 1264.51 ms | 1265.22 ms | 0.71 ms |
| d789735 | 1240.58 ms | 1246.41 ms | 5.82 ms |
| 2f63d89 | 1251.67 ms | 1263.94 ms | 12.27 ms |
| cdf371b | 1246.24 ms | 1251.10 ms | 4.86 ms |
| fd88186 | 1255.06 ms | 1252.76 ms | -2.30 ms |
| ce5c42b | 1246.06 ms | 1251.91 ms | 5.85 ms |
| e5ae2a6 | 1240.48 ms | 1253.26 ms | 12.78 ms |
| 944b773 | 1252.82 ms | 1254.08 ms | 1.27 ms |
| 73dca78 | 1246.65 ms | 1265.42 ms | 18.76 ms |
| 7cfbbd6 | 1270.63 ms | 1285.36 ms | 14.72 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1adf565 | 5.66 MiB | 6.10 MiB | 451.33 KiB |
| d789735 | 5.53 MiB | 5.96 MiB | 443.28 KiB |
| 2f63d89 | 5.65 MiB | 6.09 MiB | 446.25 KiB |
| cdf371b | 5.53 MiB | 6.02 MiB | 501.23 KiB |
| fd88186 | 5.53 MiB | 6.00 MiB | 479.94 KiB |
| ce5c42b | 5.73 MiB | 6.17 MiB | 455.86 KiB |
| e5ae2a6 | 5.65 MiB | 6.09 MiB | 446.96 KiB |
| 944b773 | 5.53 MiB | 6.00 MiB | 479.98 KiB |
| 73dca78 | 7.86 MiB | 9.44 MiB | 1.58 MiB |
| 7cfbbd6 | 7.86 MiB | 9.44 MiB | 1.58 MiB |
Previous results on branch: fix/android-replay-jni-leaks-clean
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 4a2cb99 | 1241.93 ms | 1242.60 ms | 0.66 ms |
| 3fd6cbd | 1253.15 ms | 1252.27 ms | -0.88 ms |
| 62ede5d | 1247.19 ms | 1248.71 ms | 1.53 ms |
| 99068d4 | 1231.61 ms | 1241.43 ms | 9.82 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 4a2cb99 | 5.73 MiB | 6.18 MiB | 463.51 KiB |
| 3fd6cbd | 5.73 MiB | 6.18 MiB | 463.52 KiB |
| 62ede5d | 5.73 MiB | 6.18 MiB | 463.22 KiB |
| 99068d4 | 5.73 MiB | 6.18 MiB | 463.52 KiB |
There was a problem hiding this comment.
Pull request overview
This PR addresses leaked JNI references in the Flutter Android Replay screenshot pipeline by introducing an isolate-side shutdown hook and ensuring replay-related JNI objects are released when replaced or when the worker isolate shuts down.
Changes:
- Added a
WorkerHandler.close()lifecycle hook and invoked it during worker isolate shutdown. - Released JNI references in the Android replay screenshot handler (cached
Bitmap,ReplayIntegration, and temporaryBitmap$Config.ARGB_8888). - Ensured
ReplayIntegrationhandles are released when replaced/cleared in the Java native channel, and added a test validating handler close is called before shutdown.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/flutter/lib/src/isolate/isolate_worker.dart | Adds handler shutdown hook and calls it on isolate shutdown. |
| packages/flutter/lib/src/native/java/android_replay_recorder.dart | Releases JNI refs for bitmap/config and replay integration on shutdown. |
| packages/flutter/lib/src/native/java/sentry_native_java.dart | Centralizes replay handle replacement to release previous JNI refs. |
| packages/flutter/lib/src/native/java/sentry_native_java_init.dart | Uses the new replay handle setter and avoids re-reading VERSION_NAME. |
| packages/flutter/test/isolate/isolate_worker_test.dart | Adds coverage ensuring close() is invoked before isolate shutdown. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Apply formatter output to touched Dart files so the branch stays clean. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the isolate worker shutdown behavior unchanged and release the replay integration JNI handle around screenshot recording instead of adding a generic worker lifecycle callback. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Route Android replay and native Java diagnostics through the shared internal logger instead of the options logging shim. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Create replay bitmaps per capture and release their JNI references after recording so the replay worker does not retain bitmap handles. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Release the bitmap config reference in the same cleanup block as the other per-capture JNI references. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Check the generated nullable bitmap creation result before using it so replay capture avoids a forced unwrap while preserving cleanup of JNI references. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the replay recorder local cleanup variables in use order. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the Android replay bitmap cached between same-size captures, but release it through a replay-specific worker request before the worker shuts down. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Reuse the worker-side replay integration handle during Android replay capture and release it with the cached bitmap through the replay cleanup request. Co-Authored-By: GPT-5.5 <noreply@openai.com> Co-authored-by: Cursor <cursoragent@cursor.com>
📜 Description
Release JNI references held by the Android replay screenshot path without changing the isolate worker shutdown behavior.
The replay worker now releases the temporary
Bitmap$Config.ARGB_8888reference, the per-captureBitmapreference, and the isolate-side replay integration handle after recording each screenshot. The native Java owner also releases replay integration handles before replacing or clearing them.💡 Motivation and Context
Fixes #3633.
The issue reports leaked JNI references in the Android replay path. This keeps the fix scoped to JNI handle ownership instead of adding a generic worker lifecycle hook.
💚 How did you test it?
fvm flutter analyze lib/src/native/java/android_replay_recorder.dartfvm flutter analyze lib/src/native/java/android_replay_recorder.dart lib/src/native/java/sentry_native_java.dart lib/src/native/java/sentry_native_java_init.dartfvm flutter test test/isolate/isolate_worker_test.dartflutter analyze --fatal-warningsforsentry_fluttersuccessfully.📝 Checklist
sendDefaultPiiis enabled🔮 Next steps