Skip to content

Add a deferred platform-view-created path to the shell and use it on Android - #196342

Merged
auto-submit[bot] merged 8 commits into
flutter:masterfrom
gmackall:async-notify-created
Nov 13, 2026
Merged

auto-submit[bot] merged 8 commits into
flutter:masterfrom
gmackall:async-notify-created

Conversation

@gmackall

@gmackall gmackall commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

... and also make PlatformViewAndroid::NotifyChanged non-blocking at the same time.

On Android, surfaceCreated and surfaceChanged currently block the main thread until the raster thread has finished any queued SetupImpellerContext() work, created the swapchain, and set up the rasterizer. Because this runs inside the first view traversal, both the Activity's first native draw and Dart's first frame wait behind it (~80–110 ms on mid/low-tier Android devices with Impeller Vulkan, e.g. ~70 ms faster non-debuggable cold-start time-to-first-frame on a Moto G05 once unblocked).

Per Android's SurfaceHolder.Callback contract, only surfaceDestroyed is required to block until the render thread stops accessing the Surface; surfaceCreated and surfaceChanged can forward surface work asynchronously while fml::RefPtr<AndroidNativeWindow> (ANativeWindow_fromSurface) keeps the native window reference alive until teardown (see #196180 for more details)

Changes

shell/common:

  • Adds PlatformView::NotifyCreatedDeferred and PlatformView::Delegate::OnPlatformViewCreatedDeferred, used only by Android in this PR. The platform passes a SurfaceFactory callback; Shell posts surface creation + Rasterizer::Setup directly to the raster task runner (preserving FIFO ordering with subsequent resize/teardown tasks on the raster queue), posts CreateResourceContext to the IO task runner once a valid Surface is created, and schedules a UI frame without waiting.
  • Every other platform keeps calling PlatformView::NotifyCreated, which is unchanged.
  • Because Rasterizer::Setup can now run concurrently with platform-thread calls that disable or read the raster thread merger, protects merger access with raster_thread_merger_mutex_, starts a lazily created merger disabled if DisableThreadMergerIfNeeded() was called while setup was in flight, and resolves a spawned shell's parent merger lazily on the raster thread.

Android (shell/platform/android):

  • Updates PlatformViewAndroid::NotifyCreated and PlatformViewAndroid::NotifyChanged to post to the raster thread and return immediately. An atomic generation counter (surface_generation_) skips queued setup/resize tasks from a surface that has since been destroyed or replaced.
  • Keeps NotifyDestroyed (and NotifySurfaceWindowChanged for FlutterImageView resize) synchronous, and preserves existing attempt-and-validate failure handling when surface creation fails.

Perfetto cold-start traces on Moto G05 (Impeller Vulkan): before vs. after.

Fixes #196180, and is part of flutter/rfc#24

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

…Android

On Android, surfaceCreated and surfaceChanged block the main thread until
the raster thread has created the swapchain and set up the rasterizer.
This happens inside the first view traversal, so the activity's first
draw and Dart's first frame both wait behind it (about 100 ms on a
Galaxy A16 and a Moto G05).

shell/common:
- Adds PlatformView::NotifyCreatedDeferred and
  PlatformView::Delegate::OnPlatformViewCreatedDeferred. The platform
  passes a surface factory; Shell runs it on the raster thread and
  requests a frame without waiting.
- The default delegate implementation is synchronous, and no other
  platform calls the new path.
- Rasterizer::Setup can now run while the platform thread disables or
  reads the thread merger, so that access is locked, a merger created
  while disabled starts disabled, and a spawned shell resolves its
  parent's merger lazily.

Android:
- PlatformViewAndroid::NotifyCreated and NotifyChanged post to the
  raster thread and return. A generation counter skips tasks from a
  surface that has since been destroyed or replaced.
- NotifyDestroyed stays synchronous, and a failed surface creation is
  handled as before.

Fixes flutter#196180
Add deterministic unit tests in shell_unittests and android_shell_holder_unittests covering non-blocking deferred surface creation, attempt-and-validate surface setup semantics (nullptr and invalid Surface rejection without calling Setup or CreateResourceContext, and setup continuation when SetNativeWindow fails yet CreateRenderingSurface returns a valid Surface), IO resource-context ordering after SetupImpellerContext, immediate UI frame scheduling whose draw orders after Rasterizer::Setup, in-flight NotifyDestroyed ordering and thread-merger disable safety, Shell destruction with pending deferred creation, Shell::Spawn thread-merger sharing when parent setup is deferred, and superseded surface_generation_ skipping.
@github-actions github-actions Bot added platform-android Android applications specifically engine flutter/engine related. See also e: labels. team-android Owned by Android platform team labels Nov 10, 2026
@gmackall gmackall added the CICD Run CI/CD label Nov 10, 2026
Removes the synchronous default implementation, which ran the surface
factory on the calling thread and was only there so test delegates did
not need an override. Shell is the only production delegate; the test
delegates now declare the method like the rest of the interface.
@github-actions github-actions Bot added a: text input Entering text in a text field or keyboard related problems platform-ios iOS applications specifically a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) platform-fuchsia Fuchsia code specifically team-ios Owned by iOS platform team labels Nov 11, 2026
@gmackall
gmackall marked this pull request as ready for review November 11, 2026 02:08
@gmackall
gmackall requested review from a team as code owners November 11, 2026 02:08
@gmackall
gmackall requested review from okorohelijah and removed request for a team November 11, 2026 02:08

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces deferred platform view creation to prevent blocking the platform thread during GPU context or swapchain initialization. It adds asynchronous rendering surface construction via a SurfaceFactory callable on the raster task runner, updates PlatformViewAndroid to utilize this deferred path with generation tracking, and adds thread-safety guards around the Rasterizer thread merger. Feedback suggests ensuring that all writes to raster_thread_merger_ (including in Teardown) are properly guarded by the new mutex, and defensively checking the validity of surface_factory before invocation to prevent potential crashes.

@gmackall
gmackall requested a review from mboetger November 11, 2026 02:17
mboetger
mboetger previously approved these changes Nov 11, 2026

@mboetger mboetger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Love it.

…e merged

NotifyChanged used RunNowOrPostTask on the raster task runner. While the
raster queue is merged onto the platform thread (hybrid composition), that
runs the resize inline, ahead of a surface setup or an earlier resize still
waiting in the raster queue. Post the resize unconditionally so it stays in
FIFO order.

Since the task can now be queued while the queues are merged, and
Shell::~Shell() does not drain a merged raster queue, the task no longer
captures `this`. It shares the surface generation counter instead, and the
destructor bumps the counter so a task that outlives the platform view is a
no-op.

Adds AndroidShellHolder.NotifyChangedStaysBehindQueuedSetupWhenThreadsMerge.
@gmackall

Copy link
Copy Markdown
Member Author

I found one ordering of operations bugs in the HC merged threads case, pushed a fix, but the description is:

While a Hybrid Composition platform view is on screen, raster work runs on the platform thread, so the previous use of RunNowOrPostTask ("run now if on the raster thread, otherwise post") resize ran immediately on surfaceChanged. That let it jump ahead of work still sitting in the raster queue (the surface setup, or an earlier resize), which either resized a surface that didn't exist yet or left the surface at a stale size. The fix always posts the resize so it stays in order (i.e., just use PostTask), and has the task hold a shared generation counter instead of the platform view, so a resize still queued when the engine is torn down does nothing.

@gmackall gmackall added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 13, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Nov 13, 2026
Merged via the queue into flutter:master with commit 04cb5e2 Nov 13, 2026
22 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: text input Entering text in a text field or keyboard related problems CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-android Android applications specifically platform-fuchsia Fuchsia code specifically platform-ios iOS applications specifically team-android Owned by Android platform team team-ios Owned by iOS platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Avoid blocking the platform thread in PlatformViewAndroid::NotifyCreated and NotifyChanged

2 participants