Skip to content

Android: do no native work at app startup - #21

Merged
Colton127 merged 4 commits into
mainfrom
fix/android-no-startup-work
Jul 26, 2026
Merged

Android: do no native work at app startup#21
Colton127 merged 4 commits into
mainfrom
fix/android-no-startup-work

Conversation

@Colton127

Copy link
Copy Markdown
Owner

Summary

The plugin loaded its native library from a static initializer. GeneratedPluginRegistrant instantiates and attaches every plugin during app launch, so the whole multi-megabyte library was pulled onto the main thread before any app code referenced SoLoud — including for apps that never play a sound.

An UnsatisfiedLinkError there also failed class initialization, crashing plugin registration outright rather than surfacing a catchable error.

Approach

Plugin construction and onAttachedToEngine are now pure Java bookkeeping. The library is loaded lazily at the first point a lifecycle hook actually has to call into native code.

For an app that uses SoLoud that load is a refcount bump, because Dart has already opened the same library through DynamicLibrary.open. A load failure is now swallowed rather than propagated — the FFI layer surfaces it from Dart where it is catchable and actionable.

No Dart-side change was needed: SoLoudController is a lazy singleton and SoLoud.instance is a lazily-initialized static final, so DynamicLibrary.open does not run until app code touches SoLoud.

Verification

A harness performs exactly what GeneratedPluginRegistrant does at launch — construct, then onAttachedToEngine — and reflects on the private load flag afterwards:

construct + onAttachedToEngine: 552 us, native library load attempted = false
teardown hook: load attempted lazily, UnsatisfiedLinkError swallowed, no throw

(That 552 µs is mostly JVM class-loading in the harness itself; the actual work is a field read.)

Compiled under -Xlint:all against stubs mirroring the real embedding API. flutter analyze and flutter test unchanged.

Residual

An app that depends on the plugin but never uses SoLoud will load the library once at engine destroy, to call a hook that immediately no-ops. That is a shutdown-time cost rather than a launch-time one, and does not apply to any app that actually plays audio.

Ordering

Touches only FlutterSoloudPlugin.java. Independent of the non-Java PRs in this series, but #22 and #23 build on this file — merge this one first to avoid conflicts.


Generated by Claude Code

The plugin loaded its native library from a static initializer.
GeneratedPluginRegistrant instantiates and attaches every plugin during app
launch, so the whole multi-megabyte library was pulled onto the main thread
before any app code referenced SoLoud -- and an UnsatisfiedLinkError there
failed class initialization, crashing plugin registration outright rather
than surfacing a catchable error.

Plugin construction and onAttachedToEngine are now pure Java bookkeeping.
The library is loaded lazily at the first point a lifecycle hook actually
has to call into native code. For an app that uses SoLoud that load is a
refcount bump, because Dart has already opened the same library through
DynamicLibrary.open; a load failure is now swallowed rather than propagated.

No Dart-side change was needed: SoLoudController is a lazy singleton and
SoLoud.instance is a lazily-initialized static final, so DynamicLibrary.open
does not run until app code touches SoLoud.

Verified with a harness performing exactly what GeneratedPluginRegistrant
does at launch -- construct, then onAttachedToEngine -- and reflecting on the
private load flag afterwards: no load is attempted, in well under a
millisecond of pure-Java work. The same harness confirms the detach hook
still reaches native lazily and swallows a load failure instead of throwing.
Compiled under -Xlint:all against stubs mirroring the real embedding API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Colton127
Colton127 merged commit 43a8711 into main Jul 26, 2026
1 check passed
@Colton127
Colton127 deleted the fix/android-no-startup-work branch July 26, 2026 01:22
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.

2 participants