Skip to content

Fix ~230 ms Flutter UI freeze when Android recording starts - #620

Open
AlexDochioiu wants to merge 1 commit into
llfbandit:mainfrom
sesori-ai:android-nonblocking-recorder-start
Open

Fix ~230 ms Flutter UI freeze when Android recording starts#620
AlexDochioiu wants to merge 1 commit into
llfbandit:mainfrom
sesori-ai:android-nonblocking-recorder-start

Conversation

@AlexDochioiu

@AlexDochioiu AlexDochioiu commented Aug 1, 2026

Copy link
Copy Markdown

Why

On Android, AudioRecorder.start() ultimately waits for RecordThread initialization through a CountDownLatch. MethodCallHandlerImpl currently invokes that work synchronously, so starting a recording blocks Flutter's Android platform/UI thread while AudioRecord initializes.

This freezes visible Flutter UI feedback for the duration of recorder startup. In an interface that animates immediately when the user presses a record control, the animation does not merely start late: its frames stop rendering until native initialization returns.

Repeated measurements on a Pixel 10 Pro running Android 17 showed:

  • Recorder initialization taking roughly 224-311 ms.
  • The first press-feedback frame delayed to a 321.9 ms median with the current synchronous implementation.
  • The first feedback frame arriving in 13.7 ms median after moving recorder startup off the platform thread, while initialization continued in the background.

The impact is not specific to one animation or application. Any Flutter UI expected to remain responsive while Android recording starts can visibly freeze because the plugin performs blocking native work on Flutter's platform thread.

What

  • Run each Android recorder's stateful method calls through its own serial worker queue.
  • Route Bluetooth-SCO start continuations back through that same queue so asynchronous device setup cannot return blocking recorder work to the UI thread.
  • Keep Flutter channel registration, notifications, and teardown on the UI thread.
  • Preserve per-recorder call ordering and allow separate recorder instances to operate independently.
  • Reject calls after disposal starts, reserve recorder IDs through teardown, and drain active or disposing queues during engine detach.
  • Add JVM regression tests for nonblocking submission, FIFO ordering, nested continuations, closing, and executor termination.

Complexity

Moderate. Moving the blocking call is small, but preserving recorder ordering, Bluetooth continuations, Flutter channel thread affinity, and engine-detach cleanup crosses several Android lifecycle boundaries.

Risk and test focus

The main risks are call reordering, callbacks escaping back onto the UI thread, duplicate recorder IDs, and disposal racing startup. The implementation uses one serial queue per recorder, rejects calls once disposal starts, keeps recorder IDs reserved through channel teardown, and waits for queued cleanup during engine detach.

There is no Dart API, wire-contract, persisted-data, permission, or database impact.

Expected result

Starting an Android recording no longer freezes Flutter UI feedback while native recorder initialization completes. Recorder method ordering and existing results remain intact, including with multiple recorder instances and lifecycle teardown.

Verification

  • flutter analyze record_android
  • flutter build apk --debug from record/example
  • ./gradlew :record_android:testDebugUnitTest from record/example/android

@AlexDochioiu AlexDochioiu changed the title ⚙️ Run Android recorder calls off platform thread Fix ~230 ms Flutter UI freeze when Android recording starts Aug 1, 2026
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.

1 participant