Skip to content

perf(flutter): Move Android JNI work to core worker to avoid work on main isolate#3713

Merged
buenaflor merged 29 commits into
mainfrom
buenaflor/ref/android-core-worker-fire-forget
May 26, 2026
Merged

perf(flutter): Move Android JNI work to core worker to avoid work on main isolate#3713
buenaflor merged 29 commits into
mainfrom
buenaflor/ref/android-core-worker-fire-forget

Conversation

@buenaflor
Copy link
Copy Markdown
Contributor

@buenaflor buenaflor commented May 19, 2026

📜 Description

Moves the Android envelope sender into a broader AndroidCoreWorker that can also handle native debug images, native contexts, breadcrumbs, users, and context updates. Value-returning APIs use worker request/response when available

Now running on background isolate (also confirmed these are safe to be called from non-main threads):

  • loadContexts
  • loadDebugImages
  • setContexts
  • removeContexts
  • addBreadcrumb
  • setUser

💡 Motivation and Context

This keeps more Android JNI work off the main isolate when the worker is available, while preserving current-isolate fallbacks for calls made before worker startup. The worker serializes JNI work to keep native calls ordered.

Relevant #2440, #3348
Fixes #3536

💚 How did you test it?

  • ../../.fvm/flutter_sdk/bin/flutter analyze lib/src/native/java/android_core_worker.dart lib/src/native/java/sentry_native_java.dart test/native/android_core_worker_test_real.dart test/native/sentry_native_java_test_real.dart
  • ../../.fvm/flutter_sdk/bin/flutter test test/native/android_core_worker_test_real.dart test/native/sentry_native_java_test_real.dart
  • Commit hook ran melos exec -- dart analyze --fatal-warnings and melos exec -- flutter analyze --fatal-warnings

📝 Checklist

  • I reviewed submitted code
  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPii is enabled
  • I updated the docs if needed
  • All tests passing
  • No breaking changes

🔮 Next steps

None.

Made with Cursor

buenaflor and others added 16 commits May 19, 2026 11:05
Send large Android scope payloads as JSON bytes instead of recursively
constructing Java maps and lists through JNI. This keeps nested user data
structured while reducing per-entry JNI object churn.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Clarify that large or arbitrary Dart collection payloads should cross
JNI as JSON bytes, while primitives and small controlled payloads can use
direct conversion.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the Android JSON reader helper within ktlint formatting limits after
adding the scope sync byte-array bridge.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Wrap all native JSON conversion when branches consistently so ktlint accepts
the multiline Kotlin helper added for Android scope sync.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Use the existing native boundary normalizer before encoding Android scope
payloads as JSON bytes instead of maintaining a second normalization helper.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep null values when converting JSON object and array payloads on Android
so the bridge remains lossless before native model deserialization.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Cache JSON deserializers for scope sync payloads and replace the broad
Any extension with a private helper function for Kotlin JSON conversion.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Use the Java SDK JSON reader for context payload parsing instead of a
custom recursive org.json conversion helper.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Document that the Android JSON reader accepts root-level primitives
so future changes do not replace it with object-only parsing.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Route Android context values through sentry-java's typed overloads so
primitive Dart context values are serialized as valid context objects.
Regenerate JNI bindings after removing the unused object overload entrypoint.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Release replay callback JNI handles after processing replay privacy
options, and avoid creating duplicate JNI strings when loading debug
images. This reduces leaked global refs in Android replay and debug image
paths.

Fixes #3696
Co-Authored-By: GPT-5.5 <noreply@openai.com>

Co-authored-by: Cursor <cursoragent@cursor.com>
Update the native context sync test to match the Android bridge's
valid serialized shape for primitive context values.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Use a single arena for replay callback JNI temporaries and avoid
releasing map keys before removing privacy options from the payload.

Refs #3696
Co-Authored-By: GPT-5.5 <noreply@openai.com>

Co-authored-by: Cursor <cursoragent@cursor.com>
Route Android native scope and context work through the core worker so
JNI calls can run off the main isolate when the worker is available.
Keep current-isolate fallbacks for calls made before startup.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


Fixes

Dart

  • Make sentryOnError synchronous in runZonedGuarded by theprantadutta in #3697
  • Route SDK diagnostic logs to browser console on web by theprantadutta in #3698

Flutter

  • Release Android JNI refs by buenaflor in #3712
  • Release replay JNI refs by buenaflor in #3699

Enhancements

Flutter

  • Move Android JNI work to core worker to avoid work on main isolate by buenaflor in #3713
  • Optimize Android scope sync by buenaflor in #3708

Internal Changes

  • (feedback) Rename SentryFeedbackWidget to SentryFeedbackForm by denrase in #3702
  • (flutter) Align CI with stable SwiftPM defaults by buenaflor in #3710

🤖 This preview updates automatically when you update the PR.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.57%. Comparing base (9f2a741) to head (73a206c).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3713      +/-   ##
==========================================
+ Coverage   86.96%   91.57%   +4.60%     
==========================================
  Files         336      105     -231     
  Lines       11982     3737    -8245     
==========================================
- Hits        10420     3422    -6998     
+ Misses       1562      315    -1247     
Flag Coverage Δ
sentry ?
sentry_dio ?
sentry_drift ?
sentry_file ?
sentry_firebase_remote_config 100.00% <ø> (ø)
sentry_flutter 91.16% <ø> (-0.38%) ⬇️
sentry_hive ?
sentry_isar ?
sentry_link ?
sentry_logging ?
sentry_sqflite ?
sentry_supabase 97.27% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@buenaflor buenaflor marked this pull request as ready for review May 21, 2026 13:46
@buenaflor buenaflor requested a review from denrase as a code owner May 21, 2026 13:46
Copilot AI review requested due to automatic review settings May 21, 2026 13:46
Base automatically changed from buenaflor/fix/flutter-android-jni-leaks to main May 21, 2026 13:46
@buenaflor buenaflor marked this pull request as draft May 21, 2026 13:49
@buenaflor buenaflor changed the title ref(flutter): Move Android JNI work to core worker perf(flutter): Move Android JNI work to core worker to avoid work on main isolate May 21, 2026
Co-Authored-By: OpenAI <noreply@openai.com>
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Moves Android JNI-heavy operations (envelope capture, debug image loading, native contexts, and scope updates) behind a new AndroidCoreWorker that can run on a background isolate when available, reducing main-isolate blocking and preserving synchronous fallbacks before worker startup.

Changes:

  • Replace AndroidEnvelopeSender with a broader AndroidCoreWorker that serializes JNI work in a worker isolate.
  • Delegate SentryNativeJava JNI entrypoints (envelopes, debug images, contexts, breadcrumb/user/context updates) through the worker.
  • Update/remove/replace real VM tests to validate worker request/response and one-way messaging behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/flutter/lib/src/native/java/android_core_worker.dart Introduces the new worker, message/request types, and JNI implementations.
packages/flutter/lib/src/native/java/android_envelope_sender.dart Removes the old dedicated envelope-sender worker implementation.
packages/flutter/lib/src/native/java/sentry_native_java.dart Switches Android JNI calls to delegate to AndroidCoreWorker.
packages/flutter/lib/src/native/java/sentry_native_java_init.dart Adjusts replay callback JNI handle management and masking-option updates.
packages/flutter/test/native/android_core_worker_test.dart Updates conditional import wiring to the new worker test entrypoint.
packages/flutter/test/native/android_core_worker_test_web.dart Updates web “stub” test file references to the new real test file.
packages/flutter/test/native/android_core_worker_test_real.dart Adds VM tests covering worker spawn/config, requests, and one-way updates.
packages/flutter/test/native/android_envelope_sender_test_real.dart Deletes obsolete VM tests for the removed AndroidEnvelopeSender.
packages/flutter/test/native/sentry_native_java_test_real.dart Updates initialization test to use the new AndroidCoreWorker.factory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Handle worker request failures without crashing callers and normalize
payloads before sending them across isolates. Track JNI strings as they
are allocated so partial failures still release native refs.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@buenaflor buenaflor marked this pull request as ready for review May 26, 2026 08:17
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 26, 2026

Android Performance metrics 🚀

  Plain With Sentry Diff
Startup time 361.53 ms 354.20 ms -7.33 ms
Size 14.55 MiB 15.87 MiB 1.31 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
3f47ea3 368.20 ms 388.14 ms 19.94 ms
cdf371b 367.64 ms 377.02 ms 9.38 ms
3615e19 468.38 ms 504.71 ms 36.33 ms
78c3c07 393.32 ms 394.94 ms 1.61 ms
1777727 438.67 ms 447.11 ms 8.44 ms
1f639ee 429.98 ms 476.60 ms 46.62 ms
54acf91 487.24 ms 529.60 ms 42.36 ms
3135a81 424.33 ms 423.63 ms -0.70 ms
1fff351 423.68 ms 408.96 ms -14.72 ms
8af916c 367.83 ms 379.60 ms 11.77 ms

App size

Revision Plain With Sentry Diff
3f47ea3 13.93 MiB 15.18 MiB 1.25 MiB
cdf371b 13.93 MiB 15.18 MiB 1.25 MiB
3615e19 6.54 MiB 7.70 MiB 1.16 MiB
78c3c07 14.30 MiB 15.49 MiB 1.19 MiB
1777727 14.30 MiB 15.49 MiB 1.19 MiB
1f639ee 13.93 MiB 15.00 MiB 1.06 MiB
54acf91 6.54 MiB 7.70 MiB 1.17 MiB
3135a81 14.30 MiB 15.49 MiB 1.19 MiB
1fff351 14.31 MiB 15.49 MiB 1.19 MiB
8af916c 14.31 MiB 15.56 MiB 1.25 MiB

Previous results on branch: buenaflor/ref/android-core-worker-fire-forget

Startup times

Revision Plain With Sentry Diff
5d9efbc 377.98 ms 384.94 ms 6.96 ms
b163866 348.09 ms 354.72 ms 6.63 ms
310effb 366.33 ms 344.30 ms -22.04 ms
099cefb 397.64 ms 404.37 ms 6.72 ms
f4d9e97 348.36 ms 354.15 ms 5.78 ms

App size

Revision Plain With Sentry Diff
5d9efbc 14.55 MiB 15.87 MiB 1.31 MiB
b163866 14.55 MiB 15.87 MiB 1.31 MiB
310effb 14.55 MiB 15.87 MiB 1.31 MiB
099cefb 14.55 MiB 15.87 MiB 1.31 MiB
f4d9e97 14.55 MiB 15.87 MiB 1.31 MiB

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 26, 2026

iOS Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1252.98 ms 1254.53 ms 1.55 ms
Size 5.83 MiB 6.28 MiB 461.83 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
8bfae64 1237.43 ms 1238.94 ms 1.51 ms
114239b 1225.74 ms 1230.17 ms 4.43 ms
d2356d0 1257.04 ms 1257.94 ms 0.89 ms
b6c8720 1252.65 ms 1266.61 ms 13.96 ms
4298701 1243.56 ms 1262.29 ms 18.72 ms
c002f00 1252.47 ms 1258.78 ms 6.31 ms
0265ce5 1261.66 ms 1250.42 ms -11.24 ms
ec78888 1251.37 ms 1269.40 ms 18.04 ms
73dca78 1246.65 ms 1265.42 ms 18.76 ms
c58ce03 1245.18 ms 1247.00 ms 1.82 ms

App size

Revision Plain With Sentry Diff
8bfae64 5.66 MiB 6.10 MiB 451.85 KiB
114239b 5.53 MiB 5.96 MiB 444.85 KiB
d2356d0 5.66 MiB 6.09 MiB 448.38 KiB
b6c8720 7.86 MiB 9.44 MiB 1.58 MiB
4298701 20.70 MiB 22.46 MiB 1.76 MiB
c002f00 5.65 MiB 6.09 MiB 448.38 KiB
0265ce5 5.66 MiB 6.09 MiB 448.36 KiB
ec78888 7.86 MiB 9.44 MiB 1.58 MiB
73dca78 7.86 MiB 9.44 MiB 1.58 MiB
c58ce03 5.73 MiB 6.17 MiB 455.86 KiB

Previous results on branch: buenaflor/ref/android-core-worker-fire-forget

Startup times

Revision Plain With Sentry Diff
310effb 1241.37 ms 1244.26 ms 2.89 ms
5d9efbc 1246.83 ms 1248.34 ms 1.51 ms
b163866 1243.33 ms 1253.69 ms 10.36 ms
099cefb 1247.62 ms 1257.91 ms 10.29 ms
f4d9e97 1250.96 ms 1248.98 ms -1.98 ms

App size

Revision Plain With Sentry Diff
310effb 5.83 MiB 6.28 MiB 462.24 KiB
5d9efbc 5.83 MiB 6.28 MiB 461.83 KiB
b163866 5.83 MiB 6.28 MiB 461.83 KiB
099cefb 5.83 MiB 6.28 MiB 461.84 KiB
f4d9e97 5.83 MiB 6.28 MiB 462.24 KiB

Comment thread packages/flutter/lib/src/native/java/sentry_native_java.dart Outdated
Return worker request futures for Android scope updates so awaited scope
observer calls complete after native scope synchronization finishes.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f4d9e97. Configure here.

Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/sentry_native_java.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/sentry_native_java.dart
Route paired Android scope mutations through the same worker queue so
call order is preserved across breadcrumb and context updates. Handle
worker startup failures without delaying native SDK initialization.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment on lines +41 to +43
if (_startFuture != null) return _startFuture;
_startFuture = _start();
return _startFuture;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: _startFuture deduplicates concurrent start() calls so multiple callers share a single in-flight spawn rather than racing to create multiple workers.

buenaflor and others added 2 commits May 26, 2026 11:47
Return null for Android core worker read paths after shutdown so closed
workers do not fall back to JNI work on the main isolate.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Apply formatting to the Android core worker and clarify that its
internal queue preserves JNI request order.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
buenaflor and others added 2 commits May 26, 2026 11:58
Use explicit worker request helpers for each Android scope mutation so
request construction and error logging stay local to each operation.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Route Android core worker scope payloads through the shared normalizer
to avoid broadening behavior in this refactor.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Collaborator

@denrase denrase left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could try to move more work into the worker, if it makes sense. Otherwise looking good.

Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart
Comment thread packages/flutter/lib/src/native/java/android_core_worker.dart Outdated
Comment thread packages/flutter/lib/src/native/java/sentry_native_java.dart
@buenaflor buenaflor requested a review from denrase May 26, 2026 12:29
Align Android worker scope payload normalization with the shared helper
without changing the surrounding worker request flow.

Co-Authored-By: GPT-5.5 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@buenaflor
Copy link
Copy Markdown
Contributor Author

failures are because of Github..

Comment thread packages/flutter/lib/src/native/java/sentry_native_java.dart
@buenaflor buenaflor merged commit b3b2719 into main May 26, 2026
64 of 65 checks passed
@buenaflor buenaflor deleted the buenaflor/ref/android-core-worker-fire-forget branch May 26, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ANR: loadDebugImages runs synchronously on main thread causing ANR on slow I/O devices

3 participants