From d8fc70fa3f56fcb4d94510bb090eca4a6f5c38c2 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Tue, 22 Sep 2026 19:30:03 +0000 Subject: [PATCH 01/43] Second draft. For first draft see: https://docs.google.com/document/d/1pFgFYzAKrIFEQQfSz6NbOvKXG6K3oksPQqhKSQZ7t68 TAG=agy CONV=0c48c67f-eb59-40f8-9424-1a74c8da8e46 --- ...lutter-android-embedder-c-api-migration.md | 1697 +++++++++++++++++ 1 file changed, 1697 insertions(+) create mode 100644 rfc/410.0000-flutter-android-embedder-c-api-migration.md diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md new file mode 100644 index 0000000..f531458 --- /dev/null +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -0,0 +1,1697 @@ +--- +type: rfc +rfc: '410.0000' +title: Flutter Android Embedder C-API Migration +description: Migrates the Flutter Android embedder to the public, ABI-stable Flutter Embedder C-API (embedder.h) to eliminate all direct dependencies on engine internal headers. +status: draft +created: 2026-09-17T00:00:00Z +updated: 2026-09-17T00:00:00Z +tags: + + - 410-android + - 200-engine + - embedder-api + - c-api + - impeller +authors: + + - '"Matt Boetger" ' +reviewers: + + - '"Loïc Sharma" ' + - '"Chris Bracken" ' +links: + + - https://docs.google.com/document/d/1pFgFYzAKrIFEQQfSz6NbOvKXG6K3oksPQqhKSQZ7t68 + - https://github.com/mboetger +--- + +# RFC 410.0000: Flutter Android Embedder C-API Migration + +## Executive Summary and Overview + +This RFC proposes a phased, in-place migration of the Flutter Android embedder ([`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android)) to the public Flutter Embedder C-API ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)). + +Today, the Android embedder interacts directly with engine internals: it allocates task runners, instantiates [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113), inspects internal graphics contexts, and manages platform view compositing via private `flow` headers. This coupling causes several problems: + +- Internal engine refactors in Impeller, Flow, or the Dart runtime break the Android embedder. +- Compiling `libflutter.so` requires pulling internal engine translation units into the Android target. +- Third-party desktop and embedded platforms use [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), while Flutter's primary mobile embedders do not. Because the Android embedder bypasses the public C-API, missing features in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) (such as dynamic thread merging, platform view mutators, and hardware buffer external textures) are not exercised by first-party code. + +This document establishes the plan to decouple the Android embedder from engine internals. + +### Strict Dependency Management + +The dependency graph of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) in [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) will be reduced strictly to: + +- [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L220) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L75) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` enforcing dynamic `FlutterEngineProcTable` resolution) +- [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) +- [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) +- [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) + +All direct dependencies on internal engine subsystems ([`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), and [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt)) will be eliminated. + +### Goals + +- Establish an enforced architectural boundary between Android embedder and the engine core. +- Reduce direct internal engine header inclusions in [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) from 98 to 0. +- Enable independent compilation and stable ABI boundaries for Android embeddings. +- Maintain complete behavioral correctness, performance parity, and accessibility support across all supported Android API levels and rendering backends (Impeller Vulkan, Impeller OpenGL, and legacy Skia). + +### Non-Goals + +- Modifying the public Java/Kotlin Flutter Android API ([`io.flutter.embedding.android.*`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android)). +- Repackaging or splitting `libflutter.so` into separate shared libraries (such as `libflutter_engine.so` and `libflutter_embedder.so`). Compilation remains unified within `libflutter.so` to avoid symbol export inflation, increased APK binary size, and runtime dynamic linker overhead on Android. + +## Background and Problem Statement + +The Android embedder is located in [`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android). It does not use [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). Instead, it is a customized implementation that interacts directly with core engine components. + +### Current Production Architecture ([`flutter_shell_native`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L75)) + +In the existing architecture, the Java embedding coordinates with native code through JNI interfaces, primarily [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108), [`PlatformViewAndroidJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h#L38), and [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38). When initializing the engine from the platform side, [`FlutterJNI.attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) invokes native attachment via [`nativeAttach`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L449), which dispatches across the JNI registration table ([`RegisterApi`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L754-L760)) into [`AttachJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L185-L197) to instantiate the shell holder: + +```cpp +// Existing JNI attachment in shell/platform/android/platform_view_android_jni_impl.cc +static jlong AttachJNI(JNIEnv* env, jclass clazz, jobject flutterJNI) { + fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI); + std::shared_ptr jni_facade = + std::make_shared(java_object); + auto shell_holder = std::make_unique( + FlutterMain::Get().GetSettings(), jni_facade, + FlutterMain::Get().GetAndroidRenderingAPI()); + if (shell_holder->IsValid()) { + return reinterpret_cast(shell_holder.release()); + } else { + return 0; + } +} +``` + +[`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) manages the lifecycle of the engine. It creates a [`flutter::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21) on the [native side](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L98-L113), instantiates a [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) object [directly](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L158-L164) through [`flutter::Shell::Create(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L137), and [owns an instance](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L117-L128) of [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43): + +```cpp +// Existing Shell and ThreadHost instantiation in shell/platform/android/android_shell_holder.cc +flutter::ThreadHost::ThreadHostConfig host_config( + thread_label, mask, AndroidPlatformThreadConfigSetter); +// ... +thread_host_ = std::make_shared(host_config); + +Shell::CreateCallback on_create_platform_view = + [&jni_facade, &weak_platform_view, rendering_api](Shell& shell) { + auto platform_view_android = std::make_unique( + shell, shell.GetTaskRunners(), jni_facade, rendering_api); + weak_platform_view = platform_view_android->GetWeakPtr(); + return platform_view_android; + }; + +Shell::CreateCallback on_create_rasterizer = [](Shell& shell) { + return std::make_unique(shell); +}; + +shell_ = Shell::Create(GetDefaultPlatformData(), // window data + task_runners, // task runners + settings_, // settings + on_create_platform_view, // platform view factory + on_create_rasterizer // rasterizer factory +); +``` + +To start execution, [`FlutterJNI.runBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) forwards to [`RunBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L298-L318). This function calls [`AndroidShellHolder::Launch`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L287-L305), which instructs the shell to [run the engine](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L304) with the resolved [`RunConfiguration`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h#L44). + +[`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) [subclasses the engine's internal `flutter::PlatformView` class](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L151-L160) (defined in [`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L51)): + +```cpp +// Existing subclassing of internal engine PlatformView in shell/platform/android/platform_view_android.h +class PlatformViewAndroid final : public PlatformView { + public: + PlatformViewAndroid(PlatformView::Delegate& delegate, + const flutter::TaskRunners& task_runners, + const std::shared_ptr& jni_facade, + AndroidRenderingAPI rendering_api); + // ... + void NotifyCreated(fml::RefPtr native_window); + void NotifyDestroyed(); +}; +``` + +This inheritance allows Android to interact directly with internal classes such as [`flutter::Rasterizer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h#L114) (created via [rasterizer factory callbacks](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L130-L132)), [`flutter::Surface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h#L26) (created directly by [`PlatformViewAndroid::CreateRenderingSurface()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L426-L431)), [`flutter::TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L68) (manipulated via [`PlatformViewAndroid::RegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L380)), and [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211) (dispatched across JNI via [`PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1748-L1775)). In addition, window and surface lifecycle events flow from Android callbacks directly into [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L248): [`SurfaceCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L264-L269) calls [`NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L219) to bind the native window with a synchronous waitable event latch: + +```cpp +// Existing synchronous surface lifecycle blocking in shell/platform/android/platform_view_android.cc +void PlatformViewAndroid::NotifyCreated( + fml::RefPtr native_window) { + if (android_surface_) { + InstallFirstFrameCallback(); + fml::AutoResetWaitableEvent latch; + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetRasterTaskRunner(), + [&latch, surface = android_surface_.get(), + native_window = std::move(native_window), jni_facade = jni_facade_]() { + surface->SetNativeWindow(native_window, jni_facade); + latch.Signal(); + }); + latch.Wait(); + } + PlatformView::NotifyCreated(); +} +``` + +Similarly, [`SurfaceChanged`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L285-L292) triggers [`NotifyChanged`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L236-L248) to resize the onscreen surface, and [`SurfaceDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L294-L296) triggers [`NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L234) to tear down the raster surface context synchronously. + +```mermaid +flowchart TD + subgraph Java Layer + Activity["FlutterActivity / FlutterFragment"] + FlutterView["FlutterView"] + FlutterJNI["FlutterJNI"] + end + + subgraph Android Embedder JNI + AndroidShellHolder["AndroidShellHolder"] + PlatformViewAndroid["PlatformViewAndroid"] + end + + subgraph Flutter Engine Core + Shell["Shell"] + PlatformView["PlatformView"] + TaskRunners["TaskRunners: Platform, UI, Raster, IO"] + ThreadHost["ThreadHost"] + Engine["Engine"] + Rasterizer["Rasterizer"] + end + + Activity --> FlutterView + FlutterView --> FlutterJNI + FlutterJNI --> AndroidShellHolder + AndroidShellHolder --> PlatformViewAndroid + AndroidShellHolder --> Shell + AndroidShellHolder --> ThreadHost + AndroidShellHolder --> TaskRunners + PlatformViewAndroid -- Subclasses --> PlatformView + PlatformViewAndroid -. Delegate .-> Shell + Shell --> Engine + Shell --> Rasterizer +``` + +```mermaid +sequenceDiagram + participant FlutterJNI + participant AndroidShellHolder + participant ThreadHost + participant Shell + + FlutterJNI->>AndroidShellHolder: Initialize with Surface, Looper, TaskRunners + AndroidShellHolder->>ThreadHost: Create ThreadHost: Platform, UI, Raster, IO + AndroidShellHolder->>Shell: Shell::Create with settings and task runners + Shell-->>AndroidShellHolder: Shell Instance + FlutterJNI->>AndroidShellHolder: Launch with entrypoint and args + AndroidShellHolder->>Shell: RunEngine with RunConfiguration +``` + +### Dependency Entanglement in [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) + +Because [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) reach directly into internal engine classes, [`flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L95) in [`engine/src/flutter/shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) depends on internal engine targets. + +An audit of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) reveals 98 internal engine headers included across Android shell files, categorized across 9 distinct subsystems: + +| Subsystem & Headers | Direct GN Dependencies | Key Internal Headers | Functional Purpose in Embedder | +| :--- | :--- | :--- | :--- | +| **Impeller & Graphics Context Setup**
(29 headers) | [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller)
[`//flutter/impeller/toolkit/android`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/android)
[`//flutter/impeller/toolkit/egl`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/egl)
[`//flutter/impeller/toolkit/gles`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/gles)
[`//flutter/impeller/toolkit/glvk`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/glvk) | [`renderer/backend/vulkan/context_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h)
[`surface_context_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.h)
[`renderer/backend/gles/context_gles.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h)
[`toolkit/android/hardware_buffer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/android/hardware_buffer.h)
[`renderer/backend/vulkan/android/ahb_texture_source_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h) | Initializing rendering contexts on the raster thread ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)), creating Vulkan and EGL swapchains, and managing zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports. | +| **Platform Views & Layer Compositing**
(2 headers) | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) | [`flow/embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h)
[`flow/surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h) | Platform view compositing across Hybrid Composition++ (HCPP via [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl)), Hybrid Composition (HC), Texture Layer Hybrid Composition (TLHC), and Virtual Displays (VD); managing geometric clipping and transforms via [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211), view slicing, surface pooling ([`surface_pool.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h)), and multi-surface composition frame results ([`external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). | +| **External Textures & Surface Control**
(5 headers) | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics)
[`//flutter/display_list`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list) | [`common/graphics/texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h)
[`common/graphics/gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)
[`display_list/image/dl_image_skia.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list/image/dl_image_skia.h)
[`display_list/geometry/dl_geometry_types.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list/geometry/dl_geometry_types.h) | Texture lifecycle and frame updates for Android [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (OpenGL ES) and [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (Vulkan); feeding decoded video and camera frames into the [`DisplayList`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list/display_list.h) compositor. | +| **Engine Core, Lifecycle & Threading**
(11 headers) | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) | [`shell/common/shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h)
[`thread_host.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h)
[`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h)
[`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h)
[`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h)
[`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h)
[`snapshot_surface_producer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/snapshot_surface_producer.h) | Direct instantiation of [`flutter::Shell::Create(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L137), thread host allocation (Platform, UI, Raster, IO), vsync scheduling, snapshot generation, and rasterizer coordination. | +| **UI Layer & Message Dispatch**
(5 headers) | [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) | [`lib/ui/window/platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h)
[`platform_message_response.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message_response.h)
[`lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h)
[`lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h) | Binary message encoding and decoding for platform channels, background Dart entrypoint lookup ([`PluginUtilities.getCallbackHandle`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins.dart#L54) via [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h)), and native platform image decoding. | +| **Dart VM, Runtime & Service Isolate**
(2 headers) | [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime)
[`//flutter/runtime:libdart`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/BUILD.gn) | [`runtime/dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h)
[`runtime/dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h) | Managing the Dart VM lifecycle, configuring concurrent message loops, isolate creation, and Dart service isolate initialization. | +| **Asset Management & Packaging**
(1 header) | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) | [`assets/asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h) | Resolving assets and kernel blobs packaged within the Android APK via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17). | +| **GPU Backends & Surface Delegates**
(5 headers) | [`//flutter/shell/gpu`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | [`shell/gpu/gpu_surface_gl_impeller.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu/gpu_surface_gl_impeller.h)
[`gpu_surface_vulkan_impeller.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu/gpu_surface_vulkan_impeller.h)
[`vulkan/vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h) | Implementing platform window surface delegates for OpenGL and Vulkan render pipelines. | +| **Foundational Utilities & Concurrency**
(24 headers) | [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | [`fml/raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)
[`fml/task_runner.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h)
[`fml/message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.h)
[`fml/concurrent_message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/concurrent_message_loop.h)
[`fml/platform/android/jni_util.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/jni_util.h)
[`fml/platform/android/scoped_java_ref.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/scoped_java_ref.h) | Message loops, JNI handle wrappers, task scheduling, and raster thread leasing. | + +### Technical Consequences of Coupling + +This level of coupling creates several concrete maintenance problems: + +- Fragile refactoring: Any change to private engine internals (such as Impeller backend refactors or Flow mutator stack updates) breaks the Android embedder. +- Prolonged rebuild times: Compiling `libflutter.so` requires rebuilding internal engine translation units even when only platform-specific code changes. +- Lack of API contract: No stable boundary prevents Android embedder code from reaching into private engine internals, which inhibits engine modularization. + +## Detailed Design + +To allow the Android embedder to communicate with the engine exclusively through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the C-API must be extended to cover Android-specific subsystem requirements. Each proposed extension corresponds directly to uncoupling one of the internal subsystems identified in the dependency audit and empowering a modular target architecture component (`FlutterEmbedderNative`, `JniRouter` / `JniDelegate` / `JvmInvoker`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17)). + +### Graphics Surface Setup, Context Ownership, and Surface Lifecycle ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)) + +On Android, graphics context creation must be cleanly decoupled from window surface presentation. The embedder initializes or acquires the graphics context (such as Vulkan [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html), physical device, logical [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html), and Vulkan proc loaders, or EGL display, context, and config) prior to invoking [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). + +In the existing engine architecture, [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) defers the decision between Vulkan and OpenGL ES until [`GetImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L162) is first called on the raster thread via [`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570): + +```cpp +// Existing deferred graphics resolution in shell/platform/android/android_context_dynamic_impeller.cc +void AndroidContextDynamicImpeller::SetupImpellerContext() { + if (vk_context_ || gl_context_) { + return; + } + vk_context_ = GetActualRenderingAPIForImpeller(android_get_device_api_level(), + settings_); + if (!vk_context_) { + gl_context_ = std::make_shared( + std::make_unique(), + settings_.enable_gpu_tracing, io_task_runner_); + } +} +``` + +Because `embedder.h` requires a concrete renderer configuration struct (`FlutterRendererConfig` selecting `kOpenGL` or `kVulkan`) at `FlutterEngineInitialize` time, the embedder resolves the graphics backend during initialization before passing renderer pointers to the engine. + +Performing synchronous Vulkan driver queries during `FlutterLoader.ensureInitializationComplete` on Android's main thread introduces a 15 to 30 ms cold-startup delay on budget devices (such as MediaTek or older Mali chipsets) due to driver library loading and physical device property enumeration. + +To eliminate main-thread startup delays: + +1. `FlutterLoader.startInitialization` dispatches an asynchronous Vulkan capability probe to a background worker isolate or thread when the host application starts. +2. The probe result (device extensions, driver ID, and Impeller capability flags) is cached in Android [`SharedPreferences`](https://developer.android.com/reference/android/content/SharedPreferences). +3. Subsequent application launches read the cached renderer preference in less than 0.5 ms during `FlutterLoader.ensureInitializationComplete`. +4. If the device experiences a driver crash or OS upgrade, the embedder falls back safely to OpenGLES. + +```mermaid +sequenceDiagram + autonumber + participant App as Application onCreate + participant Loader as FlutterLoader + participant Worker as Background Task Worker + participant Prefs as Android SharedPreferences + participant Init as FlutterEngineInitialize + + App->>Loader: startInitialization + Loader->>Worker: Dispatch asynchronous Vulkan capability check + Worker->>Worker: Probe VkInstance and VkDevice compatibility + Worker->>Prefs: Write cached renderer flag: kImpellerVulkan or kImpellerOpenGLES + App->>Loader: ensureInitializationComplete + Loader->>Prefs: Read cached renderer flag in under 0.5ms + Loader->>Init: Pass concrete FlutterRendererConfig without main-thread stall +``` + +To configure raster-thread graphics state and resources, [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693) and [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) provide context setup callbacks: + +```c +typedef struct { + size_t struct_size; + // Callback invoked on the raster thread to initialize OpenGL graphics state. + VoidCallback setup_callback; +} FlutterOpenGLRendererConfig; + +typedef struct { + size_t struct_size; + // Callback invoked on the raster thread to initialize Vulkan graphics state. + VoidCallback setup_callback; +} FlutterVulkanRendererConfig; +``` + +[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) also receives an API to construct a render target directly from a Vulkan backing store to match Impeller Vulkan swapchain requirements. Window surface attachments ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and swapchains are created and destroyed dynamically across Android [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events via [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) (see Renderer Availability and Lifecycle Signals) without destroying the underlying GPU context. + +> [!NOTE] Backend Simplification: Dropping Skia +> If legacy Skia support is dropped in favor of an Impeller-only architecture on Android, all legacy Skia context setup, EGL display and context fallback handling, and Ganesh header dependencies are eliminated. Graphics surface initialization narrows cleanly to Impeller Vulkan with an Impeller OpenGL ES fallback. + +### Dynamic Raster Thread Merging (Legacy Hybrid Composition) + +Android Hybrid Composition (HC) synchronizes native Android [`View`](https://developer.android.com/reference/android/view/View) widgets with Flutter layers. During frame rendering in legacy HC mode, native Android views update on the main platform thread. If Flutter continues rasterizing on a separate raster thread, thread contention and deadlocks can occur during surface redraws. + +Modern Hybrid Composition++ (HCPP, see Hybrid Composition++ (HCPP) and SurfaceControl Compositing) bypasses thread merging through [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction fences on Android 14+ (API level 34+); however, legacy Hybrid Composition remains required for earlier Android API levels under Skia. To prevent deadlocks in HC mode, Flutter dynamically merges the raster thread into the platform thread ([`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31)). In the current architecture, [`AndroidExternalViewEmbedder::PostPrerollAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213) checks if the layer tree contains platform views and manages the lease term directly: + +```cpp +// Existing thread leasing in shell/platform/android/external_view_embedder/external_view_embedder.cc +PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( + const fml::RefPtr& raster_thread_merger) { + if (!FrameHasPlatformLayers()) { + return PostPrerollResult::kSuccess; + } + if (!raster_thread_merger->IsMerged()) { + CancelFrame(); + raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration); + return PostPrerollResult::kSkipAndRetryFrame; + } + raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration); + if (previous_frame_view_count_ == 0) { + return PostPrerollResult::kResubmitFrame; + } + return PostPrerollResult::kSuccess; +} +``` + +During merged execution, both platform tasks and raster work execute on the Android platform thread. Once the platform view interaction ceases, the threads unmerge. + +> [!IMPORTANT] No C-ABI extension is proposed for thread merging +> Publishing a thread-merger handle and frame lifecycle callbacks in `embedder.h` would make the engine's internal threading model part of the public ABI and leak `RasterThreadMerger` lease semantics to embedders. Instead, the migration retains [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) and [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) only inside the quarantined legacy target during Phases 2 and 3, and deletes `external_view_embedder/` in Phase 4 once `AndroidPlatformViewsController` routes all platform view composition through `FlutterCompositor` and `AndroidSurfaceControl`. + +#### Empirical Characterization of Lease Dynamics + +Characterization of [`RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) during scroll interactions with embedded platform views reveals two operating regimes: + +1. **Steady-State Scroll Behavior**: As long as platform views remain visible in the viewport, every layer tree contains platform view layers. The embedder calls `ExtendLeaseTo(10)` on every frame (using a standard lease duration of 10 frames, which yields approximately 166 ms of hysteresis at a 60 Hz refresh rate). Each frame decrements the remaining lease count from 10 to 9, but the subsequent frame immediately resets the lease back to 10. Consequently, threads remain continuously merged with zero toggle flapping between merged and unmerged states. +2. **View Departure and Hysteresis**: When the viewport scrolls past the last platform view or the view is dismissed, subsequent frames contain no platform layers. The embedder halts lease extensions. Over the next 10 consecutive frames, the remaining lease count decrements on each frame (9, 8, ..., 0). On the 10th frame after view departure, the lease count reaches 0, `UnMergeNowIfLastOne()` executes, and the rasterizer transitions back to independent multi-threaded execution. This 10-frame lease term provides hysteresis: transient gaps without platform layers (such as dynamic view recycling or brief animations) do not repeatedly merge and unmerge OS threads. + +#### Interaction with Static Thread Merging + +Flutter supports running with statically merged platform and UI or raster threads via `--merged-platform-ui-thread` or `kMergeAfterLaunch`. When task queues are statically merged, [`RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) lease operations ([`MergeWithLease`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L43), [`ExtendLeaseTo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L70), [`DecrementLease`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L77), [`UnMergeNowIfLastOne`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L66)) are safe no-ops and the merged-state queries unconditionally report `true`. Dynamic thread merging composes safely with static merging without requiring separate code branches. + +#### Resolution: Retiring Legacy `AndroidExternalViewEmbedder` + +Legacy Hybrid Composition under Skia was the only consumer of dynamic raster thread merging in `shell/platform/android/external_view_embedder/`. Under Impeller, platform view composition executes asynchronously via Hybrid Composition++ (HCPP via `SurfaceControl.Transaction` and hardware sync fences on API 34+) and Texture Layer Hybrid Composition (TLHC on lower API levels). In Phase 4, `external_view_embedder/` (`AndroidExternalViewEmbedder` and `AndroidExternalViewEmbedder2`) is removed from `shell/platform/android/`, and `AndroidPlatformViewsController` drives all platform view composition through the public `FlutterCompositor` interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback`). This eliminates `fml::RasterThreadMerger` from the Android embedder without adding thread-merger handles to `embedder.h` and leaves zero exceptions to the GN compile-time firewall. + +### External Textures (Zero-Copy Vulkan and OpenGL ES) + +Android provides two distinct mechanisms for external textures: + +- [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline that imports [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles directly into Vulkan ([`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html)) or EGL ([`EGLImage`](https://registry.khronos.org/EGL/sdk/docs/man/html/eglCreateImage.xhtml)). +- [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture): A legacy OpenGL ES path where native frames render into a texture ID updated with [`SurfaceTexture.updateTexImage()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()). + +The public Embedder API previously lacked a way to deliver Vulkan-backed external images without depending on the internal [`flutter::Texture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L27) class. The design closes this with a pull-based frame callback on the renderer config rather than a push-based registration entry point. The embedder claims a texture ID through the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) and signals new content with [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250); the engine calls back to collect the backing image for the frame it is about to compose: + +```c +/// Specifies the backing type of a Vulkan external texture. +typedef enum { + kFlutterVulkanExternalTextureTypeVkImage, + kFlutterVulkanExternalTextureTypeAHardwareBuffer, +} FlutterVulkanExternalTextureType; + +typedef struct { + size_t struct_size; + size_t width; + size_t height; + /// VkFormat. May be 0 for an AHardwareBuffer, in which case the engine + /// queries the format from the buffer itself. + uint32_t format; + FlutterVulkanExternalTextureType type; + union { + FlutterVulkanImageHandle vk_image; + FlutterAHardwareBufferHandle hardware_buffer; + }; + /// VkImageLayout of the image. + uint32_t image_layout; + /// NULL for standard sampling; non-NULL for YCbCr camera and video buffers. + const FlutterVulkanYcbcrConversionInfo* ycbcr_conversion_info; + void* user_data; + VoidCallback destruction_callback; +} FlutterVulkanExternalTexture; + +/// Invoked on an engine-managed thread to obtain the texture for a frame. +typedef bool (*FlutterVulkanExternalTextureFrameCallback)( + void* user_data, + int64_t texture_identifier, + size_t width, + size_t height, + FlutterVulkanExternalTexture* texture_out); +``` + +`FlutterVulkanRendererConfig` carries this callback as `external_texture_frame_callback`. A parallel [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-texture-composition-and-surfaceproducer) sits on both [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) and [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693), so an [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) reaches either backend without an Android-specific engine entry point. + +The discriminated union is what makes this workable on Android. [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) hands out [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles rather than [`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html) handles, and `ycbcr_conversion_info` carries the external format identifier and component swizzle that camera and video decoder buffers require. Vendor buffers with an external format cannot be sampled correctly without it. + +For legacy [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) instances, the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) API with `kFlutterExternalTextureTypeOpenGL` is used. However, the existing [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1121) struct only specifies target, name, format, and dimensions; it lacks a UV coordinate transformation matrix. On Android, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 matrix accounting for camera sensor rotation, video decoder padding, and coordinate inversion. In the existing architecture, [`SurfaceTextureExternalTexture::Update()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc#L134-L143) queries this matrix through [`PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1626-L1645) and returns an [`SkM44`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkM44.h) matrix directly: + +```cpp +// Existing UV transformation querying in shell/platform/android/surface_texture_external_texture.cc +void SurfaceTextureExternalTexture::Update() { + jni_facade_->SurfaceTextureUpdateTexImage( + fml::jni::ScopedJavaLocalRef(surface_texture_)); + transform_ = jni_facade_->SurfaceTextureGetTransformMatrix( + fml::jni::ScopedJavaLocalRef(surface_texture_)); +} + +const SkM44& SurfaceTextureExternalTexture::GetCurrentUVTransformation() const { + return transform_; +} +``` + +Without this matrix, external video and camera frames render inverted or distorted. + +To resolve this gap, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces [`FlutterOpenGLTexture2`](#external-texture-composition-and-surfaceproducer): + +```c +typedef struct { + size_t struct_size; + uint32_t target; + uint32_t name; + uint32_t format; + void* user_data; + VoidCallback destruction_callback; + uint32_t width; + uint32_t height; + FlutterTransformation uv_transform; +} FlutterOpenGLTexture2; +``` + +Under OpenGL ES, platform view compositing also faces constraints: because [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction sync fences depend on Vulkan hardware synchronization, OpenGL platform views must use Texture Layer Hybrid Composition (TLHC) or legacy Hybrid Composition rather than HCPP. + +> [!NOTE] External Texture Simplification: Dropping Skia and Legacy OpenGL Textures +> Retiring legacy Skia and legacy OpenGL `SurfaceTexture` pathways in favor of Impeller Vulkan allows the embedder to eliminate legacy UV coordinate transform matrix conversions (`FlutterOpenGLTexture2`) and standardize directly on zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports through [`FlutterVulkanExternalTexture`](#external-texture-composition-and-surfaceproducer). + +### Platform View Mutator Stack Serialization + +Platform views embedded within Flutter require spatial transformations, opacity adjustments, and geometric clips across Android composition modes: HCPP, legacy HC, and TLHC. + +Internally, these operations are managed by [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211) in [`flow/embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h). Because `flow` and `SkPath` headers cannot cross the C-API boundary, mutators must be serialized into an ABI-stable array of C structs: + +```c +typedef enum { + kFlutterPlatformViewMutationTypeOpacity, + kFlutterPlatformViewMutationTypeClipRect, + kFlutterPlatformViewMutationTypeClipRoundedRect, + kFlutterPlatformViewMutationTypeTransformation, + kFlutterPlatformViewMutationTypeClipRoundSuperellipse, + kFlutterPlatformViewMutationTypeClipPath, +} FlutterPlatformViewMutationType; + +typedef struct { + FlutterPlatformViewMutationType type; + union { + double opacity; + FlutterRect clip_rect; + FlutterRoundedRect clip_rounded_rect; + FlutterTransformation transformation; + FlutterRoundSuperellipse clip_round_superellipse; + FlutterPath clip_path; + }; +} FlutterPlatformViewMutation; +``` + +Arbitrary clips need a path representation that does not drag [`SkPath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkPath.h) across the boundary. In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), [`FlutterPath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L757) is a flat segment array with an explicit fill rule: + +```c +typedef enum { + kFlutterPathFillTypeNonZero, + kFlutterPathFillTypeEvenOdd, +} FlutterPathFillType; + +typedef enum { + kFlutterPathVerbMove, + kFlutterPathVerbLine, + kFlutterPathVerbQuad, + kFlutterPathVerbConic, + kFlutterPathVerbCubic, + kFlutterPathVerbClose, +} FlutterPathVerb; + +typedef struct { + FlutterPathVerb verb; + /// Interpretation depends on verb; at most three points are used. + FlutterPoint points[3]; + /// Weight for the rational quadratic when verb is kFlutterPathVerbConic. + double conic_weight; +} FlutterPathSegment; + +typedef struct { + size_t struct_size; + FlutterPathFillType fill_type; + size_t segments_count; + /// Valid only for the duration of the frame presentation callback. + const FlutterPathSegment* segments; +} FlutterPath; +``` + +Mutations ride on [`FlutterPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1525), which the engine hands to the embedder inside a [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L663) through the compositor's [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1494): + +```c +typedef struct { + size_t struct_size; + FlutterPlatformViewIdentifier identifier; + size_t mutations_count; + /// Applied in order. The first entry is a transformation mutation when a + /// device pixel ratio or root surface transformation is in effect, so that + /// subsequent mutations are already in the correct coordinate space. + const FlutterPlatformViewMutation** mutations; +} FlutterPlatformView; +``` + +The array is an array of pointers rather than an array of values. Indexing strides by pointer width instead of `sizeof(FlutterPlatformViewMutation)`, so a future mutation type that widens the union does not shift the stride out from under an already-compiled embedder. This is the same technique [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) uses. + +> [!NOTE] Mutator Simplification: Dropping Complex Path Clipping +> If legacy Hybrid Composition under Skia is dropped, or if platform view clipping is restricted to standard geometric bounds, `kFlutterPlatformViewMutationTypeClipPath` and the whole `FlutterPath` family become dead weight. Mutations would then convey only affine transformations, bounding rectangles, and corner radii, all of which map directly to Android `SurfaceControl` or `View` properties. + +### Accessibility and Semantics Parity (`FlutterSemanticsNode2` In-Place Extension) + +Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in the original [`FlutterSemanticsNode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1599) struct, including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `identifier`, `traversal_parent`, and `hit_test_transform`. + +#### In-Place Extension of `FlutterSemanticsNode2` + +The legacy [`FlutterSemanticsUpdate`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1855) (v1) stored nodes in a flat array of structs by value (`FlutterSemanticsNode* nodes`). In C, indexing `nodes[i]` strides by `sizeof(FlutterSemanticsNode)`, so adding members changed the struct size and broke older binaries. + +[`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) resolved this by replacing the flat array with an array of pointers: + +```c +typedef struct { + size_t struct_size; + size_t node_count; + FlutterSemanticsNode2** nodes; // Array of pointers + size_t custom_action_count; + FlutterSemanticsCustomAction2** custom_actions; // Array of pointers + FlutterViewId view_id; +} FlutterSemanticsUpdate2; +``` + +Because `nodes` is an array of pointers, pointer striding is fixed (`sizeof(FlutterSemanticsNode2*)`). Each [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) begins with `size_t struct_size`. This design allows `FlutterSemanticsNode2` to be extended in place: + +1. **Precedent**: The engine team has already appended members directly to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) without introducing a v3 struct (for example, adding `heading_level` in engine commit `59d8010db51`, and adding `identifier` in engine commit `f01fa5950c0`). +2. **Missing Android Accessibility Members**: The remaining fields required by Android's `AccessibilityBridge` are appended to the end of `FlutterSemanticsNode2`: + +```c + // Appended to FlutterSemanticsNode2 in embedder.h: + int32_t max_value_length; + int32_t current_value_length; + const char* link_url; + const char* locale; + double min_value; + double max_value; + int32_t traversal_parent; + FlutterTransformation hit_test_transform; +``` + +3. **Safe Access & Lifecycle**: Semantics batches are delivered from the engine to the embedder via [`FlutterUpdateSemanticsCallback2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1895) registered in `FlutterProjectArgs.update_semantics_callback2`. Embedders read fields using `SAFE_ACCESS(node, member, default_value)`. Semantics updates are enabled or disabled via [`FlutterEngineUpdateSemanticsEnabled`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3352), and accessibility actions triggered by Android OS accessibility services are dispatched back to the engine via [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398). + +### Dart Deferred Library Loading + +Android supports dynamic delivery through Google Play Feature Delivery / split APKs. When Dart code requests a deferred library via `deferred as`, the embedder must download or extract the split APK and load the compiled snapshot into the running isolate. + +Currently, this is handled through private calls across JNI in [`LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L701-L735) into [`PlatformViewAndroid::LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L508-L514): + +```cpp +// Existing JNI deferred loading invocation in shell/platform/android/platform_view_android_jni_impl.cc +static void LoadDartDeferredLibrary(JNIEnv* env, + jobject obj, + jlong shell_holder, + jint jLoadingUnitId, + jobjectArray jSearchPaths) { + // ... resolve symbols data_mapping and instructions_mapping via dlopen ... + ANDROID_SHELL_HOLDER->GetPlatformView()->LoadDartDeferredLibrary( + loading_unit_id, std::move(data_mapping), + std::move(instructions_mapping)); +} +``` + +[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) is extended with callbacks and entry points mirroring the Dart SDK deferred loading API: + +```c +/// Callback invoked by the engine when Dart requests loading of a deferred +/// library / loading unit. +/// +/// This callback runs on the platform task runner thread. +/// +/// @param[in] loading_unit_id The ID of the loading unit to load. +/// @param[in] user_data The user data provided in `FlutterProjectArgs`. +typedef void (*FlutterRequestDartDeferredLibraryCallback)( + intptr_t loading_unit_id, + void* user_data); + +/// Information passed to `FlutterEngineLoadDartDeferredLibrary`. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterDartDeferredLibrary). + size_t struct_size; + + /// The ID of the loading unit being loaded. + intptr_t loading_unit_id; + + /// The snapshot data buffer containing the Dart deferred library. Must not be null. + const uint8_t* snapshot_data; + + /// The size of the snapshot data buffer in bytes. + size_t snapshot_data_size; + + /// The snapshot instructions buffer containing the Dart deferred library. + const uint8_t* snapshot_instructions; + + /// The size of the snapshot instructions buffer in bytes. + size_t snapshot_instructions_size; + + /// Optional user data pointer passed to `destruction_callback`. + void* user_data; + + /// Optional callback invoked when the engine releases its references to + /// `snapshot_data` and `snapshot_instructions`. + /// + /// If null, the embedder must ensure the buffers remain valid until + /// `FlutterEngineShutdown` returns. If non-null, this callback is invoked + /// once both snapshot buffers are no longer referenced by the engine (for + /// example, on isolate shutdown or if loading fails). + VoidCallback destruction_callback; +} FlutterDartDeferredLibrary; + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineLoadDartDeferredLibrary( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterDartDeferredLibrary* library); + +typedef struct { + size_t struct_size; + intptr_t loading_unit_id; + const char* error_message; + bool transient; +} FlutterDartDeferredLibraryLoadError; + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyDartDeferredLibraryLoadError( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterDartDeferredLibraryLoadError* error); +``` + +#### Deferred Library Memory Lifetime Management + +Because split APK assets on Android are loaded via `AAssetManager` or memory-mapped (`mmap`) from disk, buffer lifetime must be explicitly managed: + +1. When Dart requests a library, [`FlutterRequestDartDeferredLibraryCallback`](#deferred-component-loading-and-asset-lifecycles) runs asynchronously (`void` return type), so the embedder can initiate download or extraction without blocking the engine thread. +2. When loading completes, `FlutterDartDeferredLibrary.destruction_callback` provides the unmap trigger. In the proposed [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc) implementation, the engine captures this callback in a [`DeferredLibraryLifetime`](#deferred-library-memory-lifetime-management) handle held by [`fml::NonOwnedMapping`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/mapping.h#L115). When the Dart VM finishes loading and drops its references to both data and instruction buffers, the destructor triggers `destruction_callback(user_data)`, which permits the Android embedder to safely `munmap()` or close the underlying [`AAsset`](https://developer.android.com/ndk/reference/group/asset). + +### Custom Asset and Kernel Resolution (APK / In-Memory Mapping) + +Desktop embedders load assets from loose files in a filesystem directory passed to [`FlutterProjectArgs.assets_path`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2519). On Android, assets are packaged inside APK or AAB archives and accessed through the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26), which previously subclassed the internal engine [`AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23): + +```cpp +// Existing internal AssetResolver inheritance in shell/platform/android/apk_asset_provider.h +class APKAssetProvider final : public AssetResolver { + public: + explicit APKAssetProvider(JNIEnv* env, + jobject assetManager, + std::string directory); + std::unique_ptr Clone() const; +}; +``` + +Passing file paths would require extracting all assets to disk during startup, which causes latency and disk consumption. + +[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives a custom asset resolver callback interface that maps assets into memory directly from the APK archive: + +```c +typedef struct { + size_t struct_size; + const uint8_t* mapping; + size_t size; + void* user_data; + VoidCallback release_callback; +} FlutterAssetMapping; + +typedef bool (*FlutterAssetResolverGetAsMappingCallback)( + void* user_data, + const char* asset_name, + FlutterAssetMapping* mapping_out); + +typedef struct { + size_t struct_size; + void* user_data; + FlutterAssetResolverGetAsMappingCallback get_as_mapping_callback; + // Indicates if the resolver remains valid after the underlying Android + // AAssetManager instance changes or is recreated. + bool is_valid_after_asset_manager_change; + VoidCallback release_callback; +} FlutterCustomAssetResolver; + +typedef struct { + size_t struct_size; + FlutterCustomAssetResolver resolver; +} FlutterAssetResolverRegistrationInfo; + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRegisterAssetResolver( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterAssetResolverRegistrationInfo* info); + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineUpdateAssetResolverByType( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterAssetResolverRegistrationInfo* info); +``` + +A `FlutterCustomAssetResolver` passes into `FlutterProjectArgs.custom_asset_resolver` (or registers dynamically via `FlutterEngineRegisterAssetResolver`) so that the engine streams assets directly from the APK. During hot restart or dynamic feature installation, the engine calls [`UpdateResolverByType`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_manager.h#L67) via `FlutterEngineUpdateAssetResolverByType` to reload asset manifests and updated Dart bytecode bundles without restarting the native engine instance. + +### Multi-Engine Spawning and Add-to-App (`FlutterEngineSpawn`) + +Android Add-to-App architectures rely on [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) to spawn lightweight engines that share the Dart VM, isolate group, and shared cache. In the existing architecture, [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240-L285) reaches directly into [`shell_->Spawn(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L248): + +```cpp +// Existing spawning in shell/platform/android/android_shell_holder.cc +std::unique_ptr shell = + shell_->Spawn(std::move(config.value()), initial_route, + on_create_platform_view, on_create_rasterizer); + +return std::unique_ptr(new AndroidShellHolder( + GetSettings(), jni_facade, thread_host_, std::move(shell), + apk_asset_provider_->Clone(), weak_platform_view, + android_context->RenderingApi())); +``` + +The public C-API lacked a spawn API, which prevented multi-engine groups from operating without internal [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h) methods. [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives [`FlutterEngineSpawn`](#engine-spawning-and-multi-engine-management-flutterenginegroup): + +```c +typedef struct { + size_t struct_size; + const char* entrypoint; + const char* library_uri; + const char* initial_route; + const char* const* entrypoint_argv; + int entrypoint_argc; + void* user_data; + const FlutterProjectArgs* project_args; + const FlutterRendererConfig* renderer_config; +} FlutterEngineSpawnConfig; + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSpawn( + FLUTTER_API_SYMBOL(FlutterEngine) parent_engine, + const FlutterEngineSpawnConfig* config, + FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); +``` + +The [`FlutterEngineSpawnConfig`](#engine-spawning-and-multi-engine-management-flutterenginegroup) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240) so that spawned isolates mount distinct route hierarchies immediately upon launch. + +Spawned shells share the parent engine's [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context.h) / Impeller device context (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. + +#### Relationship to Single-Isolate Multiview (`FlutterEngineAddView`) + +`FlutterEngineSpawn` and single-isolate multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)) address distinct concurrency and isolation trade-offs on Android: + +1. **Multi-Isolate Engine Groups (`FlutterEngineSpawn` / `FlutterEngineGroup`)**: Each spawned `FlutterEngine` runs an independent Dart root isolate within the shared Dart VM isolate group and `AndroidContext`. A long-running computation or frame drop in one view cannot stall the UI thread of another view, while native memory overhead remains low (~180 KB per additional isolate). +2. **Single-Isolate Multiview (`FlutterEngineAddView` / `FlutterEngineRemoveView`)**: A single `FlutterEngine` and single Dart isolate drive multiple `FlutterView` targets simultaneously, allowing widgets in distinct native windows or surfaces to read and mutate the same Dart object graph directly without cross-isolate message serialization. + +Migrating the Android embedder onto `embedder.h` and `FlutterCompositor` unlocks both patterns: `FlutterEngineSpawn` supports `FlutterEngineGroup`, while per-view `FlutterCompositor` callbacks (`FlutterPresentViewInfo.view_id` and `FlutterBackingStoreConfig.view_id`) provide the foundation for single-isolate multiview and multi-window rendering. + +### Renderer Availability, Multi-Window Lifecycle, and `FlutterViewId` Keying + +Android window surfaces are created and destroyed dynamically by the operating system during [`Activity`](https://developer.android.com/reference/android/app/Activity) transitions, configuration changes, or backgrounding. The embedder must coordinate surface availability and GPU context lifecycle without calling protected methods on [`PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L51). + +#### Orthogonality of Configuration and Availability + +A central design principle of the C-API architecture is that renderer configuration and surface availability are orthogonal concerns: + +1. Static graphics configuration (such as the Vulkan device, queue handles, or EGL context) persists inside [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) throughout the engine's lifetime. +2. Presentation targets ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) attach and detach across [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events. +3. GPU execution permission toggles between foreground and background states to satisfy OS process management policies. + +Attempting to recreate the entire `FlutterRendererConfig` during window lifecycle changes introduces severe driver overhead and destroys pipeline caches. Instead, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) decouples static configuration from dynamic surface availability: + +```c +/// GPU availability states for FlutterEngineSetGpuAvailability. +typedef enum { + /// GPU operations permitted. Rasterizer executes normally. + kFlutterGpuAvailabilityAvailable = 0, + /// Intermediate backgrounding state: synchronously flushes pending command + /// queues, drains resource deletions on the IO task runner, and marks the + /// GPU unavailable before process suspension. + kFlutterGpuAvailabilityFlushAndMakeUnavailable = 1, + /// Blocks raster thread from submitting commands to the GPU driver. + kFlutterGpuAvailabilityUnavailable = 2, +} FlutterGpuAvailability; + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyCreated( + FLUTTER_API_SYMBOL(FlutterEngine) engine); + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyDestroyed( + FLUTTER_API_SYMBOL(FlutterEngine) engine); + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSetGpuAvailability( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterGpuAvailability availability); +``` + +> [!IMPORTANT] [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) is proposed, not existing API +> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are already in `embedder.h`. `FlutterEngineSetGpuAvailability` is not. It is proposed here as a thin exposure of [`Shell::SetGpuAvailability`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L404), which the engine already implements and which the iOS embedder already drives. The proposal adds no new engine behavior; it gives the C-API access to behavior that exists. + +#### Implicit Single-View vs. Explicit Multi-View Surface Lifecycle + +In `embedder.h`, `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` manage the surface lifecycle of the implicit default view (`kFlutterImplicitViewId = 0`). When an Android application attaches secondary `FlutterView` instances to the same `FlutterEngine` (such as foldable dual-screen postures, secondary [`android.app.Presentation`](https://developer.android.com/reference/android/app/Presentation) external displays, or multi-surface Add-to-App hosts), the embedder registers and removes secondary views via [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) and [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062): + +- `FlutterCompositor.present_view_callback` receives a [`FlutterPresentViewInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1498) struct carrying the target `FlutterViewId view_id`, and `FlutterCompositor.create_backing_store_callback` receives `FlutterBackingStoreConfig.view_id`. +- To support multi-view and multi-window rendering without cross-view state corruption, `FlutterEmbedderNative` and `AndroidSurfaceControl` key native window handles (`ANativeWindow*`) and `ASurfaceControl*` layer trees by `FlutterViewId` rather than storing a single engine-global surface pointer. + +#### Lifecycle Mapping and Synchronous Destruction Contract + +These entry points map to Android [`SurfaceHolder.Callback`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback) and [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events: + +- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder binds the native window ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) for the target `FlutterViewId` and calls `FlutterEngineNotifyCreated` (for the implicit view) or `FlutterEngineAddView` (for secondary views) to allocate swapchains and resume rendering. +- [`surfaceChanged`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder,%20int,%20int,%20int)): The embedder transmits physical dimension and scale changes to the engine via `FlutterEngineSendWindowMetricsEvent` with the target `view_id`. +- [`surfaceDestroyed`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)): **Hard Synchronous Contract**. The embedder calls `FlutterEngineNotifyDestroyed` (or `FlutterEngineRemoveView`). The engine flushes pending rasterization workloads, tears down on-screen swapchains, drains pending resource deletions on the IO thread, and releases native window references synchronously before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns to the Android OS. This prevents `SIGSEGV` or `EGL_BAD_NATIVE_WINDOW` errors caused by subsequent rasterization attempts on invalidated window handles. +- [`Activity`](https://developer.android.com/reference/android/app/Activity) [`onStop`](https://developer.android.com/reference/android/app/Activity#onStop()) / [`onStart`](https://developer.android.com/reference/android/app/Activity#onStart()): When an application is backgrounded, the OS may reclaim volatile GPU allocations. The embedder invokes `FlutterEngineSetGpuAvailability(engine, kFlutterGpuAvailabilityFlushAndMakeUnavailable)` followed by `kFlutterGpuAvailabilityUnavailable` to drain command buffers and suspend raster tasks without terminating the engine or tearing down isolate groups. When the [`Activity`](https://developer.android.com/reference/android/app/Activity) returns to the foreground, `FlutterEngineSetGpuAvailability(engine, kFlutterGpuAvailabilityAvailable)` restores raster scheduling. + +#### Cross-Platform Alignment with iOS + +This lifecycle mechanism establishes direct parity with the iOS engine backgrounding contract in [`shell/platform/darwin/ios/framework/Source/FlutterEngine.mm`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm#L857) and [`Shell::SetGpuAvailability`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L64): + +- On iOS, submitting GPU commands while in the background triggers fatal OS process termination (Jetsam `0x8badf00d`). iOS calls `SetGpuAvailability(kFlushAndMakeUnavailable)` upon backgrounding to flush pending work and prevent crashes. +- On Android, attempting GPU operations after window detachment risks driver timeouts (`VK_ERROR_DEVICE_LOST`) or memory corruption when the OS evicts application GPU memory. +- `FlutterEngineSetGpuAvailability` provides a unified, cross-platform C-API abstraction that satisfies both Android and iOS operational requirements. + +### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State + +Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). + +Unlike legacy Hybrid Composition (see Dynamic Raster Thread Merging), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: + +- Rasterization and Android view updates execute on separate threads without leasing or merging overhead. +- The embedder arranges the base Flutter surface, native platform views, and overlay slices into a tree of [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) nodes. +- Visual synchronization occurs at the GPU level via hardware sync fences ([`ASyncFence`](https://developer.android.com/ndk/reference/group/sync) or Vulkan binary semaphores) passed through [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits, synchronizing buffer availability without CPU blocking. +- **Per-`FlutterViewId` Value-Captured Frame State**: In a multiview or multi-window environment, the raster thread renders View A (`FlutterPresentViewInfo.view_id = 0`) and immediately begins rasterizing View B (`view_id = 1`) while the platform thread is still applying View A's platform view layout mutations. Storing per-frame composition state (such as `composition_order_`, `picture_bounds_`, or `views_to_recomposite_`) as mutable fields on a shared controller causes cross-thread data races between consecutive views. `AndroidPlatformViewsController` eliminates this hazard by keying active platform view hierarchies by `FlutterViewId` and capturing each view's ordered `FlutterLayer` slice and `FlutterPlatformViewMutation` parameters by value inside the `present_view_callback` closure before dispatching to the platform thread. + +To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the public C-API provides multi-layer compositing primitives through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2322): + +1. **Compositor Backing Stores**: The embedder registers a `FlutterCompositor` configuration where `create_backing_store_callback` allocates [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains managed by an embedder-owned surface pool (`AndroidSurfaceControl`). +2. **Layer Slicing and Dispatch**: During frame presentation, `present_view_callback` (or `present_layers_callback`) delivers an ordered list of `FlutterLayer` items scoped to `FlutterPresentViewInfo.view_id`: + - `kFlutterLayerContentTypeBackingStore`: Rendered Flutter graphics slices (base surface or overlay surfaces). + - `kFlutterLayerContentTypePlatformView`: Embedded native Android [`View`](https://developer.android.com/reference/android/view/View) nodes, accompanied by [`FlutterPlatformViewIdentifier`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1883) and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) arrays (see Platform View Mutator Stack Serialization). +3. **Fence Transfer and Atomic Commit**: In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), extensions are added through [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198) instead of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L663). `FlutterBackingStorePresentInfo` is extended in place with a synchronization fence file descriptor: + +```c +typedef struct { + /// The size of this struct. Must be sizeof(FlutterBackingStorePresentInfo). + size_t struct_size; + + /// The area of the backing store that contains Flutter contents. + FlutterRegion* paint_region; + + /// File descriptor representing a sync_fence / ASyncFence. + /// When presenting on Android API 34+ via SurfaceControl.Transaction, + /// the engine passes this fence to the embedder. + /// + /// The embedder takes ownership of this file descriptor and must close it + /// (for example, by passing it to SurfaceControl.Transaction.setBuffer or + /// calling close()). A value of -1 indicates no fence. + int synchronization_fence_fd; +} FlutterBackingStorePresentInfo; +``` + +#### Synchronization Fence Lifecycle and Ownership + +1. **GPU Fence Export**: When Impeller submits render passes for a backing store, it exports the GPU completion semaphore or fence into a POSIX `sync_fence` file descriptor using the [`VK_KHR_external_fence_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_fence_fd.html) extension (`vkGetFenceFdKHR`). +2. **Transfer to Embedder**: The engine populates `FlutterBackingStorePresentInfo.synchronization_fence_fd` and delivers the layers to `present_view_callback`. Ownership of the file descriptor transfers to the embedder. +3. **Buffer Attachment**: In HCPP mode, `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered `FlutterLayer` sequence directly into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`). The embedder attaches the backing store `AHardwareBuffer` and fence file descriptor to the layer via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)). Passing the fence to Android's transaction wraps or closes the descriptor; if unused or on error, the embedder must close it with `::close(fd)`. +4. **SurfaceFlinger Wait & Buffer Release**: Android's window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) waits on the acquire fence on the GPU before compositing. Once compositing completes, SurfaceFlinger signals the transaction's release fence callback, which enables the embedder to recycle the backing store back to its surface pool. + +```mermaid +sequenceDiagram + autonumber + participant Impeller as Impeller Vulkan Backend + participant Engine as Flutter Engine Core + participant Embedder as Android Embedder C++ + participant SC as SurfaceControl.Transaction Java and NDK + participant SF as SurfaceFlinger Android OS + + Impeller->>Impeller: Submit Vulkan render commands + Impeller->>Impeller: Export VkFence / VkSemaphore to sync_fence fd via vkGetFenceFdKHR + Impeller->>Engine: Deliver backing store + fence fd + Engine->>Embedder: present_view_callback with view_id and synchronization_fence_fd + Embedder->>SC: setBuffer with surfaceControl, hardwareBuffer, fenceFd + Embedder->>SC: applyTransactions or swapTransactions + SC->>SF: Commit transaction with acquire fence + SF->>SF: GPU waits on acquire fence before compositing + SF-->>Embedder: Release fence signaled when buffer reading completes + Embedder->>Embedder: Return buffer to AndroidSurfaceControl for recycling +``` + +> [!NOTE] Compositor Simplification: Dropping Skia and Legacy Hybrid Composition +> Dropping legacy Skia and legacy Hybrid Composition collapses platform view composition on Android into exactly two asynchronous Impeller pipelines: HCPP on Android API 34+ and TLHC on older releases. This completely eliminates dynamic thread leasing, Virtual Display mode, and offscreen Skia canvas allocations. + +### Task Runner Configuration, Thread Priorities, and Threading Modes + +The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (platform task runner) alongside dedicated worker threads (UI, raster, and IO task runners). + +Thread priority configuration on Android requires system-level tuning. In the existing architecture, [`AndroidPlatformThreadConfigSetter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35) (located in [`android_shell_holder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35-L75)) uses `setpriority()` system calls to set Linux thread scheduling priorities: + +```cpp +// Existing thread priority configuration in shell/platform/android/android_shell_holder.cc +static void AndroidPlatformThreadConfigSetter( + const fml::Thread::ThreadConfig& config) { + fml::Thread::SetCurrentThreadName(config); + switch (config.priority) { + case fml::Thread::ThreadPriority::kBackground: { + fml::RequestAffinity(fml::CpuAffinity::kEfficiency); + if (::setpriority(PRIO_PROCESS, 0, 10) != 0) { + FML_LOG(ERROR) << "Failed to set IO task runner priority"; + } + break; + } + case fml::Thread::ThreadPriority::kDisplay: { + fml::RequestAffinity(fml::CpuAffinity::kNotEfficiency); + if (::setpriority(PRIO_PROCESS, 0, -1) != 0) { + FML_LOG(ERROR) << "Failed to set UI task runner priority"; + } + break; + } + case fml::Thread::ThreadPriority::kRaster: { + fml::RequestAffinity(fml::CpuAffinity::kNotEfficiency); + if (::setpriority(PRIO_PROCESS, 0, -5) != 0) { + if (::setpriority(PRIO_PROCESS, 0, -2) != 0) { + FML_LOG(ERROR) << "Failed to set raster task runner priority"; + } + } + break; + } + default: + fml::RequestAffinity(fml::CpuAffinity::kNotPerformance); + if (::setpriority(PRIO_PROCESS, 0, 0) != 0) { + FML_LOG(ERROR) << "Failed to set priority"; + } + } +} +``` + +The C-API embedder wires [`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1961) in [`FlutterCustomTaskRunners`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1961) to execute `setpriority()`. This reconciles the priority discrepancy between Android's embedder (which configures the IO task runner to `kNormal` priority, Linux nice value 0) and the engine-managed default (`kBackground`, Linux nice value 10). + +Internal consumers relying on the `kMergeAfterLaunch` threading model are supported without adding public fields to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). The embedder passes the engine switch `--merged-platform-ui-thread=mergeAfterLaunch` via `FlutterProjectArgs.command_line_argv`. In Java, [`FlutterLoader.ensureInitializationComplete`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L162) synthesizes this command-line argument dynamically from application manifest metadata and runtime configuration. + +### Process-Scoped Utilities, Image Decoding, and Diagnostics C-APIs + +Several Android JNI entry points in `FlutterMain` and `FlutterJNI` interact with engine utilities outside the scope of a single frame or require decoding and diagnostic primitives that previously pulled `//flutter/lib/ui`, `//flutter/txt`, `//flutter/runtime`, and `//flutter/skia` headers into `shell/platform/android/`. To sever those dependencies completely, `embedder.h` exposes five focused C-API groups: + +1. **Process-Scoped Callback Cache (`//flutter/lib/ui/plugins/callback_cache.h` Severance)**: Background plugins that execute Dart entrypoints without an active `FlutterView` (such as background alarm or geofencing isolates) resolve Dart function handles via [`FlutterCallbackCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h). `embedder.h` exposes process-scoped C entry points (`FlutterEngineSetCallbackCachePath`, `FlutterEngineLoadCallbackCache`, `FlutterEngineGetCallbackInformation`, and `FlutterEngineGetCallbackHandle`) along with `FlutterCallbackInformation` (`name`, `class_name`, `library_path`) so `FlutterEmbedderNative` and `FlutterMain` can query and persist callback handles without including `//flutter/lib/ui`. +2. **Hardware-Accelerated Image Decoding (`//flutter/lib/ui/painting/image_generator.h` and `//flutter/skia` Severance)**: Android API 28+ supports hardware-accelerated image decoding via `ImageDecoder`. To register platform image decoders without subclassing internal C++ `flutter::ImageGenerator` or `SkImageGenerator` types, `embedder.h` adds `FlutterEngineRegisterImageGenerator` (`FlutterImageGeneratorRegistrationInfo`, `FlutterImageGeneratorFactoryCallback`, `FlutterImageGeneratorGetInfoCallback`, and `FlutterImageGeneratorDecodeCallback`) backed by an internal `EmbedderImageLRU` cache inside `shell/platform/embedder/`. +3. **Font Manager Prefetching (`//flutter/txt` Severance)**: During `FlutterMain.init`, Android warms up the default system font manager on a background worker. `FlutterEnginePrefetchDefaultFontManager()` exposes this one-time initialization in `embedder.h` so `flutter_main.cc` no longer includes `flutter/txt/src/txt/platform.h`. +4. **VM Service URI Discovery (`//flutter/runtime/dart_service_isolate.h` Severance)**: Tooling and integration tests discover the Dart VM Service observatory URL via `FlutterJNI.getVMServiceUri`. `FlutterEngineRegisterVMServiceUriCallback` and `FlutterEngineDeregisterVMServiceUriCallback` deliver the URI string asynchronously via a `FlutterVMServiceUriCallback` handle, removing the last `//flutter/runtime` header from `shell/platform/android/`. +5. **Synchronous Rasterizer Screenshots**: `FlutterEngineScreenshot` and `FlutterEngineFreeScreenshot` capture uncompressed RGBA pixel buffers (`FlutterEngineScreenshotData`) from the active rasterizer to service `FlutterJNI.takeScreenshot` without reaching into `flutter::Rasterizer::Screenshot`. + +### Platform Channel Messaging, Background Handlers, and Task Queues + +In the existing Android architecture, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false` to permit message dispatch from arbitrary threads: + +```cpp +// Existing Android message dispatch in shell/platform/android/platform_message_handler_android.h +class PlatformMessageHandlerAndroid : public PlatformMessageHandler { + public: + bool DoesHandlePlatformMessageOnPlatformThread() const override { + return false; + } + void HandlePlatformMessage(std::unique_ptr message) override; +}; +``` + +In [`platform_message_handler_android.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.cc), [`HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h) dispatches the message directly across JNI without posting to the platform thread first: + +```cpp +// Existing Android message dispatch in shell/platform/android/platform_message_handler_android.cc +void PlatformMessageHandlerAndroid::HandlePlatformMessage( + std::unique_ptr message) { + // Called from any thread. + int response_id = next_response_id_.fetch_add(1); + if (auto response = message->response()) { + std::lock_guard lock(pending_responses_mutex_); + pending_responses_[response_id] = response; + } + jni_facade_->FlutterViewHandlePlatformMessage(std::move(message), + response_id); +} +``` + +In contrast, desktop embedders using [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) return `true` (enforcing platform-thread handling in [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L34-L36)): + +```cpp +// Desktop embedder behavior in shell/platform/embedder/platform_view_embedder.cc +virtual bool DoesHandlePlatformMessageOnPlatformThread() const { + return true; +} +``` + +Forcing mobile platforms to conform to desktop's `true` model would introduce severe performance and architectural regressions. + +#### Architectural Rationale for Off-Platform-Thread Dispatch + +Returning `false` provides two critical architectural capabilities on mobile platforms: + +1. **Zero Main-Thread Contention for Background Channels**: Flutter plugins handling high-bandwidth or compute-heavy workloads (such as camera stream ingestion, local database operations via SQLite, network responses, and cryptography) register background task queues via `BinaryMessenger.makeBackgroundTaskQueue()`. Because message dispatch does not force an intermediate hop to the platform thread, this traffic bypasses Android's main [`Looper`](https://developer.android.com/reference/android/os/Looper) entirely. The platform main thread remains dedicated to view layout, input processing, and frame presentation. +2. **Elimination of Latency and Head-of-Line Blocking**: Forcing a hop from the engine UI thread through the platform thread before dispatching to a background worker pool introduces an extra event loop dispatch cycle and thread context switch. If the Android main thread is blocked performing expensive view inflation or layout measurement passes, background channel execution stalls behind that work, which negates the advantage of offloading work to background workers. + +#### C-ABI Specification + +To reconcile this discrepancy without breaking existing desktop embedders, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces an explicit configuration field and an extended callback: + +```c +/// Callback invoked when the engine delivers a platform message from Dart. +/// When does_handle_platform_messages_on_platform_thread is false, this +/// callback may be invoked from any thread (typically the engine UI thread). +/// Embedders are responsible for dispatching to the appropriate task runner +/// or background worker pool. +typedef void (*FlutterPlatformMessageCallback2)( + const FlutterPlatformMessage* message, + void* user_data); +``` + +In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480): + +```c + /// When true (or zero-initialized default for legacy embedders), the engine + /// routes all incoming platform messages to the platform task runner before + /// invoking platform_message_callback. + /// + /// When false, the engine invokes platform_message_callback2 (or + /// platform_message_callback) directly on the originating thread + /// (typically the UI task runner) so the embedder can route + /// directly to background worker queues without platform thread hopping. + bool does_handle_platform_messages_on_platform_thread; +``` + +#### Android and iOS Cross-Platform Parity + +This configuration field satisfies both mobile embedder targets: + +- **Zero-Initialization Safety**: Existing desktop embedders (Linux, macOS, Windows) zero-initialize `FlutterProjectArgs`, which defaults the field to `true` and preserves their single-threaded platform message assumptions. +- **Mobile Embedder Parity**: Both Android and iOS explicitly set `does_handle_platform_messages_on_platform_thread = false`. On iOS, `PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()` returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`FlutterPlatformMessageCallback2`](#platform-message-routing-and-channel-registration) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): + - If registered with a `BinaryMessenger.TaskQueue`, the task is dispatched directly to that queue's background executor pool. + - If registered on the default queue, the task posts to the Android platform [`Looper`](https://developer.android.com/reference/android/os/Looper). + - Responses submitted via [`FlutterEngineSendPlatformMessageResponse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3133) execute safely from any thread without main-thread hops. + +For empty platform messages, the C-API guarantees that a message where `message_size == 0` and `message == nullptr` is delivered to the framework as an empty byte buffer without triggering null-pointer dereferences or serialization assertions. + +### Pointer Input Events, Viewport Metrics, and System Configuration + +#### Pointer Input Events + +Android touch, stylus, and mouse interactions processed in [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L434) provide axis and device metadata. The C-API embedder populates [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1377) with: + +- Device kind: `kFlutterPointerDeviceKindTouch`, `kFlutterPointerDeviceKindMouse`, `kFlutterPointerDeviceKindStylus`, or `kFlutterPointerDeviceKindTrackpad`. +- Axis values: `tilt`, `orientation`, `pressure`, and secondary scroll axes. +- Button states: primary, secondary, and stylus barrel buttons. + +#### Viewport Metrics, Edge-to-Edge Insets, and Foldables + +Android delivers window configuration metrics through [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433): + +- Physical padding (status bar, navigation bar, display cutouts) +- Physical view insets (software IME keyboard) +- System gesture insets (Android 10+ edge back gestures) +- Physical touch slop threshold for scroll disambiguation +- Physical display corner radii +- Display features (hinge bounds, display feature types, and device posture state) + +[`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) in `embedder.h` historically provided only width, height, and pixel ratio. + +Rather than creating a separate struct, `FlutterWindowMetricsEvent` is extended directly in place: + +1. `FlutterWindowMetricsEvent` contains a `struct_size` field and is passed by pointer (`const FlutterWindowMetricsEvent* event`). +2. This follows the existing precedent in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), which appended window constraint fields directly to `FlutterWindowMetricsEvent`. +3. Engine implementations use `SAFE_ACCESS(event, member, default_value)` to read new fields, which guarantees that existing desktop embedders passing the baseline struct size continue to work without modification. + +```c +typedef struct { + double left; + double top; + double right; + double bottom; +} FlutterRectInsets; + +typedef enum { + kFlutterDisplayFeatureTypeUnknown = 0, + kFlutterDisplayFeatureTypeFold = 1, + kFlutterDisplayFeatureTypeHinge = 2, + kFlutterDisplayFeatureTypeCutout = 3, +} FlutterDisplayFeatureType; + +typedef enum { + kFlutterDisplayFeatureStateUnknown = 0, + kFlutterDisplayFeatureStatePostureFlat = 1, + kFlutterDisplayFeatureStatePostureHalfOpened = 2, +} FlutterDisplayFeatureState; + +typedef struct { + size_t struct_size; + FlutterRect bounds; + FlutterDisplayFeatureType type; + FlutterDisplayFeatureState state; +} FlutterDisplayFeature; +``` + +The following fields are appended to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110): + +```c + /// Physical window padding (for example, status bar, navigation bar, cutouts). + FlutterRectInsets physical_padding; + + /// System gesture insets (for example, Android 10+ edge back gestures). + FlutterRectInsets physical_system_gesture_insets; + + /// Physical touch slop threshold for scroll disambiguation. + double physical_touch_slop; + + /// Display corner radii in physical pixels. + double display_corner_radius_top_left; + double display_corner_radius_top_right; + double display_corner_radius_bottom_right; + double display_corner_radius_bottom_left; + + /// Number of display features in the `display_features` array. + size_t display_features_count; + + /// Array of physical display features (foldable hinges, cutouts). + const FlutterDisplayFeature* display_features; +``` + +#### System Channels: Locale and Non-Linear Font Scaling + +Features that communicate standard framework and platform configuration use platform message channels rather than custom additions to `embedder.h`: + +1. **Locale Configuration**: The embedder uses the standard `flutter/localization` platform message channel ([`LocalizationChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java#L61-L97)). In the engine ([`embedder.cc:3423-3480`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3423-L3480)), [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3552) serializes locale data into a JSON message on this channel. No new C-API entry point is added for locale settings. + +2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses non-linear font scaling curves so that large typography scales at lower ratios than body text, which prevents UI clipping. In the legacy architecture, this was serviced through a synchronous JNI query ([`PlatformDispatcher`](https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher-class.html).`_getScaledFontSize` -> [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h)), which does not exist in `PlatformViewEmbedder` and triggers `FML_UNREACHABLE()`. + + To support non-linear scaling without synchronous engine queries or new C-APIs, the existing `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) is extended: + + - During startup and configuration changes, the embedder extracts curve sample points from Android's [`FontScaleConverter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/FontScaleConverter.java) and includes them in the `flutter/settings` message payload: + + ```json + { + "textScaleFactor": 1.15, + "platformBrightness": "light", + "alwaysUse24HourFormat": false, + "fontScaleCurves": [ + {"unscaled": 8.0, "scaled": 9.2}, + {"unscaled": 14.0, "scaled": 15.4}, + {"unscaled": 20.0, "scaled": 21.0} + ] + } + ``` + + - Dart's [`PlatformDispatcher`](https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher-class.html) caches these sample points and interpolates scaled font sizes locally in Dart. This avoids synchronous JNI calls during text layout and resolves the `FML_UNREACHABLE()` abort under `embedder.h`. + +## Target Architecture and Design Principles + +The target architecture establishes a strict C-ABI boundary between the Android embedder and the engine core, replacing the legacy `AndroidShellHolder` and `PlatformViewAndroid` hierarchy with `FlutterEmbedderNative` and modular, single-responsibility C-API components. + +```mermaid +flowchart TD + subgraph Java Layer + Activity["FlutterActivity / FlutterFragment"] + FlutterView["FlutterView"] + FlutterJNI["FlutterJNI"] + end + + subgraph Android Embedder C++: flutter_embedder_native_src + FlutterEmbedderNative["FlutterEmbedderNative: C-API Client & JNI Coordinator"] + JniRouter["JniRouter / JniDelegate / JvmInvoker"] + SurfaceControl["AndroidSurfaceControl"] + PlatformViews["AndroidPlatformViewsController & AndroidMutatorsMapper"] + Textures["AndroidHardwareBuffer & AndroidVulkanExternalTexture"] + Mappers["AndroidSemanticsMapper & AndroidWindowMetricsMapper"] + end + + subgraph Public C-API Boundary + EmbedderH["embedder.h: FlutterEngineProcTable (FLUTTER_ENGINE_NO_PROTOTYPES)"] + end + + subgraph Flutter Engine Core + FlutterEngineOpaque["FlutterEngine Opaque Handle"] + Shell["Shell"] + ImpellerFlow["Impeller / Flow Compositor"] + DartVM["Dart VM"] + end + + Activity --> FlutterView + FlutterView --> FlutterJNI + FlutterJNI --> FlutterEmbedderNative + FlutterEmbedderNative --> JniRouter + FlutterEmbedderNative --> SurfaceControl + FlutterEmbedderNative --> PlatformViews + FlutterEmbedderNative --> Textures + FlutterEmbedderNative --> Mappers + FlutterEmbedderNative --> EmbedderH + EmbedderH --> FlutterEngineOpaque + FlutterEngineOpaque --> Shell + Shell --> ImpellerFlow + Shell --> DartVM +``` + +```mermaid +sequenceDiagram + participant FlutterJNI + participant FlutterEmbedderNative as FlutterEmbedderNative: C-API Client + participant EmbedderAPI as embedder.h ProcTable + participant Core as FlutterEngine Core + + FlutterJNI->>FlutterEmbedderNative: Initialize with Surface, Assets, Looper + FlutterEmbedderNative->>EmbedderAPI: FlutterEngineInitialize with project_args + EmbedderAPI->>Core: Shell::Create with configuration + Core-->>EmbedderAPI: Shell Instance + EmbedderAPI-->>FlutterEmbedderNative: FlutterEngine Opaque Handle + FlutterJNI->>FlutterEmbedderNative: Launch with entrypoint and args + FlutterEmbedderNative->>EmbedderAPI: FlutterEngineRunInitialized with engine_handle + EmbedderAPI->>Core: RunEngine with configuration +``` + +### Pure Embedder API Client (`FLUTTER_ENGINE_NO_PROTOTYPES`) + +In the target architecture: + +- `FlutterEmbedderNative` holds only an opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) handle and dispatches all engine calls through an initialized `FlutterEngineProcTable` (`FlutterEngineGetProcAddresses`). +- The `:flutter_embedder_native_src` GN source set compiles with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`, ensuring at compile time that all C-API symbols are invoked through the function pointer table rather than direct static symbol linkage. +- Event dispatch (touch, window metrics, platform messages, accessibility actions) calls public functions: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082), [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077), [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114), and [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398). +- Engine initialization delegates thread creation to the engine via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006). + +### Decomposition and Elimination of [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) + +In the legacy architecture, the native Android embedding is split across three tightly coupled classes that reach directly into 9 internal engine subsystems (98 internal headers): + +1. [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc): A monolithic JNI binding file that directly casts `jlong` handles to `AndroidShellHolder*` and calls internal C++ methods across `Shell` and `PlatformViewAndroid`. +2. [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38): The legacy C++ orchestrator instantiated by [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108). It allocates `fml::ThreadHost`, instantiates [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113), and owns [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43). +3. [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43): An internal engine class subclassing [`flutter::PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h) that centralizes window surfaces, platform view composition, external textures, and semantics. + +In the completed target architecture, `AndroidShellHolder`, `PlatformViewAndroid`, and `PlatformViewAndroidJNIImpl` are completely deleted. In their place, `:flutter_embedder_native_src` organizes the Android embedder into a clean coordinator-component architecture: + +- `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Serves as the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in `library_loader.cc`). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. +- `JniRouter`, `JniDelegate`, and `JvmInvoker` (`jni_router.h`, `jni_delegate.h`, `jvm_invoker.h`): Decouple outbound C++-to-Java JNI invocations from live Android `JNIEnv*` state through injectable delegate and provider interfaces (`FlutterJniProvider`, `HardwareBufferProvider`, `SurfaceControlProvider`, `SurfaceTransactionProvider`, and `ChoreographerProvider`). This allows the entire C-API embedder (`flutter_embedder_native_unittests`) to be unit-tested on the host machine without an Android JVM or device. +- `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, enforces synchronous surface destruction contracts before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns, and suspends GPU command submission during application backgrounding via `FlutterEngineSetGpuAvailability`. +- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+) and Texture Layer Hybrid Composition (TLHC), deserialize `FlutterPlatformViewMutation` arrays, capture per-`FlutterViewId` frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). +- `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` (`android_hardware_buffer.cc`, `android_vulkan_external_texture.cc`): Manage [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (OpenGL ES) with 4x4 UV transformation matrices (`FlutterOpenGLTexture2`) and zero-copy [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) ([`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) / Vulkan) texture callbacks (`FlutterVulkanExternalTextureFrameCallback` and `FlutterHardwareBufferExternalTextureFrameCallback`). +- `AndroidSemanticsMapper` and `AndroidWindowMetricsMapper` (`android_semantics_mapper.cc`, `android_window_metrics_mapper.cc`): Translate `FlutterSemanticsUpdate2` trees into JNI byte buffers for Java `AccessibilityBridge` and pack viewport metrics, insets, corner radii, and foldable display features into `FlutterWindowMetricsEvent`. +- `AndroidVsyncWaiter` (`android_vsync_waiter.cc`): Bridges Android NDK [`AChoreographer`](https://developer.android.com/ndk/reference/group/choreographer) and Java [`Choreographer`](https://developer.android.com/reference/android/view/Choreographer) frame callbacks to `FlutterProjectArgs.vsync_callback` and forwards frame pacing timestamps via `FlutterEngineOnVsync`. +- `AndroidVMInit` and `AndroidEngineGroup` (`android_vm_init.cc`, `android_engine_group.cc`): Coordinate initial Dart VM argument synthesis, Linux thread priority tuning (`thread_priority_setter`), and multi-engine group spawning via `FlutterEngineSpawn`. +- `APKAssetProvider` (`apk_asset_provider.cc`): Implements `FlutterCustomAssetResolver` to stream kernel blobs and application assets directly from Android APK or AAB archives via NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) memory mapping without disk extraction. + +```mermaid +flowchart TD + subgraph Legacy["Legacy Monolithic Architecture (Deleted in Phase 4)"] + direction TB + JNI_Legacy["FlutterJNI & PlatformViewAndroidJNIImpl"] + ASH_Legacy["AndroidShellHolder
JNI Anchor, ThreadHost, Shell Owner"] + PVA_Legacy["PlatformViewAndroid: Subclasses flutter::PlatformView
Window Surfaces, Platform Views, Textures, Semantics"] + VWA_Legacy["VsyncWaiterAndroid: Subclasses flutter::VsyncWaiter"] + + JNI_Legacy --> ASH_Legacy + ASH_Legacy --> PVA_Legacy + ASH_Legacy --> VWA_Legacy + end + + subgraph Modular["Completed Target Architecture: flutter_embedder_native_src"] + direction TB + JNI_Target["FlutterJNI"] + FEN_Target["FlutterEmbedderNative
C-API Client & JNI Coordinator"] + JNIR_Target["JniRouter / JniDelegate / JvmInvoker
Host-Testable Outbound JNI"] + ASC_Target["AndroidSurfaceControl: Keyed by FlutterViewId"] + APVC_Target["AndroidPlatformViewsController & AndroidMutatorsMapper"] + ATR_Target["AndroidHardwareBuffer & AndroidVulkanExternalTexture"] + AAB_Target["AndroidSemanticsMapper & AndroidWindowMetricsMapper"] + AVW_Target["AndroidVsyncWaiter"] + AEG_Target["AndroidVMInit & AndroidEngineGroup"] + APK_Target["APKAssetProvider: FlutterCustomAssetResolver"] + + JNI_Target --> FEN_Target + FEN_Target --> JNIR_Target + FEN_Target --> ASC_Target + FEN_Target --> APVC_Target + FEN_Target --> ATR_Target + FEN_Target --> AAB_Target + FEN_Target --> AVW_Target + FEN_Target --> AEG_Target + FEN_Target --> APK_Target + end +``` + +#### Mapping Embedder C-APIs to Target Architecture Components + +The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) correspond directly to severing private engine headers and uncoupling each internal subsystem into an independent target component: + +| Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | +| :--- | :--- | :--- | :--- | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#engine-spawning-and-multi-engine-management-flutterenginegroup) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.does_handle_platform_messages_on_platform_thread`
- [`FlutterPlatformMessageCallback2`](#platform-message-routing-and-channel-registration)
- `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | +| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`)
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation` and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | +| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-texture-composition-and-surfaceproducer) and [`FlutterVulkanExternalTextureFrameCallback`](#external-texture-composition-and-surfaceproducer)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-texture-composition-and-surfaceproducer)
- [`FlutterOpenGLTexture2`](#external-texture-composition-and-surfaceproducer) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | +| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | +| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolverByType` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | +| **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | + +## Phased Implementation Strategy + +To maintain continuous test coverage and avoid architectural traps, the migration executes in four sequential phases: + +```mermaid +flowchart LR + Phase1["Phase 1:
Embedder C-API
Extensions"] + Phase2["Phase 2:
Android Shell
Modularization"] + Phase3["Phase 3:
C-API Cutover
Behind Feature Flag"] + Phase4["Phase 4:
Build Decoupling
& CI Ratchet"] + + Phase1 --> Phase2 + Phase2 --> Phase3 + Phase3 --> Phase4 +``` + +### Phase 1: Embedder C-API Extensions ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)) + +All C-API extensions specified in Detailed Design are implemented in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc). + +Each new API is validated with automated engine unit tests ([`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L89)) using mock platform delegates. No Android-specific files are modified in Phase 1. + +Phase 1 must be fully merged and green across CI trees before Phase 2 begins. + +### Phase 2: Android Shell Modularization and GN Target Partitioning + +Phase 2 reorganizes the Android embedder C++ codebase in-place to establish clean component boundaries before enabling C-API cutover in Phase 3. In the existing architecture, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) are monolithic entities that conflate engine execution, platform message delivery, surface lifecycle management, external texture tracking, and platform view composition. + +Phase 2 accomplishes two mandatory structural goals: + +1. It establishes a hard compile-time firewall in GN (`:flutter_embedder_native_src`) that isolates new C-API code from legacy internal engine headers and enforces `FLUTTER_ENGINE_NO_PROTOTYPES`. +2. It decomposes the monolithic `PlatformViewAndroid` into focused modular components (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and `APKAssetProvider`) and decouples them from internal engine classes. + +#### Compile-Time Firewall via GN Target Partitioning + +To guarantee that new modular components cannot inadvertently include private engine headers (such as `//flutter/flow`, `//flutter/runtime`, `//flutter/skia`, or `//flutter/impeller`), [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates the new C-API implementation inside `source_set("flutter_embedder_native_src")`: + +```gn +# GN Target Partitioning in shell/platform/android/BUILD.gn: + +# 1. Modular C-API Source Set (Compile-Time Firewall) +source_set("flutter_embedder_native_src") { + visibility = [ ":*" ] + check_includes = true + defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ] + + sources = [ + "android_engine_group.cc", + "android_engine_group.h", + "android_hardware_buffer.cc", + "android_hardware_buffer.h", + "android_mutators_mapper.cc", + "android_mutators_mapper.h", + "android_platform_views_controller.cc", + "android_platform_views_controller.h", + "android_semantics_mapper.cc", + "android_semantics_mapper.h", + "android_surface_control.cc", + "android_surface_control.h", + "android_vm_init.cc", + "android_vm_init.h", + "android_vsync_waiter.cc", + "android_vsync_waiter.h", + "android_vulkan_external_texture.cc", + "android_vulkan_external_texture.h", + "android_window_metrics_mapper.cc", + "android_window_metrics_mapper.h", + "apk_asset_provider.cc", + "apk_asset_provider.h", + "flutter_embedder_native.cc", + "flutter_embedder_native.h", + "jni_delegate.h", + "jni_router.cc", + "jni_router.h", + "jvm_invoker.cc", + "jvm_invoker.h", + ] + + # Strict dependencies: ONLY public embedder headers and foundational utilities + deps = [ + "//flutter/assets", + "//flutter/common", + "//flutter/fml", + "//flutter/shell/platform/embedder:embedder_headers", + ] +} + +# 2. Outer Shell Source Set (Reduced to 3 files in Phase 4 once legacy classes are purged) +source_set("flutter_shell_native_src") { + visibility = [ ":*" ] + + sources = [ + "flutter_main.cc", + "flutter_main.h", + "library_loader.cc", + ] + + public_deps = [ + ":flutter_embedder_native_src", + "//flutter/fml", + "//flutter/shell/platform/embedder:embedder_as_internal_library", + ] +} +``` + +```mermaid +flowchart TD + subgraph BuildTarget["Build Target: //flutter/shell/platform/android:flutter_shell_native"] + SharedLib["shared_library flutter_shell_native
libflutter.so"] + ShellSrc["source_set flutter_shell_native_src
flutter_main.cc, library_loader.cc"] + end + + subgraph SourceSets["Partitioned Source Sets in BUILD.gn"] + CAPISourceSet["source_set flutter_embedder_native_src
defines = FLUTTER_ENGINE_NO_PROTOTYPES
----------------------------------------
flutter_embedder_native.cc
jni_router.cc / jni_delegate.h / jvm_invoker.cc
android_surface_control.cc
android_platform_views_controller.cc
android_hardware_buffer.cc / android_vulkan_external_texture.cc
android_semantics_mapper.cc / android_window_metrics_mapper.cc"] + LegacySourceSet["Transitional Phase 2-3 Legacy Files (Deleted in Phase 4)
----------------------------------------
android_shell_holder.cc
platform_view_android.cc / platform_view_android_jni_impl.cc
shell_android_engine.cc / embedder_android_engine.cc
external_view_embedder/"] + end + + subgraph AllowedCAPIDeps["Allowed Dependencies: Strict Firewall"] + EmbedderHeaders["//flutter/shell/platform/embedder:embedder_headers"] + EmbedderLib["//flutter/shell/platform/embedder:embedder_as_internal_library"] + FML["//flutter/fml, //flutter/common, //flutter/assets"] + end + + subgraph ForbiddenCAPIDeps["Forbidden Dependencies: Blocked by GN check_includes"] + Flow["//flutter/flow: Flow Layers, MutatorsStack, ExternalViewEmbedder"] + Runtime["//flutter/runtime: Dart VM, Isolates, Service Isolate"] + ShellCommon["//flutter/shell/common: Shell, Rasterizer, PlatformView"] + SkiaImpeller["//flutter/skia, //flutter/impeller, //flutter/lib/ui, //flutter/txt"] + end + + SharedLib --> ShellSrc + ShellSrc --> CAPISourceSet + ShellSrc -.->|Phases 2-3 Only| LegacySourceSet + + CAPISourceSet --> AllowedCAPIDeps + CAPISourceSet -.->|COMPILE ERROR if included| ForbiddenCAPIDeps + + LegacySourceSet --> ForbiddenCAPIDeps +``` + +GN enforces `check_includes = true` and `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` on `flutter_embedder_native_src`. If any file in `flutter_embedder_native_src` attempts to include a private engine header (such as [`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), or [`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h)) or call an `embedder.h` function outside `FlutterEngineProcTable`, the build fails immediately. + +#### Elimination and Decomposition of `AndroidShellHolder` and `PlatformViewAndroid` + +In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) directly [subclasses the internal engine class `flutter::PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L151-L160). This inheritance models Android as an internal engine subsystem rather than an external client embedder. Both `AndroidShellHolder` and `PlatformViewAndroid` are eliminated by decomposing their responsibilities across `FlutterEmbedderNative` and the modular components in `:flutter_embedder_native_src`. + +In contrast, external embedders communicating via [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) never subclass `flutter::PlatformView`. Instead, the embedder library (`//flutter/shell/platform/embedder:embedder_as_internal_library`) internally instantiates a generic [`flutter::PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) to satisfy the [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) class contract. + +#### Lifecycle and Deletion of Legacy Classes (`AndroidShellHolder` and `PlatformViewAndroid`) + +`AndroidShellHolder` and `PlatformViewAndroid` progress across four migration stages culminating in their complete removal: + +```mermaid +flowchart TD + Pre["Current State: Monolithic Production
AndroidShellHolder & PlatformViewAndroid active in flutter_shell_native_src.
All CI and presubmits exercise this path."] + + P2["Phase 2: Target Partitioning and Modularization
Modular C-API components built in flutter_embedder_native_src.
Transitional AndroidEngine seam bridges AndroidShellHolder during migration."] + + P3["Phase 3: Genuine C-API Cutover
FlutterEmbedderNative registers JNI bindings directly.
Internal PlatformViewEmbedder handles engine shell duties."] + + P4["Phase 4: Legacy Class Purge and GN Decoupling
git rm android_shell_holder.* platform_view_android* external_view_embedder/
flutter_shell_native_src reduced to flutter_main.cc & library_loader.cc."] + + Pre --> P2 + P2 --> P3 + P3 --> P4 +``` + +1. **Pre-Migration**: `AndroidShellHolder` and `PlatformViewAndroid` handle 100% of production traffic and link to 98 internal engine headers. +2. **Phase 2 (Partitioning & Strangler-Fig Seam)**: Modular components are constructed inside `flutter_embedder_native_src` with `check_includes = true` and `FLUTTER_ENGINE_NO_PROTOTYPES`. During incremental slice development, a transitional polymorphic delegate interface (`AndroidEngine`, implemented by `ShellAndroidEngine` and `EmbedderAndroidEngine`) allows individual subsystems to be migrated and tested against `AndroidShellHolder` without breaking existing `flutter_shell_native_unittests`. +3. **Phase 3 (C-API Cutover)**: `FlutterEmbedderNative` assumes direct ownership of the `FlutterJNI` native bindings (`FlutterEmbedderNative::RegisterJni(env)`) and drives the engine exclusively through `FlutterEngineProcTable`. Inside the core engine, the embedder runtime uses [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and `EmbedderExternalViewEmbedder` to fulfill the shell's internal requirements. +4. **Phase 4 (Legacy Class Purge & Build Decoupling)**: Once `FlutterEmbedderNative` and `flutter_embedder_native_unittests` reach full parity, `AndroidShellHolder`, `PlatformViewAndroid`, `PlatformViewAndroidJNIImpl`, `AndroidEngine`, `ShellAndroidEngine`, `EmbedderAndroidEngine`, and `external_view_embedder/` are permanently deleted from the repository (`legacy-class-purge`). `:flutter_shell_native_src` is reduced to just three files (`flutter_main.cc`, `flutter_main.h`, and `library_loader.cc`), and the Android embedder retains zero subclassing of internal engine classes. + +### Phase 3: Genuine C-API Cutover (`FlutterEmbedderNative`) + +In Phase 3, `FlutterEmbedderNative` initializes [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L398) via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006) through `FlutterEngineProcTable`. + +Event dispatch routes directly to public C-API entry points: + +- Touch events: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082) +- Viewport metrics: [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077) +- Platform messages: [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114) +- Semantics actions: [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398) +- Texture registrations: [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) +- Compositor and multi-surface presentation: [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2322) layer presentation (`present_view_callback`) coordinating [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits via `AndroidSurfaceControl` and `AndroidPlatformViewsController`. + +#### Transitional Polymorphic Delegation via `AndroidEngine` (Phases 2–3) + +During Phases 2 and 3, before `AndroidShellHolder` is deleted in Phase 4, incremental subsystem migration is bridged using the transitional `AndroidEngine` seam: + +1. **`ShellAndroidEngine` (Legacy Path)**: + - Directs lifecycle and events to internal engine components: [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L137), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), and [`fml::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L27). + - Retains private engine dependencies (`//flutter/flow`, `//flutter/runtime`, `//flutter/impeller`) until Phase 4 deletion. + +2. **`EmbedderAndroidEngine` / `FlutterEmbedderNative` (C-API Target Path)**: + - Encapsulates the opaque `FlutterEngine` handle and `FlutterEngineProcTable`. + - Forwards all operations strictly through the public C-API in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). + - Once `FlutterEmbedderNative` registers JNI directly in `library_loader.cc`, both `AndroidShellHolder` and the transitional `AndroidEngine` classes become dead code and are removed in Phase 4. + +### Phase 4: Build Decoupling and Legacy Class Purge + +Once `FlutterEmbedderNative` passes all functional and performance tests, [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) and `shell/platform/android/` are permanently decoupled: + +- Delete `AndroidShellHolder`, `PlatformViewAndroid`, `PlatformViewAndroidJNIImpl`, `AndroidEngine`, `EmbedderAndroidEngine`, `ShellAndroidEngine`, `VsyncWaiterAndroid`, `PlatformMessageHandlerAndroid`, `SurfaceTextureExternalTexture`, `ImageExternalTexture`, and the `external_view_embedder/` directory. +- Reduce `:flutter_shell_native_src` to `flutter_main.cc`, `flutter_main.h`, and `library_loader.cc` (which calls `FlutterEmbedderNative::RegisterJni(env)`). +- Remove dependencies on [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common). +- Restrict dependencies strictly to: + - [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L75) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`) + - [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) + - [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) + - [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) +- Lock the CI ratchet tool to zero allowed internal headers. + +### The Dependency Ratchet Mechanism + +In a large codebase with active development, multi-quarter refactoring efforts face persistent regression risks: developers resolving defects or introducing features may inadvertently add new `#include` directives referencing private engine headers, or introduce `// nogncheck` annotations to bypass visibility boundaries. + +To enforce that dependency reduction is strictly monotonic throughout the migration, an automated ratchet system operates across three distinct layers: + +```mermaid +flowchart TD + subgraph Layer1["Layer 1: GN Target Partitioning - Compile-Time Firewall"] + NewCAPI["flutter_embedder_native_src
FlutterEmbedderNative, AndroidSurfaceControl, etc."] + StrictDeps["Strict deps only:
embedder_headers, fml, common, assets"] + LegacyTarget["Transitional Legacy Files (Phases 2-3)
AndroidShellHolder, PlatformViewAndroid"] + LegacyDeps["Legacy internal deps:
//flutter/flow, //flutter/runtime, etc."] + + NewCAPI --> StrictDeps + LegacyTarget --> LegacyDeps + end + + subgraph Layer2["Layer 2: CI Presubmit Linter - Monotonic Header Ratchet"] + PR["Pull Request touching shell/platform/android/"] + Script["check_android_embedder_deps.py"] + YAML["allowed_internal_headers.yaml
Tracked baseline of 98 headers"] + Check1{"New internal header
or unlisted file?"} + Check2{"Include removed without
decrementing baseline?"} + Reject1["Reject PR: New internal header forbidden"] + Reject2["Reject PR: Ratchet down baseline in YAML"] + Accept["Pass Presubmit"] + + PR --> Script + YAML --> Script + Script --> Check1 + Check1 -- Yes --> Reject1 + Check1 -- No --> Check2 + Check2 -- Yes --> Reject2 + Check2 -- No --> Accept + end + + subgraph Layer3["Layer 3: Terminal Build Decoupling - Permanent Lock"] + DeleteLegacy["Purge legacy classes & external_view_embedder/"] + PruneDeps["Prune BUILD.gn to pure embedder targets"] + ZeroTolerance["Lock CI check to total_allowed_internal_headers: 0"] + + DeleteLegacy --> PruneDeps --> ZeroTolerance + end + + Layer1 --> Layer2 --> Layer3 +``` + +#### Layer 1: GN Target Partitioning (Compile-Time Firewall) + +In Phase 2, the Android native source set in [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all C-API code in `flutter_embedder_native_src` (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and `APKAssetProvider`). This target sets `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` and declares dependencies strictly on [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml), [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common), and [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets). Because GN evaluates `check_includes = true` during builds, any attempt to include headers from undeclared targets (such as [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) or [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime)) causes a compilation error immediately. + +#### Layer 2: Monotonic Header Inclusion Ratchet ([`check_android_embedder_deps.py`](#presubmit-header-allowlist-and-monotonic-decrement-ratchet)) + +To prevent the quarantined legacy files from accumulating additional internal includes during Phase 2 and Phase 3, an automated presubmit linter enforces a monotonic reduction policy. + +The linter references a version-controlled baseline configuration file located at [`shell/platform/android/allowed_internal_headers.yaml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android): + +```yaml +version: 1 +total_allowed_internal_headers: 98 +subsystems: + impeller: + count: 29 + allowed_headers: + - "flutter/impeller/renderer/backend/vulkan/context_vk.h" + - "flutter/impeller/renderer/backend/gles/context_gles.h" + - "flutter/impeller/toolkit/android/hardware_buffer.h" + flow: + count: 2 + allowed_headers: + - "flutter/flow/embedded_views.h" + - "flutter/flow/surface.h" + engine_core: + count: 11 + allowed_headers: + - "flutter/shell/common/shell.h" + - "flutter/shell/common/thread_host.h" + - "flutter/shell/common/platform_view.h" +``` + +The presubmit linter enforces four deterministic rules on incoming pull requests: + +1. *Zero new files*: Only source files explicitly registered in [`allowed_internal_headers.yaml`](#presubmit-header-allowlist-and-monotonic-decrement-ratchet) may contain internal engine includes. Any inclusion in an unlisted file fails presubmit. +2. *Zero new headers*: New `#include "flutter/..."` directives not in the allowlist fail presubmit, requiring the API to be routed through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). +3. *Monotonic decrement*: When an incremental refactoring removes an internal header, the pull request must remove it from `allowed_internal_headers.yaml` and decrement `total_allowed_internal_headers`. Once merged, the ratchet locks the lower count permanently. +4. *No escape hatches*: The linter parses [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) and rejects modifications introducing `// nogncheck` or widening GN target visibility. + +#### Layer 3: Terminal Build Decoupling (Permanent Lock) + +Following Phase 3 cutover validation: + +1. All legacy classes (`AndroidShellHolder`, `PlatformViewAndroid`, `PlatformViewAndroidJNIImpl`, `external_view_embedder/`) are deleted from the repository. +2. The allowlist configuration is locked permanently: + + ```yaml + total_allowed_internal_headers: 0 + subsystems: {} + ``` + +3. All internal GN dependencies ([`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common)) are pruned from [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn). +4. Any future pull request including an internal header will fail both `gn check` at compile time and the presubmit ratchet script, which permanently prevents architectural regressions. + +## Verification and Testing Plan + +Because Android is Flutter's largest mobile deployment target, verification must confirm that behavioral correctness, performance benchmarks, and accessibility support match the existing embedder. + +### Engine Unit Tests (`flutter_embedder_native_unittests`) + +- Host-executable C++ unit tests (`flutter_embedder_native_unittests`) for `FlutterEmbedderNative` and each modularized component (`AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, and `AndroidEngineGroup`) using mock `FlutterEngineProcTable` entries and injectable `JniDelegate` / `JvmInvoker` provider interfaces without requiring a live Android JVM or device. +- Unit tests validating the serialization and deserialization of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) structs and per-`FlutterViewId` frame state capture. + +### Android Integration Tests + +- [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L48) and `flutter_embedder_native_unittests` executed across the CI test matrix. +- Platform view lifecycle and composition tests verifying Hybrid Composition++ (HCPP with [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) on API 34+), legacy Hybrid Composition (HC), Texture Layer Hybrid Composition (TLHC), and Virtual Displays (VD) under both flag configurations. +- Android embedding integration tests verifying JNI message channels, background isolate callbacks, and deferred library loading. + +### DeviceLab Benchmarks + +- Frame pacing and rasterization latency benchmarks (comparing legacy vs C-API) to verify that the C-ABI abstraction does not introduce frame drops or scheduling jitter. +- Platform view scrolling benchmarks across HCPP and HC modes to measure GPU sync fence latency, [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) submission timing, and touch response. +- Startup latency benchmarks on low-end and high-end devices to verify that [`FlutterAssetResolver`](#custom-asset-and-kernel-resolution-apk--in-memory-mapping) memory mapping maintains zero-copy asset loading performance. + +### Golden and Conformance Tests + +- Platform view rendering goldens validating that clipping paths, rounded rectangles, and opacity mutators render identically under HCPP ([`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) Vulkan), HC, and TLHC. +- External texture playback verification across video player and camera plugins. + +### Pre-Existing Android Embedder Testing Gaps + +Historical testing in the Android embedder contains notable test coverage gaps, particularly around concurrency and OS lifecycle transitions. Relying solely on existing tests risks false-positive test runs where pre-existing bugs remain hidden during the refactor. The following test suites must be implemented up front prior to enabling the C-API embedder in production: + +1. **Thread-Sensitive Concurrency and Race Conditions**: + - Surface destruction concurrent with rasterizer frame submission: Native Android [`SurfaceView`](https://developer.android.com/reference/android/view/SurfaceView) and [`TextureView`](https://developer.android.com/reference/android/view/TextureView) lifecycles destroy surfaces asynchronously on the Android UI thread while the rasterizer submits draw calls on the raster thread. Dedicated concurrency tests must simulate rapid window detachment during active frame rendering to catch race conditions and dangling surface handles. + - Initialization and destruction races: Multi-threaded startup where Flutter engine initialization coincides with rapid [`Activity`](https://developer.android.com/reference/android/app/Activity) destruction to verify thread safety across [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108), [`ALooper`](https://developer.android.com/ndk/reference/group/looper) callbacks, and native task runners. + +2. **[`Activity`](https://developer.android.com/reference/android/app/Activity) and Window Lifecycle Transitions**: + - Full [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle permutations: Explicit tests exercising [`onPause`](https://developer.android.com/reference/android/app/Activity#onPause()) -> [`onResume`](https://developer.android.com/reference/android/app/Activity#onResume()), [`onStop`](https://developer.android.com/reference/android/app/Activity#onStop()) -> [`onRestart`](https://developer.android.com/reference/android/app/Activity#onRestart()) -> [`onStart`](https://developer.android.com/reference/android/app/Activity#onStart()), and [`onDestroy`](https://developer.android.com/reference/android/app/Activity#onDestroy()) -> [`onCreate`](https://developer.android.com/reference/android/app/Activity#onCreate(android.os.Bundle)) (including configuration changes such as screen rotation, split-screen multi-window mode changes, and foldable device posture folds). + - Window surface reattachment without engine destruction: Testing that [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) followed by [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) cleanly rebinds new swapchains and preserves running isolate state without context leaks or corrupted render targets. + +3. **Multi-Display and Presentation Surfaces**: + - Presentation displays: Android applications projecting secondary UI to external displays (via [`android.app.Presentation`](https://developer.android.com/reference/android/app/Presentation), wireless casting, or Android Auto) operate multiple window surfaces across distinct refresh rates and display densities. Dedicated integration tests must validate multiple simultaneous embedder instances. + +4. **Background Execution and Headless Services**: + - Background execution without an attached window surface: Testing headless execution inside Android [`Service`](https://developer.android.com/reference/android/app/Service) or [`BroadcastReceiver`](https://developer.android.com/reference/android/content/BroadcastReceiver) components (e.g., background work managers, geofencing, background audio) to confirm that background platform channels, Dart isolates, and deferred library loaders execute properly when no window surface is bound and GPU resources are unavailable. + +## Risks and Alternatives Considered + +### Risks and Mitigations + +- Risk: Performance regression in event dispatch or texture rendering due to C-API abstraction. + - Mitigation: [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) structures use plain C data layouts with zero heap allocations during per-frame event dispatch. Event data is passed via direct function pointers. +- Risk: Degradation of Hybrid Composition++ (HCPP) hardware buffer presentation or [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction synchronization. + - Mitigation: HCPP surface pooling and fence handling are ported directly into the modular [`AndroidPlatformViewsController`](#phase-2-in-place-modularization-and-component-decomposition) delegating to [`PlatformViewsController2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java#L55). DeviceLab benchmarks run side-by-side on Android 14+ devices comparing frame pacing and transaction submission times. +- Risk: Clipping anomalies in complex platform views. + - Mitigation: Serialization of mutator stacks is verified with golden tests across Android API levels to compare visual output between legacy and C-API paths. +- Risk: Memory footprint increase from duplicate engine scaffolding. + - Mitigation: The Adapter Pattern enforces that only one engine delegate is instantiated per engine instance. + +### Alternatives Considered + +- Alternative 1: Parallel Build Target Rewrite. Rejected because isolated build targets drift from `main`, are not tested by CI presubmits, and incentivize omitting platform features. +- Alternative 2: Monolithic Cutover without Dual-Delegate Abstraction. Rejected because attempting a single large-scale replacement of `AndroidShellHolder` and `PlatformViewAndroid` without a phased runtime fallback increases regression risk, prevents side-by-side CI verification, and impedes defect triage. +- Alternative 3: Status Quo (Tightly Coupled Direct Shell Interaction). Rejected because it permanently blocks engine modularization, slows builds, and prevents establishing a stable ABI boundary for Android. + +## Open Questions and Community Discussion + +- Granularity of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) serialization: Whether complex custom clipping paths should be serialized as vector verbs and points or pre-rasterized masks when passing across the C-ABI. +- Long-term deprecation timeline for Virtual Display platform views: Virtual Display mode is legacy fallback technology on Android; establishing an explicit deprecation timeline in favor of Hybrid Composition++ (HCPP via [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl)) and Texture Layer Hybrid Composition (TLHC) will simplify embedder maintenance. From a5015d01d368120331f43a34c2809b5bd6d32e53 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 24 Sep 2026 20:36:54 +0000 Subject: [PATCH 02/43] docs(rfc-410): add author and reviewer names with GitHub URLs and update BUILD.gn line links --- ...lutter-android-embedder-c-api-migration.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index f531458..ed11797 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -15,11 +15,13 @@ tags: - impeller authors: - - '"Matt Boetger" ' + - https://github.com/mboetger # Matt Boetger reviewers: - - '"Loïc Sharma" ' - - '"Chris Bracken" ' + - '"Loïc Sharma" ' + - '"Chris Bracken" ' + - '"Andy Wolff" ' + - '"Aaron Clarke" ' links: - https://docs.google.com/document/d/1pFgFYzAKrIFEQQfSz6NbOvKXG6K3oksPQqhKSQZ7t68 @@ -28,6 +30,9 @@ links: # RFC 410.0000: Flutter Android Embedder C-API Migration +**Author:** [Matt Boetger](https://github.com/mboetger) +**Reviewers:** [Loïc Sharma](https://github.com/loic-sharma), [Chris Bracken](https://github.com/cbracken), [Andy Wolff](https://github.com/andywolff), [Aaron Clarke](https://github.com/gaaclarke) + ## Executive Summary and Overview This RFC proposes a phased, in-place migration of the Flutter Android embedder ([`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android)) to the public Flutter Embedder C-API ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)). @@ -44,7 +49,7 @@ This document establishes the plan to decouple the Android embedder from engine The dependency graph of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) in [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) will be reduced strictly to: -- [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L220) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L75) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` enforcing dynamic `FlutterEngineProcTable` resolution) +- [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` enforcing dynamic `FlutterEngineProcTable` resolution) - [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) - [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) - [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) @@ -1459,7 +1464,7 @@ GN enforces `check_includes = true` and `defines = [ "FLUTTER_ENGINE_NO_PROTOTYP In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) directly [subclasses the internal engine class `flutter::PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L151-L160). This inheritance models Android as an internal engine subsystem rather than an external client embedder. Both `AndroidShellHolder` and `PlatformViewAndroid` are eliminated by decomposing their responsibilities across `FlutterEmbedderNative` and the modular components in `:flutter_embedder_native_src`. -In contrast, external embedders communicating via [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) never subclass `flutter::PlatformView`. Instead, the embedder library (`//flutter/shell/platform/embedder:embedder_as_internal_library`) internally instantiates a generic [`flutter::PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) to satisfy the [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) class contract. +In contrast, external embedders communicating via [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) never subclass `flutter::PlatformView`. Instead, the embedder library ([`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206)) internally instantiates a generic [`flutter::PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) to satisfy the [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) class contract. #### Lifecycle and Deletion of Legacy Classes (`AndroidShellHolder` and `PlatformViewAndroid`) @@ -1519,7 +1524,7 @@ Once `FlutterEmbedderNative` passes all functional and performance tests, [`BUIL - Reduce `:flutter_shell_native_src` to `flutter_main.cc`, `flutter_main.h`, and `library_loader.cc` (which calls `FlutterEmbedderNative::RegisterJni(env)`). - Remove dependencies on [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common). - Restrict dependencies strictly to: - - [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L75) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`) + - [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`) - [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) - [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) - [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) @@ -1575,7 +1580,7 @@ flowchart TD #### Layer 1: GN Target Partitioning (Compile-Time Firewall) -In Phase 2, the Android native source set in [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all C-API code in `flutter_embedder_native_src` (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and `APKAssetProvider`). This target sets `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` and declares dependencies strictly on [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml), [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common), and [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets). Because GN evaluates `check_includes = true` during builds, any attempt to include headers from undeclared targets (such as [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) or [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime)) causes a compilation error immediately. +In Phase 2, the Android native source set in [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all C-API code in `flutter_embedder_native_src` (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and `APKAssetProvider`). This target sets `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` and declares dependencies strictly on [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml), [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common), and [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets). Because GN evaluates `check_includes = true` during builds, any attempt to include headers from undeclared targets (such as [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) or [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime)) causes a compilation error immediately. #### Layer 2: Monotonic Header Inclusion Ratchet ([`check_android_embedder_deps.py`](#presubmit-header-allowlist-and-monotonic-decrement-ratchet)) From ec55aa444d403964241e56925007bea146dceb92 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 24 Sep 2026 20:41:01 +0000 Subject: [PATCH 03/43] docs(rfc-410): remove non-standard reviewers field from frontmatter --- ...000-flutter-android-embedder-c-api-migration.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index ed11797..7f54fc2 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -14,25 +14,11 @@ tags: - c-api - impeller authors: - - https://github.com/mboetger # Matt Boetger -reviewers: - - - '"Loïc Sharma" ' - - '"Chris Bracken" ' - - '"Andy Wolff" ' - - '"Aaron Clarke" ' -links: - - - https://docs.google.com/document/d/1pFgFYzAKrIFEQQfSz6NbOvKXG6K3oksPQqhKSQZ7t68 - - https://github.com/mboetger --- # RFC 410.0000: Flutter Android Embedder C-API Migration -**Author:** [Matt Boetger](https://github.com/mboetger) -**Reviewers:** [Loïc Sharma](https://github.com/loic-sharma), [Chris Bracken](https://github.com/cbracken), [Andy Wolff](https://github.com/andywolff), [Aaron Clarke](https://github.com/gaaclarke) - ## Executive Summary and Overview This RFC proposes a phased, in-place migration of the Flutter Android embedder ([`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android)) to the public Flutter Embedder C-API ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)). From 88251ef7e3d3f518780bfd97cba109e755724da9 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 24 Sep 2026 21:10:52 +0000 Subject: [PATCH 04/43] docs(rfc-410): update dates, remove trailing sections, and eliminate AI writing signs --- ...lutter-android-embedder-c-api-migration.md | 65 ++++--------------- 1 file changed, 11 insertions(+), 54 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 7f54fc2..b4b5ad8 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -4,10 +4,9 @@ rfc: '410.0000' title: Flutter Android Embedder C-API Migration description: Migrates the Flutter Android embedder to the public, ABI-stable Flutter Embedder C-API (embedder.h) to eliminate all direct dependencies on engine internal headers. status: draft -created: 2026-09-17T00:00:00Z -updated: 2026-09-17T00:00:00Z +created: 2026-09-24T00:00:00Z +updated: 2026-09-24T00:00:00Z tags: - - 410-android - 200-engine - embedder-api @@ -228,11 +227,11 @@ This level of coupling creates several concrete maintenance problems: ## Detailed Design -To allow the Android embedder to communicate with the engine exclusively through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the C-API must be extended to cover Android-specific subsystem requirements. Each proposed extension corresponds directly to uncoupling one of the internal subsystems identified in the dependency audit and empowering a modular target architecture component (`FlutterEmbedderNative`, `JniRouter` / `JniDelegate` / `JvmInvoker`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17)). +To allow the Android embedder to communicate with the engine exclusively through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the C-API must be extended to cover Android-specific subsystem requirements. Each proposed extension corresponds directly to uncoupling one of the internal subsystems identified in the dependency audit and supporting a target embedder component (`FlutterEmbedderNative`, `JniRouter` / `JniDelegate` / `JvmInvoker`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17)). ### Graphics Surface Setup, Context Ownership, and Surface Lifecycle ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)) -On Android, graphics context creation must be cleanly decoupled from window surface presentation. The embedder initializes or acquires the graphics context (such as Vulkan [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html), physical device, logical [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html), and Vulkan proc loaders, or EGL display, context, and config) prior to invoking [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). +On Android, graphics context creation must be cleanly decoupled from window surface presentation. The embedder initializes or acquires the graphics context (such as Vulkan [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html), physical device, logical [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html), and Vulkan proc loaders, or an `EGLDisplay`, `EGLContext`, and `EGLConfig`) prior to invoking [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). In the existing engine architecture, [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) defers the decision between Vulkan and OpenGL ES until [`GetImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L162) is first called on the raster thread via [`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570): @@ -397,7 +396,7 @@ typedef bool (*FlutterVulkanExternalTextureFrameCallback)( The discriminated union is what makes this workable on Android. [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) hands out [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles rather than [`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html) handles, and `ycbcr_conversion_info` carries the external format identifier and component swizzle that camera and video decoder buffers require. Vendor buffers with an external format cannot be sampled correctly without it. -For legacy [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) instances, the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) API with `kFlutterExternalTextureTypeOpenGL` is used. However, the existing [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1121) struct only specifies target, name, format, and dimensions; it lacks a UV coordinate transformation matrix. On Android, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 matrix accounting for camera sensor rotation, video decoder padding, and coordinate inversion. In the existing architecture, [`SurfaceTextureExternalTexture::Update()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc#L134-L143) queries this matrix through [`PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1626-L1645) and returns an [`SkM44`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkM44.h) matrix directly: +For legacy [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) instances, the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) API with `kFlutterExternalTextureTypeOpenGL` is used. However, the existing [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1121) struct only specifies `target`, `name`, `format`, `width`, and `height`; it lacks a UV coordinate transformation matrix. On Android, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 matrix accounting for camera sensor rotation, video decoder padding, and coordinate inversion. In the existing architecture, [`SurfaceTextureExternalTexture::Update()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc#L134-L143) queries this matrix through [`PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1626-L1645) and returns an [`SkM44`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkM44.h) matrix directly: ```cpp // Existing UV transformation querying in shell/platform/android/surface_texture_external_texture.cc @@ -901,7 +900,7 @@ sequenceDiagram ### Task Runner Configuration, Thread Priorities, and Threading Modes -The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (platform task runner) alongside dedicated worker threads (UI, raster, and IO task runners). +The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (`platform_task_runner`) alongside dedicated worker task runners (`ui_task_runner`, `render_task_runner`, and `thread_priority_setter`). Thread priority configuration on Android requires system-level tuning. In the existing architecture, [`AndroidPlatformThreadConfigSetter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35) (located in [`android_shell_holder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35-L75)) uses `setpriority()` system calls to set Linux thread scheduling priorities: @@ -1052,11 +1051,11 @@ For empty platform messages, the C-API guarantees that a message where `message_ #### Pointer Input Events -Android touch, stylus, and mouse interactions processed in [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L434) provide axis and device metadata. The C-API embedder populates [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1377) with: +Input events processed in [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L434) provide axis and device metadata. The C-API embedder populates [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1377) with: - Device kind: `kFlutterPointerDeviceKindTouch`, `kFlutterPointerDeviceKindMouse`, `kFlutterPointerDeviceKindStylus`, or `kFlutterPointerDeviceKindTrackpad`. - Axis values: `tilt`, `orientation`, `pressure`, and secondary scroll axes. -- Button states: primary, secondary, and stylus barrel buttons. +- Button states: `kFlutterPointerButtonMousePrimary`, `kFlutterPointerButtonMouseSecondary`, and stylus button bitmasks. #### Viewport Metrics, Edge-to-Edge Insets, and Foldables @@ -1069,7 +1068,7 @@ Android delivers window configuration metrics through [`FlutterJNI.nativeSetView - Physical display corner radii - Display features (hinge bounds, display feature types, and device posture state) -[`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) in `embedder.h` historically provided only width, height, and pixel ratio. +[`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) in `embedder.h` historically provided only `width`, `height`, and `pixel_ratio`. Rather than creating a separate struct, `FlutterWindowMetricsEvent` is extended directly in place: @@ -1227,7 +1226,7 @@ sequenceDiagram In the target architecture: - `FlutterEmbedderNative` holds only an opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) handle and dispatches all engine calls through an initialized `FlutterEngineProcTable` (`FlutterEngineGetProcAddresses`). -- The `:flutter_embedder_native_src` GN source set compiles with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`, ensuring at compile time that all C-API symbols are invoked through the function pointer table rather than direct static symbol linkage. +- The `:flutter_embedder_native_src` GN source set compiles with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`. This enforces at compile time that all C-API symbols are invoked through the function pointer table rather than direct static symbol linkage. - Event dispatch (touch, window metrics, platform messages, accessibility actions) calls public functions: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082), [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077), [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114), and [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398). - Engine initialization delegates thread creation to the engine via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006). @@ -1241,7 +1240,7 @@ In the legacy architecture, the native Android embedding is split across three t In the completed target architecture, `AndroidShellHolder`, `PlatformViewAndroid`, and `PlatformViewAndroidJNIImpl` are completely deleted. In their place, `:flutter_embedder_native_src` organizes the Android embedder into a clean coordinator-component architecture: -- `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Serves as the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in `library_loader.cc`). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. +- `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Is the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in `library_loader.cc`). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. - `JniRouter`, `JniDelegate`, and `JvmInvoker` (`jni_router.h`, `jni_delegate.h`, `jvm_invoker.h`): Decouple outbound C++-to-Java JNI invocations from live Android `JNIEnv*` state through injectable delegate and provider interfaces (`FlutterJniProvider`, `HardwareBufferProvider`, `SurfaceControlProvider`, `SurfaceTransactionProvider`, and `ChoreographerProvider`). This allows the entire C-API embedder (`flutter_embedder_native_unittests`) to be unit-tested on the host machine without an Android JVM or device. - `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, enforces synchronous surface destruction contracts before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns, and suspends GPU command submission during application backgrounding via `FlutterEngineSetGpuAvailability`. - `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+) and Texture Layer Hybrid Composition (TLHC), deserialize `FlutterPlatformViewMutation` arrays, capture per-`FlutterViewId` frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). @@ -1644,45 +1643,3 @@ Because Android is Flutter's largest mobile deployment target, verification must - Platform view rendering goldens validating that clipping paths, rounded rectangles, and opacity mutators render identically under HCPP ([`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) Vulkan), HC, and TLHC. - External texture playback verification across video player and camera plugins. - -### Pre-Existing Android Embedder Testing Gaps - -Historical testing in the Android embedder contains notable test coverage gaps, particularly around concurrency and OS lifecycle transitions. Relying solely on existing tests risks false-positive test runs where pre-existing bugs remain hidden during the refactor. The following test suites must be implemented up front prior to enabling the C-API embedder in production: - -1. **Thread-Sensitive Concurrency and Race Conditions**: - - Surface destruction concurrent with rasterizer frame submission: Native Android [`SurfaceView`](https://developer.android.com/reference/android/view/SurfaceView) and [`TextureView`](https://developer.android.com/reference/android/view/TextureView) lifecycles destroy surfaces asynchronously on the Android UI thread while the rasterizer submits draw calls on the raster thread. Dedicated concurrency tests must simulate rapid window detachment during active frame rendering to catch race conditions and dangling surface handles. - - Initialization and destruction races: Multi-threaded startup where Flutter engine initialization coincides with rapid [`Activity`](https://developer.android.com/reference/android/app/Activity) destruction to verify thread safety across [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108), [`ALooper`](https://developer.android.com/ndk/reference/group/looper) callbacks, and native task runners. - -2. **[`Activity`](https://developer.android.com/reference/android/app/Activity) and Window Lifecycle Transitions**: - - Full [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle permutations: Explicit tests exercising [`onPause`](https://developer.android.com/reference/android/app/Activity#onPause()) -> [`onResume`](https://developer.android.com/reference/android/app/Activity#onResume()), [`onStop`](https://developer.android.com/reference/android/app/Activity#onStop()) -> [`onRestart`](https://developer.android.com/reference/android/app/Activity#onRestart()) -> [`onStart`](https://developer.android.com/reference/android/app/Activity#onStart()), and [`onDestroy`](https://developer.android.com/reference/android/app/Activity#onDestroy()) -> [`onCreate`](https://developer.android.com/reference/android/app/Activity#onCreate(android.os.Bundle)) (including configuration changes such as screen rotation, split-screen multi-window mode changes, and foldable device posture folds). - - Window surface reattachment without engine destruction: Testing that [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) followed by [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) cleanly rebinds new swapchains and preserves running isolate state without context leaks or corrupted render targets. - -3. **Multi-Display and Presentation Surfaces**: - - Presentation displays: Android applications projecting secondary UI to external displays (via [`android.app.Presentation`](https://developer.android.com/reference/android/app/Presentation), wireless casting, or Android Auto) operate multiple window surfaces across distinct refresh rates and display densities. Dedicated integration tests must validate multiple simultaneous embedder instances. - -4. **Background Execution and Headless Services**: - - Background execution without an attached window surface: Testing headless execution inside Android [`Service`](https://developer.android.com/reference/android/app/Service) or [`BroadcastReceiver`](https://developer.android.com/reference/android/content/BroadcastReceiver) components (e.g., background work managers, geofencing, background audio) to confirm that background platform channels, Dart isolates, and deferred library loaders execute properly when no window surface is bound and GPU resources are unavailable. - -## Risks and Alternatives Considered - -### Risks and Mitigations - -- Risk: Performance regression in event dispatch or texture rendering due to C-API abstraction. - - Mitigation: [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) structures use plain C data layouts with zero heap allocations during per-frame event dispatch. Event data is passed via direct function pointers. -- Risk: Degradation of Hybrid Composition++ (HCPP) hardware buffer presentation or [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction synchronization. - - Mitigation: HCPP surface pooling and fence handling are ported directly into the modular [`AndroidPlatformViewsController`](#phase-2-in-place-modularization-and-component-decomposition) delegating to [`PlatformViewsController2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java#L55). DeviceLab benchmarks run side-by-side on Android 14+ devices comparing frame pacing and transaction submission times. -- Risk: Clipping anomalies in complex platform views. - - Mitigation: Serialization of mutator stacks is verified with golden tests across Android API levels to compare visual output between legacy and C-API paths. -- Risk: Memory footprint increase from duplicate engine scaffolding. - - Mitigation: The Adapter Pattern enforces that only one engine delegate is instantiated per engine instance. - -### Alternatives Considered - -- Alternative 1: Parallel Build Target Rewrite. Rejected because isolated build targets drift from `main`, are not tested by CI presubmits, and incentivize omitting platform features. -- Alternative 2: Monolithic Cutover without Dual-Delegate Abstraction. Rejected because attempting a single large-scale replacement of `AndroidShellHolder` and `PlatformViewAndroid` without a phased runtime fallback increases regression risk, prevents side-by-side CI verification, and impedes defect triage. -- Alternative 3: Status Quo (Tightly Coupled Direct Shell Interaction). Rejected because it permanently blocks engine modularization, slows builds, and prevents establishing a stable ABI boundary for Android. - -## Open Questions and Community Discussion - -- Granularity of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) serialization: Whether complex custom clipping paths should be serialized as vector verbs and points or pre-rasterized masks when passing across the C-ABI. -- Long-term deprecation timeline for Virtual Display platform views: Virtual Display mode is legacy fallback technology on Android; establishing an explicit deprecation timeline in favor of Hybrid Composition++ (HCPP via [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl)) and Texture Layer Hybrid Composition (TLHC) will simplify embedder maintenance. From e84aee7379b3554b042cbe9813d601c922200f1e Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 24 Sep 2026 21:16:42 +0000 Subject: [PATCH 05/43] docs(rfc-410): fix markdownlint anchor links and ordered list indentation --- ...lutter-android-embedder-c-api-migration.md | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index b4b5ad8..3f518a4 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -392,7 +392,7 @@ typedef bool (*FlutterVulkanExternalTextureFrameCallback)( FlutterVulkanExternalTexture* texture_out); ``` -`FlutterVulkanRendererConfig` carries this callback as `external_texture_frame_callback`. A parallel [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-texture-composition-and-surfaceproducer) sits on both [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) and [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693), so an [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) reaches either backend without an Android-specific engine entry point. +`FlutterVulkanRendererConfig` carries this callback as `external_texture_frame_callback`. A parallel [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es) sits on both [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) and [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693), so an [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) reaches either backend without an Android-specific engine entry point. The discriminated union is what makes this workable on Android. [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) hands out [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles rather than [`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html) handles, and `ycbcr_conversion_info` carries the external format identifier and component swizzle that camera and video decoder buffers require. Vendor buffers with an external format cannot be sampled correctly without it. @@ -414,7 +414,7 @@ const SkM44& SurfaceTextureExternalTexture::GetCurrentUVTransformation() const { Without this matrix, external video and camera frames render inverted or distorted. -To resolve this gap, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces [`FlutterOpenGLTexture2`](#external-texture-composition-and-surfaceproducer): +To resolve this gap, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es): ```c typedef struct { @@ -433,7 +433,7 @@ typedef struct { Under OpenGL ES, platform view compositing also faces constraints: because [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction sync fences depend on Vulkan hardware synchronization, OpenGL platform views must use Texture Layer Hybrid Composition (TLHC) or legacy Hybrid Composition rather than HCPP. > [!NOTE] External Texture Simplification: Dropping Skia and Legacy OpenGL Textures -> Retiring legacy Skia and legacy OpenGL `SurfaceTexture` pathways in favor of Impeller Vulkan allows the embedder to eliminate legacy UV coordinate transform matrix conversions (`FlutterOpenGLTexture2`) and standardize directly on zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports through [`FlutterVulkanExternalTexture`](#external-texture-composition-and-surfaceproducer). +> Retiring legacy Skia and legacy OpenGL `SurfaceTexture` pathways in favor of Impeller Vulkan allows the embedder to eliminate legacy UV coordinate transform matrix conversions (`FlutterOpenGLTexture2`) and standardize directly on zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports through [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es). ### Platform View Mutator Stack Serialization @@ -543,17 +543,17 @@ Because `nodes` is an array of pointers, pointer striding is fixed (`sizeof(Flut 1. **Precedent**: The engine team has already appended members directly to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) without introducing a v3 struct (for example, adding `heading_level` in engine commit `59d8010db51`, and adding `identifier` in engine commit `f01fa5950c0`). 2. **Missing Android Accessibility Members**: The remaining fields required by Android's `AccessibilityBridge` are appended to the end of `FlutterSemanticsNode2`: -```c - // Appended to FlutterSemanticsNode2 in embedder.h: - int32_t max_value_length; - int32_t current_value_length; - const char* link_url; - const char* locale; - double min_value; - double max_value; - int32_t traversal_parent; - FlutterTransformation hit_test_transform; -``` + ```c + // Appended to FlutterSemanticsNode2 in embedder.h: + int32_t max_value_length; + int32_t current_value_length; + const char* link_url; + const char* locale; + double min_value; + double max_value; + int32_t traversal_parent; + FlutterTransformation hit_test_transform; + ``` 3. **Safe Access & Lifecycle**: Semantics batches are delivered from the engine to the embedder via [`FlutterUpdateSemanticsCallback2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1895) registered in `FlutterProjectArgs.update_semantics_callback2`. Embedders read fields using `SAFE_ACCESS(node, member, default_value)`. Semantics updates are enabled or disabled via [`FlutterEngineUpdateSemanticsEnabled`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3352), and accessibility actions triggered by Android OS accessibility services are dispatched back to the engine via [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398). @@ -646,7 +646,7 @@ FlutterEngineResult FlutterEngineNotifyDartDeferredLibraryLoadError( Because split APK assets on Android are loaded via `AAssetManager` or memory-mapped (`mmap`) from disk, buffer lifetime must be explicitly managed: -1. When Dart requests a library, [`FlutterRequestDartDeferredLibraryCallback`](#deferred-component-loading-and-asset-lifecycles) runs asynchronously (`void` return type), so the embedder can initiate download or extraction without blocking the engine thread. +1. When Dart requests a library, [`FlutterRequestDartDeferredLibraryCallback`](#dart-deferred-library-loading) runs asynchronously (`void` return type), so the embedder can initiate download or extraction without blocking the engine thread. 2. When loading completes, `FlutterDartDeferredLibrary.destruction_callback` provides the unmap trigger. In the proposed [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc) implementation, the engine captures this callback in a [`DeferredLibraryLifetime`](#deferred-library-memory-lifetime-management) handle held by [`fml::NonOwnedMapping`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/mapping.h#L115). When the Dart VM finishes loading and drops its references to both data and instruction buffers, the destructor triggers `destruction_callback(user_data)`, which permits the Android embedder to safely `munmap()` or close the underlying [`AAsset`](https://developer.android.com/ndk/reference/group/asset). ### Custom Asset and Kernel Resolution (APK / In-Memory Mapping) @@ -726,7 +726,7 @@ return std::unique_ptr(new AndroidShellHolder( android_context->RenderingApi())); ``` -The public C-API lacked a spawn API, which prevented multi-engine groups from operating without internal [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h) methods. [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives [`FlutterEngineSpawn`](#engine-spawning-and-multi-engine-management-flutterenginegroup): +The public C-API lacked a spawn API, which prevented multi-engine groups from operating without internal [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h) methods. [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn): ```c typedef struct { @@ -748,7 +748,7 @@ FlutterEngineResult FlutterEngineSpawn( FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); ``` -The [`FlutterEngineSpawnConfig`](#engine-spawning-and-multi-engine-management-flutterenginegroup) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240) so that spawned isolates mount distinct route hierarchies immediately upon launch. +The [`FlutterEngineSpawnConfig`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240) so that spawned isolates mount distinct route hierarchies immediately upon launch. Spawned shells share the parent engine's [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context.h) / Impeller device context (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. @@ -1040,7 +1040,7 @@ In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072 This configuration field satisfies both mobile embedder targets: - **Zero-Initialization Safety**: Existing desktop embedders (Linux, macOS, Windows) zero-initialize `FlutterProjectArgs`, which defaults the field to `true` and preserves their single-threaded platform message assumptions. -- **Mobile Embedder Parity**: Both Android and iOS explicitly set `does_handle_platform_messages_on_platform_thread = false`. On iOS, `PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()` returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`FlutterPlatformMessageCallback2`](#platform-message-routing-and-channel-registration) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): +- **Mobile Embedder Parity**: Both Android and iOS explicitly set `does_handle_platform_messages_on_platform_thread = false`. On iOS, `PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()` returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`FlutterPlatformMessageCallback2`](#platform-channel-messaging-background-handlers-and-task-queues) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): - If registered with a `BinaryMessenger.TaskQueue`, the task is dispatched directly to that queue's background executor pool. - If registered on the default queue, the task posts to the Android platform [`Looper`](https://developer.android.com/reference/android/os/Looper). - Responses submitted via [`FlutterEngineSendPlatformMessageResponse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3133) execute safely from any thread without main-thread hops. @@ -1295,10 +1295,10 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#engine-spawning-and-multi-engine-management-flutterenginegroup) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.does_handle_platform_messages_on_platform_thread`
- [`FlutterPlatformMessageCallback2`](#platform-message-routing-and-channel-registration)
- `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.does_handle_platform_messages_on_platform_thread`
- [`FlutterPlatformMessageCallback2`](#platform-channel-messaging-background-handlers-and-task-queues)
- `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`)
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation` and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | -| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-texture-composition-and-surfaceproducer) and [`FlutterVulkanExternalTextureFrameCallback`](#external-texture-composition-and-surfaceproducer)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-texture-composition-and-surfaceproducer)
- [`FlutterOpenGLTexture2`](#external-texture-composition-and-surfaceproducer) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | +| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) and [`FlutterVulkanExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | | **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolverByType` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | | **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | @@ -1567,7 +1567,7 @@ flowchart TD In Phase 2, the Android native source set in [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all C-API code in `flutter_embedder_native_src` (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and `APKAssetProvider`). This target sets `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` and declares dependencies strictly on [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml), [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common), and [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets). Because GN evaluates `check_includes = true` during builds, any attempt to include headers from undeclared targets (such as [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) or [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime)) causes a compilation error immediately. -#### Layer 2: Monotonic Header Inclusion Ratchet ([`check_android_embedder_deps.py`](#presubmit-header-allowlist-and-monotonic-decrement-ratchet)) +#### Layer 2: Monotonic Header Inclusion Ratchet ([`check_android_embedder_deps.py`](#the-dependency-ratchet-mechanism)) To prevent the quarantined legacy files from accumulating additional internal includes during Phase 2 and Phase 3, an automated presubmit linter enforces a monotonic reduction policy. @@ -1598,7 +1598,7 @@ subsystems: The presubmit linter enforces four deterministic rules on incoming pull requests: -1. *Zero new files*: Only source files explicitly registered in [`allowed_internal_headers.yaml`](#presubmit-header-allowlist-and-monotonic-decrement-ratchet) may contain internal engine includes. Any inclusion in an unlisted file fails presubmit. +1. *Zero new files*: Only source files explicitly registered in [`allowed_internal_headers.yaml`](#the-dependency-ratchet-mechanism) may contain internal engine includes. Any inclusion in an unlisted file fails presubmit. 2. *Zero new headers*: New `#include "flutter/..."` directives not in the allowlist fail presubmit, requiring the API to be routed through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). 3. *Monotonic decrement*: When an incremental refactoring removes an internal header, the pull request must remove it from `allowed_internal_headers.yaml` and decrement `total_allowed_internal_headers`. Once merged, the ratchet locks the lower count permanently. 4. *No escape hatches*: The linter parses [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) and rejects modifications introducing `// nogncheck` or widening GN target visibility. From af4c3d297ddcbf606c6f57b742d388b8e02034e6 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 24 Sep 2026 21:53:41 +0000 Subject: [PATCH 06/43] docs(rfc-410): clarify Hybrid Composition preservation via FlutterCompositor --- ...lutter-android-embedder-c-api-migration.md | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 3f518a4..35c78ef 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -301,14 +301,20 @@ typedef struct { > [!NOTE] Backend Simplification: Dropping Skia > If legacy Skia support is dropped in favor of an Impeller-only architecture on Android, all legacy Skia context setup, EGL display and context fallback handling, and Ganesh header dependencies are eliminated. Graphics surface initialization narrows cleanly to Impeller Vulkan with an Impeller OpenGL ES fallback. -### Dynamic Raster Thread Merging (Legacy Hybrid Composition) +### Replacing Internal `AndroidExternalViewEmbedder` with `FlutterCompositor` (Preserving Hybrid Composition) -Android Hybrid Composition (HC) synchronizes native Android [`View`](https://developer.android.com/reference/android/view/View) widgets with Flutter layers. During frame rendering in legacy HC mode, native Android views update on the main platform thread. If Flutter continues rasterizing on a separate raster thread, thread contention and deadlocks can occur during surface redraws. +Android supports three platform view composition modes: -Modern Hybrid Composition++ (HCPP, see Hybrid Composition++ (HCPP) and SurfaceControl Compositing) bypasses thread merging through [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction fences on Android 14+ (API level 34+); however, legacy Hybrid Composition remains required for earlier Android API levels under Skia. To prevent deadlocks in HC mode, Flutter dynamically merges the raster thread into the platform thread ([`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31)). In the current architecture, [`AndroidExternalViewEmbedder::PostPrerollAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213) checks if the layer tree contains platform views and manages the lease term directly: +1. **Hybrid Composition (HC)**: Places the native Android [`View`](https://developer.android.com/reference/android/view/View) directly in the view hierarchy (`createForPlatformViewLayer`) and composites Flutter UI layers above the platform view into Android overlay surfaces (`FlutterImageView` / `PlatformViewWrapper` via `createOverlaySurface` and `onDisplayOverlaySurface`). +2. **Hybrid Composition++ (HCPP)**: Uses NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU acquire/release sync fences on Android 14+ (API level 34+) (`createPlatformViewHcpp`). +3. **Texture Layer Hybrid Composition (TLHC)**: Redirects the Android `View` into an offscreen [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) / [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) registered as a Flutter external texture (`createForTextureLayer`). + +**Retiring Hybrid Composition (HC) is not an option.** Standard Hybrid Composition remains required across Android devices and API levels where HCPP or TLHC cannot be used. What this migration eliminates is **only the internal C++ `flutter::ExternalViewEmbedder` subclass (`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`) and its direct dependencies on `//flutter/flow` (`embedded_views.h`) and `fml::RasterThreadMerger`**, rather than Hybrid Composition itself. + +In the legacy architecture, [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) subclassed `flutter::ExternalViewEmbedder` directly and used [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) in [`PostPrerollAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213) to dynamically lease and merge the raster thread onto the platform thread during HC frames: ```cpp -// Existing thread leasing in shell/platform/android/external_view_embedder/external_view_embedder.cc +// Existing internal ExternalViewEmbedder subclass in shell/platform/android/external_view_embedder/external_view_embedder.cc PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( const fml::RefPtr& raster_thread_merger) { if (!FrameHasPlatformLayers()) { @@ -327,25 +333,19 @@ PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( } ``` -During merged execution, both platform tasks and raster work execute on the Android platform thread. Once the platform view interaction ceases, the threads unmerge. - -> [!IMPORTANT] No C-ABI extension is proposed for thread merging -> Publishing a thread-merger handle and frame lifecycle callbacks in `embedder.h` would make the engine's internal threading model part of the public ABI and leak `RasterThreadMerger` lease semantics to embedders. Instead, the migration retains [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) and [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) only inside the quarantined legacy target during Phases 2 and 3, and deletes `external_view_embedder/` in Phase 4 once `AndroidPlatformViewsController` routes all platform view composition through `FlutterCompositor` and `AndroidSurfaceControl`. - -#### Empirical Characterization of Lease Dynamics +#### How Hybrid Composition (HC) Works Through `FlutterCompositor` -Characterization of [`RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) during scroll interactions with embedded platform views reveals two operating regimes: +In the C-API architecture, the engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) already implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2117) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). -1. **Steady-State Scroll Behavior**: As long as platform views remain visible in the viewport, every layer tree contains platform view layers. The embedder calls `ExtendLeaseTo(10)` on every frame (using a standard lease duration of 10 frames, which yields approximately 166 ms of hysteresis at a 60 Hz refresh rate). Each frame decrements the remaining lease count from 10 to 9, but the subsequent frame immediately resets the lease back to 10. Consequently, threads remain continuously merged with zero toggle flapping between merged and unmerged states. -2. **View Departure and Hysteresis**: When the viewport scrolls past the last platform view or the view is dismissed, subsequent frames contain no platform layers. The embedder halts lease extensions. Over the next 10 consecutive frames, the remaining lease count decrements on each frame (9, 8, ..., 0). On the 10th frame after view departure, the lease count reaches 0, `UnMergeNowIfLastOne()` executes, and the rasterizer transitions back to independent multi-threaded execution. This 10-frame lease term provides hysteresis: transient gaps without platform layers (such as dynamic view recycling or brief animations) do not repeatedly merge and unmerge OS threads. +`AndroidCompositor` and `AndroidPlatformViewsController` support all three composition modes (`kHybridComposition`, `kHybridCompositionPlusPlus`, and `kTextureLayer`) over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: -#### Interaction with Static Thread Merging +1. **Backing Store Allocation (`create_backing_store_callback`)**: For the primary Flutter layer (`platform_views_count == 0`), `AndroidCompositor::CreateBackingStore` returns the main surface framebuffer (`GetFBO()`). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above the platform view, subsequent `CreateBackingStore` calls within the same frame allocate offscreen framebuffers (`AcquireOffscreenFBO`). +2. **Layer and Overlay Presentation (`present_layers_callback` / `present_view_callback`)**: `AndroidCompositor::PresentLayers` begins the platform view frame (`OnBeginFrame`), iterates through the ordered `FlutterLayer` array, and dispatches each layer by type: + - The base `kFlutterLayerContentTypeBackingStore` is presented to the primary `ANativeWindow`. + - Each `kFlutterLayerContentTypePlatformView` layer forwards the view identifier, physical bounds (`offset`, `size`), and serialized `FlutterPlatformViewMutation` stack to `AndroidPlatformViewsController::OnDisplayPlatformView` (`PlatformViewsController.onDisplayPlatformView` in Java). + - Any `kFlutterLayerContentTypeBackingStore` layer positioned above a platform view acquires an Android overlay surface (`createOverlaySurface` / `GetOverlayWindow`), blits and swaps the offscreen FBO into that overlay window (`BlitAndSwapOverlaySurface`), and updates the overlay geometry (`onDisplayOverlaySurface` / `showOverlaySurface`), followed by `OnEndFrame`. +3. **Removing `external_view_embedder/`**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting, HCPP `SurfaceControl` transactions, and TLHC textures through `FlutterCompositor` while capturing per-frame layer and mutator state by value across threads, `shell/platform/android/external_view_embedder/` (`AndroidExternalViewEmbedder` and `AndroidExternalViewEmbedder2`) is deleted in Phase 4 without removing or degrading Hybrid Composition. No `fml::RasterThreadMerger` handles or internal flow types are exposed in `embedder.h`. -Flutter supports running with statically merged platform and UI or raster threads via `--merged-platform-ui-thread` or `kMergeAfterLaunch`. When task queues are statically merged, [`RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) lease operations ([`MergeWithLease`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L43), [`ExtendLeaseTo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L70), [`DecrementLease`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L77), [`UnMergeNowIfLastOne`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L66)) are safe no-ops and the merged-state queries unconditionally report `true`. Dynamic thread merging composes safely with static merging without requiring separate code branches. - -#### Resolution: Retiring Legacy `AndroidExternalViewEmbedder` - -Legacy Hybrid Composition under Skia was the only consumer of dynamic raster thread merging in `shell/platform/android/external_view_embedder/`. Under Impeller, platform view composition executes asynchronously via Hybrid Composition++ (HCPP via `SurfaceControl.Transaction` and hardware sync fences on API 34+) and Texture Layer Hybrid Composition (TLHC on lower API levels). In Phase 4, `external_view_embedder/` (`AndroidExternalViewEmbedder` and `AndroidExternalViewEmbedder2`) is removed from `shell/platform/android/`, and `AndroidPlatformViewsController` drives all platform view composition through the public `FlutterCompositor` interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback`). This eliminates `fml::RasterThreadMerger` from the Android embedder without adding thread-merger handles to `embedder.h` and leaves zero exceptions to the GN compile-time firewall. ### External Textures (Zero-Copy Vulkan and OpenGL ES) @@ -895,9 +895,6 @@ sequenceDiagram Embedder->>Embedder: Return buffer to AndroidSurfaceControl for recycling ``` -> [!NOTE] Compositor Simplification: Dropping Skia and Legacy Hybrid Composition -> Dropping legacy Skia and legacy Hybrid Composition collapses platform view composition on Android into exactly two asynchronous Impeller pipelines: HCPP on Android API 34+ and TLHC on older releases. This completely eliminates dynamic thread leasing, Virtual Display mode, and offscreen Skia canvas allocations. - ### Task Runner Configuration, Thread Priorities, and Threading Modes The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (`platform_task_runner`) alongside dedicated worker task runners (`ui_task_runner`, `render_task_runner`, and `thread_priority_setter`). From 3d6bf6a5bd6c49a693514f8cf527ad7bddfef0e2 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 24 Sep 2026 22:11:40 +0000 Subject: [PATCH 07/43] docs(rfc): document Virtual Display (VD) as the 4th Android platform view mode --- ...lutter-android-embedder-c-api-migration.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 35c78ef..34e89fa 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -303,13 +303,14 @@ typedef struct { ### Replacing Internal `AndroidExternalViewEmbedder` with `FlutterCompositor` (Preserving Hybrid Composition) -Android supports three platform view composition modes: +Android supports four platform view composition modes: 1. **Hybrid Composition (HC)**: Places the native Android [`View`](https://developer.android.com/reference/android/view/View) directly in the view hierarchy (`createForPlatformViewLayer`) and composites Flutter UI layers above the platform view into Android overlay surfaces (`FlutterImageView` / `PlatformViewWrapper` via `createOverlaySurface` and `onDisplayOverlaySurface`). 2. **Hybrid Composition++ (HCPP)**: Uses NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU acquire/release sync fences on Android 14+ (API level 34+) (`createPlatformViewHcpp`). -3. **Texture Layer Hybrid Composition (TLHC)**: Redirects the Android `View` into an offscreen [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) / [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) registered as a Flutter external texture (`createForTextureLayer`). +3. **Texture Layer Hybrid Composition (TLHC)**: Attaches the Android `View` to the view hierarchy (`PlatformViewWrapper`) while redirecting its draw output into an offscreen [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) or [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) render target registered as a Flutter external texture (`createForTextureLayer` / `configureForTextureLayerComposition`). +4. **Virtual Display (VD)**: Hosts the Android `View` inside an offscreen [`VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay) (`VirtualDisplayController`) backed by a `TextureRegistry` render target (`SurfaceProducer` or `SurfaceTexture`) and composites its output as a Flutter external texture (`createForTextureLayer` / `configureForVirtualDisplay`). -**Retiring Hybrid Composition (HC) is not an option.** Standard Hybrid Composition remains required across Android devices and API levels where HCPP or TLHC cannot be used. What this migration eliminates is **only the internal C++ `flutter::ExternalViewEmbedder` subclass (`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`) and its direct dependencies on `//flutter/flow` (`embedded_views.h`) and `fml::RasterThreadMerger`**, rather than Hybrid Composition itself. +**All four platform view composition modes are preserved; retiring Hybrid Composition (HC) or Virtual Display (VD) is not an option.** Standard Hybrid Composition and Virtual Display remain required across Android devices, API levels, and embedded view hierarchies (such as views containing `SurfaceView` or `TextureView` children where `PlatformViewsController` falls back from TLHC to Virtual Display). What this migration eliminates is **only the internal C++ `flutter::ExternalViewEmbedder` subclass (`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`) and its direct dependencies on `//flutter/flow` (`embedded_views.h`) and `fml::RasterThreadMerger`**, rather than any platform view composition mode. In the legacy architecture, [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) subclassed `flutter::ExternalViewEmbedder` directly and used [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) in [`PostPrerollAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213) to dynamically lease and merge the raster thread onto the platform thread during HC frames: @@ -333,19 +334,19 @@ PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( } ``` -#### How Hybrid Composition (HC) Works Through `FlutterCompositor` +#### How All Four Platform View Modes Work in the C-API Architecture -In the C-API architecture, the engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) already implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2117) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). +At the C-API boundary (`embedder.h`), the four composition modes partition into two external-texture modes and two compositor-layer modes: -`AndroidCompositor` and `AndroidPlatformViewsController` support all three composition modes (`kHybridComposition`, `kHybridCompositionPlusPlus`, and `kTextureLayer`) over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: +- **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L652) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanExternalTextureFrameCallback`, `FlutterHardwareBufferExternalTextureFrameCallback`, and `FlutterOpenGLTexture`) without creating `FlutterPlatformView` layers. +- **Hybrid Composition (HC) and Hybrid Composition++ (HCPP) via `FlutterCompositor`**: The engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2117) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). `AndroidCompositor` and `AndroidPlatformViewsController` drive both HC and HCPP over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: 1. **Backing Store Allocation (`create_backing_store_callback`)**: For the primary Flutter layer (`platform_views_count == 0`), `AndroidCompositor::CreateBackingStore` returns the main surface framebuffer (`GetFBO()`). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above the platform view, subsequent `CreateBackingStore` calls within the same frame allocate offscreen framebuffers (`AcquireOffscreenFBO`). 2. **Layer and Overlay Presentation (`present_layers_callback` / `present_view_callback`)**: `AndroidCompositor::PresentLayers` begins the platform view frame (`OnBeginFrame`), iterates through the ordered `FlutterLayer` array, and dispatches each layer by type: - The base `kFlutterLayerContentTypeBackingStore` is presented to the primary `ANativeWindow`. - Each `kFlutterLayerContentTypePlatformView` layer forwards the view identifier, physical bounds (`offset`, `size`), and serialized `FlutterPlatformViewMutation` stack to `AndroidPlatformViewsController::OnDisplayPlatformView` (`PlatformViewsController.onDisplayPlatformView` in Java). - Any `kFlutterLayerContentTypeBackingStore` layer positioned above a platform view acquires an Android overlay surface (`createOverlaySurface` / `GetOverlayWindow`), blits and swaps the offscreen FBO into that overlay window (`BlitAndSwapOverlaySurface`), and updates the overlay geometry (`onDisplayOverlaySurface` / `showOverlaySurface`), followed by `OnEndFrame`. -3. **Removing `external_view_embedder/`**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting, HCPP `SurfaceControl` transactions, and TLHC textures through `FlutterCompositor` while capturing per-frame layer and mutator state by value across threads, `shell/platform/android/external_view_embedder/` (`AndroidExternalViewEmbedder` and `AndroidExternalViewEmbedder2`) is deleted in Phase 4 without removing or degrading Hybrid Composition. No `fml::RasterThreadMerger` handles or internal flow types are exposed in `embedder.h`. - +3. **Removing `external_view_embedder/`**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting and HCPP `SurfaceControl` transactions through `FlutterCompositor` (while TLHC and VD operate through the C-API external texture pipeline) and capture per-frame layer and mutator state by value across threads, `shell/platform/android/external_view_embedder/` (`AndroidExternalViewEmbedder` and `AndroidExternalViewEmbedder2`) is deleted in Phase 4 without removing or degrading any of the four platform view modes. No `fml::RasterThreadMerger` handles or internal flow types are exposed in `embedder.h`. ### External Textures (Zero-Copy Vulkan and OpenGL ES) @@ -1638,5 +1639,5 @@ Because Android is Flutter's largest mobile deployment target, verification must ### Golden and Conformance Tests -- Platform view rendering goldens validating that clipping paths, rounded rectangles, and opacity mutators render identically under HCPP ([`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) Vulkan), HC, and TLHC. +- Platform view rendering goldens validating that clipping paths, rounded rectangles, and opacity mutators render identically across both compositor modes (HCPP with [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) Vulkan and HC) and external-texture modes (TLHC and VD). - External texture playback verification across video player and camera plugins. From 61db3708465bcc97d9cff568265606e282099001 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 24 Sep 2026 22:18:26 +0000 Subject: [PATCH 08/43] docs(rfc): specify API 21-33 Hybrid Composition frame synchronization without RasterThreadMerger --- ...lutter-android-embedder-c-api-migration.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 34e89fa..5400a93 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -348,6 +348,27 @@ At the C-API boundary (`embedder.h`), the four composition modes partition into - Any `kFlutterLayerContentTypeBackingStore` layer positioned above a platform view acquires an Android overlay surface (`createOverlaySurface` / `GetOverlayWindow`), blits and swaps the offscreen FBO into that overlay window (`BlitAndSwapOverlaySurface`), and updates the overlay geometry (`onDisplayOverlaySurface` / `showOverlaySurface`), followed by `OnEndFrame`. 3. **Removing `external_view_embedder/`**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting and HCPP `SurfaceControl` transactions through `FlutterCompositor` (while TLHC and VD operate through the C-API external texture pipeline) and capture per-frame layer and mutator state by value across threads, `shell/platform/android/external_view_embedder/` (`AndroidExternalViewEmbedder` and `AndroidExternalViewEmbedder2`) is deleted in Phase 4 without removing or degrading any of the four platform view modes. No `fml::RasterThreadMerger` handles or internal flow types are exposed in `embedder.h`. +#### Frame Synchronization on API 21–33 Without `fml::RasterThreadMerger` + +On Android 5.0 through Android 13 (API levels 21–33), NDK [`ASurfaceTransaction_setBuffer`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_setbuffer) and hardware sync fence handoff (HCPP) are unavailable. Two runtime paths can still prevent a platform view from using Texture Layer Hybrid Composition (TLHC) on these API levels: + +1. **Explicit Hybrid Composition (`initExpensiveAndroidView`)**: The plugin requests `createForPlatformViewLayer`, routing directly to [`PlatformViewsController.configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L583). +2. **Embedded Child `SurfaceView` or `TextureView` (`VIEW_TYPES_REQUIRE_NON_TLHC`)**: When a plugin uses `initAndroidView` (`createForTextureLayer`) and the embedded Android `View` hierarchy contains a `SurfaceView` or `TextureView` child whose out-of-band buffer queue bypasses `ViewParent.onDescendantInvalidated()`, [`PlatformViewsController.createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L229-L246) detects `!supportsTextureLayerMode` and branches by `request.displayMode`: + - **`TEXTURE_WITH_VIRTUAL_FALLBACK` $\rightarrow$ Virtual Display (VD)**: Calls `configureForVirtualDisplay`, hosting the view and its child `SurfaceView` inside an offscreen `VirtualDisplay` backed by a `TextureRegistry` render target. Frame synchronization is handled entirely on the GPU/raster thread as a standard C-API external texture (`FlutterEngineRegisterExternalTexture`). + - **`TEXTURE_WITH_HYBRID_FALLBACK` $\rightarrow$ Hybrid Composition (HC)**: Calls `configureForHybridComposition` and returns `NON_TEXTURE_FALLBACK` (`-2`), instructing the Dart framework to composite the view via a `PlatformViewLayer` (`kFlutterLayerContentTypePlatformView`). + +When Hybrid Composition is active on API 21–33 without `fml::RasterThreadMerger`, frame synchronization between the Flutter rasterizer and the Android `View` hierarchy is enforced through a three-part lockstep protocol across `AndroidCompositor` and `PlatformViewsController`: + +1. **`FlutterImageView` (`ImageReader`) Target Conversion**: + Rendering the base Flutter layer into a `FlutterSurfaceView` (`SurfaceView`) during HC would present to `SurfaceFlinger` out of band from the Android `ViewRootImpl` UI traversal, causing visual lag against the `FlutterMutatorView`. When the first HC platform view is displayed (`onDisplayPlatformView`), `PlatformViewsController.initializeRootImageViewIfNeeded()` calls `flutterView.convertToImageView()`, switching the engine's primary `ANativeWindow` to an [`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)-backed `FlutterImageView`. Each overlay layer (`PlatformOverlayView`) is likewise backed by its own `ImageReader` `ANativeWindow`. +2. **Raster-Before-Acquire Ordering and Synchronous Platform-Thread Latch**: + In the legacy architecture, `AndroidExternalViewEmbedder` merged the raster thread onto the platform thread via `fml::RasterThreadMerger` only because it called JNI view hierarchy mutations inline during `SubmitFlutterView`. In `AndroidCompositor::PresentLayers`: + - On the raster thread, `surface_manager_->Present()` and `surface_manager_->BlitAndSwapOverlaySurface(...)` first complete `eglSwapBuffers` / `vkQueuePresentKHR` into the root `FlutterImageView` and `PlatformOverlayView` `ImageReader` `ANativeWindow` queues for Frame $N$. + - `AndroidCompositor` packages Frame $N$'s platform view geometry (`offset`, `size`), value-copied `AndroidMutatorsStack`, and overlay rectangles into a single atomic platform-thread frame transaction (`onBeginFrame` $\rightarrow$ `onDisplayPlatformView` $\rightarrow$ `onDisplayOverlaySurface` $\rightarrow$ `onEndFrame`). + - When `platform_views_count > 0` and HCPP is not active (`!IsHcppEnabled()`), if `PresentLayers` is not already running on the platform thread (`!platform_runner->RunsTasksOnCurrentThread()`), `PresentLayers` synchronizes on a frame-completion latch (`fml::AutoResetWaitableEvent`) until the platform thread finishes `onEndFrame()` (with a teardown guard to prevent ANRs if the surface detaches concurrently). Waiting on this latch prevents the raster thread from queuing Frame $N+1$ into the `ImageReader` before the platform thread has consumed Frame $N$, guaranteeing that `ImageReader.acquireLatestImage()` pairs Frame $N$'s rasterized pixels 1:1 with Frame $N$'s `FlutterMutatorView` scroll offset and mutator matrix. +3. **Atomic `ImageReader` Gate in `PlatformViewsController.onEndFrame()`**: + During the platform-thread commit in [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391), `flutterView.acquireLatestImageViewFrame()` and `overlayView.acquireLatestImage()` pull the newly queued GPU buffers and invalidate the `FlutterImageView` instances. `finishFrame()` checks `isFrameRenderedUsingImageReaders` (verifying that the converted root `FlutterImageView` and every active `PlatformOverlayView` acquired a valid image) before setting `parentView.setVisibility(View.VISIBLE)`. Consequently, the base `FlutterImageView`, the `FlutterMutatorView` (wrapping the native `View`), and all `PlatformOverlayView` layers are drawn together in a single `ViewRootImpl.performTraversals()` pass on the Android UI thread. When the last HC view is disposed (`currentFrameUsedPlatformViewIds.isEmpty()`), `onEndFrame()` invokes `flutterView.revertImageView()` to return to direct `SurfaceView` presentation. + ### External Textures (Zero-Copy Vulkan and OpenGL ES) Android provides two distinct mechanisms for external textures: From 68925f6ec60429c03fd10a7be2fcd4ff2296df59 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 7 Oct 2026 14:31:16 -0700 Subject: [PATCH 09/43] feedback changes --- ...lutter-android-embedder-c-api-migration.md | 99 ++++--------------- 1 file changed, 21 insertions(+), 78 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 5400a93..5f09004 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -34,10 +34,13 @@ This document establishes the plan to decouple the Android embedder from engine The dependency graph of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) in [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) will be reduced strictly to: -- [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` enforcing dynamic `FlutterEngineProcTable` resolution) -- [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) -- [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) -- [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) +|Allowed Dependencies|| +|---|---| +|[`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210)|| +|[`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206)|Enforce dynamic `FlutterEngineProcTable` resolution with`defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`| +|[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml)|Optional| +|[`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common)|Optional| +|[`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets)|Optional| All direct dependencies on internal engine subsystems ([`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), and [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt)) will be eliminated. @@ -47,6 +50,7 @@ All direct dependencies on internal engine subsystems ([`//flutter/shell/common` - Reduce direct internal engine header inclusions in [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) from 98 to 0. - Enable independent compilation and stable ABI boundaries for Android embeddings. - Maintain complete behavioral correctness, performance parity, and accessibility support across all supported Android API levels and rendering backends (Impeller Vulkan, Impeller OpenGL, and legacy Skia). +- Ensure the embedder API exposes the features needed to create an out-of-tree embedder that is as feature-complete as Flutter Android. ### Non-Goals @@ -224,6 +228,7 @@ This level of coupling creates several concrete maintenance problems: - Fragile refactoring: Any change to private engine internals (such as Impeller backend refactors or Flow mutator stack updates) breaks the Android embedder. - Prolonged rebuild times: Compiling `libflutter.so` requires rebuilding internal engine translation units even when only platform-specific code changes. - Lack of API contract: No stable boundary prevents Android embedder code from reaching into private engine internals, which inhibits engine modularization. +- Feature parity lag: Because Android and iOS bypass the embedder API, public engine capabilities often lag behind engine internals, which prevents out-of-tree platforms from achieving feature parity with mobile platforms. ## Detailed Design @@ -253,52 +258,13 @@ void AndroidContextDynamicImpeller::SetupImpellerContext() { Because `embedder.h` requires a concrete renderer configuration struct (`FlutterRendererConfig` selecting `kOpenGL` or `kVulkan`) at `FlutterEngineInitialize` time, the embedder resolves the graphics backend during initialization before passing renderer pointers to the engine. -Performing synchronous Vulkan driver queries during `FlutterLoader.ensureInitializationComplete` on Android's main thread introduces a 15 to 30 ms cold-startup delay on budget devices (such as MediaTek or older Mali chipsets) due to driver library loading and physical device property enumeration. +It is not explicitly needed to connect this API through the Embedder API though. This can be handled during Android's initialization path. -To eliminate main-thread startup delays: +Performing synchronous Vulkan driver queries during `FlutterLoader.ensureInitializationComplete` on Android's main thread introduces a cold-startup delay on devices due to driver library loading and physical device property enumeration. So it will need to offloaded to a background thread. -1. `FlutterLoader.startInitialization` dispatches an asynchronous Vulkan capability probe to a background worker isolate or thread when the host application starts. -2. The probe result (device extensions, driver ID, and Impeller capability flags) is cached in Android [`SharedPreferences`](https://developer.android.com/reference/android/content/SharedPreferences). -3. Subsequent application launches read the cached renderer preference in less than 0.5 ms during `FlutterLoader.ensureInitializationComplete`. -4. If the device experiences a driver crash or OS upgrade, the embedder falls back safely to OpenGLES. +If the device experiences a driver crash or OS upgrade, the embedder falls back safely to OpenGLES. -```mermaid -sequenceDiagram - autonumber - participant App as Application onCreate - participant Loader as FlutterLoader - participant Worker as Background Task Worker - participant Prefs as Android SharedPreferences - participant Init as FlutterEngineInitialize - - App->>Loader: startInitialization - Loader->>Worker: Dispatch asynchronous Vulkan capability check - Worker->>Worker: Probe VkInstance and VkDevice compatibility - Worker->>Prefs: Write cached renderer flag: kImpellerVulkan or kImpellerOpenGLES - App->>Loader: ensureInitializationComplete - Loader->>Prefs: Read cached renderer flag in under 0.5ms - Loader->>Init: Pass concrete FlutterRendererConfig without main-thread stall -``` - -To configure raster-thread graphics state and resources, [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693) and [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) provide context setup callbacks: - -```c -typedef struct { - size_t struct_size; - // Callback invoked on the raster thread to initialize OpenGL graphics state. - VoidCallback setup_callback; -} FlutterOpenGLRendererConfig; - -typedef struct { - size_t struct_size; - // Callback invoked on the raster thread to initialize Vulkan graphics state. - VoidCallback setup_callback; -} FlutterVulkanRendererConfig; -``` - -[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) also receives an API to construct a render target directly from a Vulkan backing store to match Impeller Vulkan swapchain requirements. Window surface attachments ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and swapchains are created and destroyed dynamically across Android [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events via [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) (see Renderer Availability and Lifecycle Signals) without destroying the underlying GPU context. - -> [!NOTE] Backend Simplification: Dropping Skia +> [!NOTE] **Backend Simplification: Dropping Skia** > If legacy Skia support is dropped in favor of an Impeller-only architecture on Android, all legacy Skia context setup, EGL display and context fallback handling, and Ganesh header dependencies are eliminated. Graphics surface initialization narrows cleanly to Impeller Vulkan with an Impeller OpenGL ES fallback. ### Replacing Internal `AndroidExternalViewEmbedder` with `FlutterCompositor` (Preserving Hybrid Composition) @@ -539,31 +505,11 @@ The array is an array of pointers rather than an array of values. Indexing strid > [!NOTE] Mutator Simplification: Dropping Complex Path Clipping > If legacy Hybrid Composition under Skia is dropped, or if platform view clipping is restricted to standard geometric bounds, `kFlutterPlatformViewMutationTypeClipPath` and the whole `FlutterPath` family become dead weight. Mutations would then convey only affine transformations, bounding rectangles, and corner radii, all of which map directly to Android `SurfaceControl` or `View` properties. -### Accessibility and Semantics Parity (`FlutterSemanticsNode2` In-Place Extension) - -Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in the original [`FlutterSemanticsNode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1599) struct, including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `identifier`, `traversal_parent`, and `hit_test_transform`. +### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) In-Place Extension) -#### In-Place Extension of `FlutterSemanticsNode2` +Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in the original [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) struct, including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `identifier`, `traversal_parent`, and `hit_test_transform`. -The legacy [`FlutterSemanticsUpdate`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1855) (v1) stored nodes in a flat array of structs by value (`FlutterSemanticsNode* nodes`). In C, indexing `nodes[i]` strides by `sizeof(FlutterSemanticsNode)`, so adding members changed the struct size and broke older binaries. - -[`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) resolved this by replacing the flat array with an array of pointers: - -```c -typedef struct { - size_t struct_size; - size_t node_count; - FlutterSemanticsNode2** nodes; // Array of pointers - size_t custom_action_count; - FlutterSemanticsCustomAction2** custom_actions; // Array of pointers - FlutterViewId view_id; -} FlutterSemanticsUpdate2; -``` - -Because `nodes` is an array of pointers, pointer striding is fixed (`sizeof(FlutterSemanticsNode2*)`). Each [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) begins with `size_t struct_size`. This design allows `FlutterSemanticsNode2` to be extended in place: - -1. **Precedent**: The engine team has already appended members directly to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) without introducing a v3 struct (for example, adding `heading_level` in engine commit `59d8010db51`, and adding `identifier` in engine commit `f01fa5950c0`). -2. **Missing Android Accessibility Members**: The remaining fields required by Android's `AccessibilityBridge` are appended to the end of `FlutterSemanticsNode2`: +**Missing Android Accessibility Members**: The remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677): ```c // Appended to FlutterSemanticsNode2 in embedder.h: @@ -577,8 +523,6 @@ Because `nodes` is an array of pointers, pointer striding is fixed (`sizeof(Flut FlutterTransformation hit_test_transform; ``` -3. **Safe Access & Lifecycle**: Semantics batches are delivered from the engine to the embedder via [`FlutterUpdateSemanticsCallback2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1895) registered in `FlutterProjectArgs.update_semantics_callback2`. Embedders read fields using `SAFE_ACCESS(node, member, default_value)`. Semantics updates are enabled or disabled via [`FlutterEngineUpdateSemanticsEnabled`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3352), and accessibility actions triggered by Android OS accessibility services are dispatched back to the engine via [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398). - ### Dart Deferred Library Loading Android supports dynamic delivery through Google Play Feature Delivery / split APKs. When Dart code requests a deferred library via `deferred as`, the embedder must download or extract the split APK and load the compiled snapshot into the running isolate. @@ -694,9 +638,9 @@ Passing file paths would require extracting all assets to disk during startup, w typedef struct { size_t struct_size; const uint8_t* mapping; - size_t size; + size_t mapping_size; void* user_data; - VoidCallback release_callback; + VoidCallback destruction_callback; } FlutterAssetMapping; typedef bool (*FlutterAssetResolverGetAsMappingCallback)( @@ -761,16 +705,16 @@ typedef struct { void* user_data; const FlutterProjectArgs* project_args; const FlutterRendererConfig* renderer_config; -} FlutterEngineSpawnConfig; +} FlutterEngineSpawnInfo; FLUTTER_EXPORT FlutterEngineResult FlutterEngineSpawn( FLUTTER_API_SYMBOL(FlutterEngine) parent_engine, - const FlutterEngineSpawnConfig* config, + const FlutterEngineSpawnInfo* config, FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); ``` -The [`FlutterEngineSpawnConfig`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240) so that spawned isolates mount distinct route hierarchies immediately upon launch. +The [`FlutterEngineSpawnInfo`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240) so that spawned isolates mount distinct route hierarchies immediately upon launch. Spawned shells share the parent engine's [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context.h) / Impeller device context (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. @@ -973,7 +917,6 @@ Several Android JNI entry points in `FlutterMain` and `FlutterJNI` interact with 2. **Hardware-Accelerated Image Decoding (`//flutter/lib/ui/painting/image_generator.h` and `//flutter/skia` Severance)**: Android API 28+ supports hardware-accelerated image decoding via `ImageDecoder`. To register platform image decoders without subclassing internal C++ `flutter::ImageGenerator` or `SkImageGenerator` types, `embedder.h` adds `FlutterEngineRegisterImageGenerator` (`FlutterImageGeneratorRegistrationInfo`, `FlutterImageGeneratorFactoryCallback`, `FlutterImageGeneratorGetInfoCallback`, and `FlutterImageGeneratorDecodeCallback`) backed by an internal `EmbedderImageLRU` cache inside `shell/platform/embedder/`. 3. **Font Manager Prefetching (`//flutter/txt` Severance)**: During `FlutterMain.init`, Android warms up the default system font manager on a background worker. `FlutterEnginePrefetchDefaultFontManager()` exposes this one-time initialization in `embedder.h` so `flutter_main.cc` no longer includes `flutter/txt/src/txt/platform.h`. 4. **VM Service URI Discovery (`//flutter/runtime/dart_service_isolate.h` Severance)**: Tooling and integration tests discover the Dart VM Service observatory URL via `FlutterJNI.getVMServiceUri`. `FlutterEngineRegisterVMServiceUriCallback` and `FlutterEngineDeregisterVMServiceUriCallback` deliver the URI string asynchronously via a `FlutterVMServiceUriCallback` handle, removing the last `//flutter/runtime` header from `shell/platform/android/`. -5. **Synchronous Rasterizer Screenshots**: `FlutterEngineScreenshot` and `FlutterEngineFreeScreenshot` capture uncompressed RGBA pixel buffers (`FlutterEngineScreenshotData`) from the active rasterizer to service `FlutterJNI.takeScreenshot` without reaching into `flutter::Rasterizer::Screenshot`. ### Platform Channel Messaging, Background Handlers, and Task Queues From f25b6c5d93bd68c0864021ce1923ebb9d8517e44 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 14 Oct 2026 15:21:10 -0700 Subject: [PATCH 10/43] clean up initialize and lifecycle sections --- ...lutter-android-embedder-c-api-migration.md | 227 +++++++++++------- 1 file changed, 146 insertions(+), 81 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 5f09004..10812d0 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -235,11 +235,21 @@ This level of coupling creates several concrete maintenance problems: To allow the Android embedder to communicate with the engine exclusively through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the C-API must be extended to cover Android-specific subsystem requirements. Each proposed extension corresponds directly to uncoupling one of the internal subsystems identified in the dependency audit and supporting a target embedder component (`FlutterEmbedderNative`, `JniRouter` / `JniDelegate` / `JvmInvoker`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17)). ### Graphics Surface Setup, Context Ownership, and Surface Lifecycle ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)) - -On Android, graphics context creation must be cleanly decoupled from window surface presentation. The embedder initializes or acquires the graphics context (such as Vulkan [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html), physical device, logical [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html), and Vulkan proc loaders, or an `EGLDisplay`, `EGLContext`, and `EGLConfig`) prior to invoking [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). - -In the existing engine architecture, [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) defers the decision between Vulkan and OpenGL ES until [`GetImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L162) is first called on the raster thread via [`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570): - +#### How `SetupImpellerContext` Works on Android Today +During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L275-L333) creates the `PlatformView` on the Android UI thread (`on_create_platform_view`) and then posts a `CreateImpellerContext` task to the raster task runner without blocking the UI thread: +```cpp +// Existing raster-thread task in shell/common/shell.cc (L316-L324) +fml::TaskRunner::RunNowOrPostTask( + task_runners.GetRasterTaskRunner(), + fml::MakeCopyable([..., platform_view_ptr]() mutable { + TRACE_EVENT0("flutter", "CreateImpellerContext"); + platform_view_ptr->SetupImpellerContext(); + std::shared_ptr impeller_context = + platform_view_ptr->GetImpellerContext(); + ... + })); +``` +`PlatformView::SetupImpellerContext()` was added (in [#167765](https://github.com/flutter/flutter/pull/167765)) because Vulkan driver and context initialization can take 100+ ms on some Android devices. In the current Android embedder, `PlatformViewAndroid` constructs [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) as an empty shell on the UI thread and defers all graphics work until `CreateImpellerContext` runs [`PlatformViewAndroid::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570) on the raster thread: ```cpp // Existing deferred graphics resolution in shell/platform/android/android_context_dynamic_impeller.cc void AndroidContextDynamicImpeller::SetupImpellerContext() { @@ -255,19 +265,140 @@ void AndroidContextDynamicImpeller::SetupImpellerContext() { } } ``` +That single raster-thread call currently performs two distinct jobs inside `shell/platform/android/` (which is why `shell/platform/android/` includes 29 internal `//flutter/impeller` headers): +1. **Choosing the backend and creating the underlying GPU device**: [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120) checks system properties and a known-bad SoC list, loads `libvulkan.so`, creates the [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html) and [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), checks Impeller's known-bad driver list ([`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)) in [`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.cc#L65-L69), and falls back to OpenGL ES if any step fails. +2. **Constructing the `impeller::Context`**: `AndroidContextVKImpeller` and `AndroidContextGLImpeller` call [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h) or [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h) to set up shader library mappings, the pipeline cache, the memory allocator, and descriptor/command pools. +#### Why Moving to `embedder.h` Requires Changes on Both Sides +When Android migrates to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `PlatformViewAndroid` and `AndroidContextDynamicImpeller` are removed. The Android embedder no longer creates or owns `impeller::Context`; it creates only raw Vulkan (`VkInstance`, `VkPhysicalDevice`, `VkDevice`, [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html)) or EGL (`EGLDisplay`, `EGLContext`, `EGLConfig`) handles and passes them in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) to [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). Inside the engine, [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h) (`EmbedderSurfaceVulkanImpeller` and `EmbedderSurfaceGLImpeller`) own the `impeller::Context`. +Preserving Android's non-blocking startup behavior requires two changes: +**Backend selection and `VkDevice` creation must happen before `FlutterEngineInitialize`**, because `FlutterRendererConfig` requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when `FlutterEngineInitialize` is called—before `Shell::CreateWithSnapshot` or the raster thread runs. +2. **`PlatformViewEmbedder` and `EmbedderSurface` must be wired up to `SetupImpellerContext()` in `shell/platform/embedder/`**, because [#167765](https://github.com/flutter/flutter/pull/167765) only implemented `SetupImpellerContext()` on `PlatformViewAndroid`. In `shell/platform/embedder/` today: + - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) does not override `SetupImpellerContext()`. It only overrides `GetImpellerContext()`, which calls `embedder_surface_->CreateImpellerContext()`. + - Despite the name `CreateImpellerContext()`, both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L680-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L496-L499)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. Without changing `shell/platform/embedder/`, migrating Android to `embedder.h` would move `impeller::Context` construction back onto the UI thread. +#### Backend Selection and Background Vulkan Probe Before `FlutterEngineInitialize` +To choose between `kVulkan` and `kOpenGL` before `FlutterEngineInitialize` without blocking the Android UI thread, the embedder runs a two-stage probe on the background executor task already started by [`FlutterLoader.startInitialization`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L197-L291) and joins that task in `FlutterLoader.ensureInitializationComplete`: +- **Stage 1 (Android system-property and SoC deny-list)**: Before loading `libvulkan.so`, the Android embedder runs the checks from [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120): known emulators, Huawei devices (`ro.com.google.clientidbase`), MediaTek devices with `ro.vendor.build.version.sdk < 32`, and the `ro.product.board` deny-list ([`IsKnownBadSOC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L68-L77)). Matching devices select `kOpenGL` immediately without loading Vulkan. +- **Stage 2 (Impeller known-bad driver query via `embedder.h`)**: If Stage 1 passes, the background task loads Vulkan, selects a physical device, and reads `VkPhysicalDeviceProperties`. To check those properties against Impeller's [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377) while keeping the driver blocklist in one place inside Impeller, `embedder.h` adds a stateless query function: +```c +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanDriverProperties). + size_t struct_size; + /// Vulkan API version reported by VkPhysicalDeviceProperties::apiVersion. + uint32_t api_version; + /// Driver version reported by VkPhysicalDeviceProperties::driverVersion. + uint32_t driver_version; + /// PCI/Khronos vendor ID reported by VkPhysicalDeviceProperties::vendorID. + uint32_t vendor_id; + /// Device ID reported by VkPhysicalDeviceProperties::deviceID. + uint32_t device_id; + /// NUL-terminated device name string from + /// VkPhysicalDeviceProperties::deviceName; only read during the call. + const char* device_name; +} FlutterVulkanDriverProperties; +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineQueryVulkanDriverSupport( + const FlutterVulkanDriverProperties* properties, + bool* out_is_known_bad); +``` +The embedder initializes `is_known_bad = true` before calling `FlutterEngineQueryVulkanDriverSupport`. If the call fails or leaves `is_known_bad` set to `true` (unless Vulkan was explicitly forced in a non-release build), or if `VkDevice` creation fails, the embedder tears down its Vulkan handles and falls back to `kOpenGL`. +#### Deferring `impeller::Context` Creation in `shell/platform/embedder/` (`SetupImpellerContext`) +To keep `impeller::ContextVK::Create` and `impeller::ContextGLES::Create` on the raster thread instead of running them in the `EmbedderSurface` constructors on the UI thread, `shell/platform/embedder/` is updated as follows: +- [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) adds `virtual void SetupImpellerContext() {}`. +- [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) overrides `PlatformView::SetupImpellerContext()` and forwards the call to `embedder_surface_->SetupImpellerContext()`. +- [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L20-L95) validates the `VulkanDispatchTable` callbacks and stores `impeller::ContextVK::Settings` (including `EmbedderData` handles and extension lists) in its constructor without creating the Impeller context. It overrides `SetupImpellerContext()` to run `context_ = impeller::ContextVK::Create(std::move(settings_))` on the raster thread when `Shell::CreateWithSnapshot` executes the `CreateImpellerContext` task. +- [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L77-L132) likewise validates `gl_dispatch_table_` in its constructor and moves `gl_make_current_callback()`, `ProcTableGLES` creation, `impeller::ContextGLES::Create(...)`, and `gl_clear_current_callback()` into `EmbedderSurfaceGLImpeller::SetupImpellerContext()` on the raster thread. +```cpp +// In shell/platform/embedder/platform_view_embedder.cc +void PlatformViewEmbedder::SetupImpellerContext() { + if (embedder_surface_ != nullptr) { + embedder_surface_->SetupImpellerContext(); + } +} +std::shared_ptr PlatformViewEmbedder::GetImpellerContext() + const { + if (embedder_surface_ == nullptr) { + return nullptr; + } + return embedder_surface_->CreateImpellerContext(); +} +// In shell/platform/embedder/embedder_surface_vulkan_impeller.cc +void EmbedderSurfaceVulkanImpeller::SetupImpellerContext() { + if (context_ || !valid_) { + return; + } + context_ = impeller::ContextVK::Create(std::move(settings_)); + if (!context_) { + FML_LOG(ERROR) << "Failed to initialize Vulkan Context."; + valid_ = false; + } +} +``` +```mermaid +sequenceDiagram + autonumber + participant UI as "Android UI Thread" + participant Bg as "FlutterLoader Executor" + participant Emb as "PlatformViewEmbedder & EmbedderSurface" + participant Raster as "Engine Raster Thread" + UI->>Bg: FlutterLoader.startInitialization() + Bg->>Bg: Stage 1: check system properties & SoC deny-list + Bg->>Bg: Create VkInstance & read VkPhysicalDeviceProperties + Bg->>Emb: FlutterEngineQueryVulkanDriverSupport(props, &is_known_bad) + Emb-->>Bg: kSuccess (is_known_bad == false) + Bg->>Bg: Create VkDevice & VkQueue (or fall back to EGL if known-bad) + UI->>Bg: FlutterLoader.ensureInitializationComplete() (join probe) + Bg-->>UI: Resolved backend & device handles + UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig) + Emb->>Emb: Construct EmbedderSurfaceVulkanImpeller (store settings_ only) + UI->>Emb: FlutterEngineRunInitialized() -> Shell::CreateWithSnapshot() + Emb->>Raster: Post CreateImpellerContext task + Emb-->>UI: Return without waiting for ContextVK + Raster->>Emb: PlatformViewEmbedder::SetupImpellerContext() + Emb->>Emb: EmbedderSurfaceVulkanImpeller::SetupImpellerContext() + Emb->>Emb: context_ = impeller::ContextVK::Create(std::move(settings_)) + Raster->>Emb: PlatformViewEmbedder::GetImpellerContext() + Emb-->>Raster: context_ +``` +Once the context is initialized, window surface attachments ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and swapchains are created and destroyed across Android [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events (`FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` - see next section) without recreating the `VkDevice`, `EGLContext`, or `impeller::Context`. -Because `embedder.h` requires a concrete renderer configuration struct (`FlutterRendererConfig` selecting `kOpenGL` or `kVulkan`) at `FlutterEngineInitialize` time, the embedder resolves the graphics backend during initialization before passing renderer pointers to the engine. +### Renderer Availability and Surface Lifecycle +Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) are created and destroyed by the OS during [`Activity`](https://developer.android.com/reference/android/app/Activity) transitions, configuration changes, and backgrounding while the `FlutterEngine` remains alive. -It is not explicitly needed to connect this API through the Embedder API though. This can be handled during Android's initialization path. +#### Decoupling Configuration from Surface Availability -Performing synchronous Vulkan driver queries during `FlutterLoader.ensureInitializationComplete` on Android's main thread introduces a cold-startup delay on devices due to driver library loading and physical device property enumeration. So it will need to offloaded to a background thread. +Static graphics configuration (the Vulkan device and queue handles, or the EGL context and callbacks) persists in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) for the lifetime of the engine, while presentation surfaces attach and detach across `Activity` lifecycle events. Recreating `FlutterRendererConfig` on every window change would re-initialize the GPU context and discard Impeller pipeline caches. +In the legacy architecture, `PlatformViewAndroid` called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L200) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L232) directly as a `PlatformView` subclass. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, two new functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): +```c +/// Notify the engine that the render surface or native window was created. +/// +/// Must be called on the platform thread after the embedder's window surface +/// exists and is ready for the raster thread to acquire images or framebuffers. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyCreated( + FLUTTER_API_SYMBOL(FlutterEngine) engine); -If the device experiences a driver crash or OS upgrade, the embedder falls back safely to OpenGLES. +/// Notify the engine that the render surface or native window is being +/// destroyed. +/// +/// Must be called on the platform thread before the embedder releases the +/// underlying window surface. This call blocks until the raster thread has +/// torn down the on-screen render surface and the IO thread has drained +/// pending GPU resource deletions. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyDestroyed( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +``` +> [!NOTE] **New Surface Lifecycle Exports** +> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are new additions to `embedder.h` exposing `EmbedderEngine::NotifyCreated` and `EmbedderEngine::NotifyDestroyed` (`PlatformView::NotifyCreated` and `PlatformView::NotifyDestroyed`). -> [!NOTE] **Backend Simplification: Dropping Skia** -> If legacy Skia support is dropped in favor of an Impeller-only architecture on Android, all legacy Skia context setup, EGL display and context fallback handling, and Ganesh header dependencies are eliminated. Graphics surface initialization narrows cleanly to Impeller Vulkan with an Impeller OpenGL ES fallback. +#### Lifecycle Mapping and Synchronous Destruction Contract -### Replacing Internal `AndroidExternalViewEmbedder` with `FlutterCompositor` (Preserving Hybrid Composition) +These functions map to Android [`SurfaceHolder.Callback`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback) events on the platform thread: +- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder binds the [`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window) and swapchain surface first, then calls `FlutterEngineNotifyCreated` so the rasterizer can allocate its GPU surface and resume rendering. +- [`surfaceChanged`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder,%20int,%20int,%20int)): The embedder sends the updated physical dimensions and device pixel ratio via `FlutterEngineSendWindowMetricsEvent`. +- [`surfaceDestroyed`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)): The embedder calls `FlutterEngineNotifyDestroyed` before releasing the `ANativeWindow`. Inside the engine, [`Shell::OnPlatformViewDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1066-L1129) posts teardown to the raster thread, chains an unref-queue drain on the IO thread, and waits on a latch before returning to the platform thread. Only after `FlutterEngineNotifyDestroyed` returns does the embedder destroy its swapchain and release the `ANativeWindow` back to the OS, preventing `SIGSEGV` or `EGL_BAD_NATIVE_WINDOW` errors from in-flight raster work. + +### Replacing Internal [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) with `FlutterCompositor` (Preserving Hybrid Composition) Android supports four platform view composition modes: @@ -276,7 +407,9 @@ Android supports four platform view composition modes: 3. **Texture Layer Hybrid Composition (TLHC)**: Attaches the Android `View` to the view hierarchy (`PlatformViewWrapper`) while redirecting its draw output into an offscreen [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) or [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) render target registered as a Flutter external texture (`createForTextureLayer` / `configureForTextureLayerComposition`). 4. **Virtual Display (VD)**: Hosts the Android `View` inside an offscreen [`VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay) (`VirtualDisplayController`) backed by a `TextureRegistry` render target (`SurfaceProducer` or `SurfaceTexture`) and composites its output as a Flutter external texture (`createForTextureLayer` / `configureForVirtualDisplay`). -**All four platform view composition modes are preserved; retiring Hybrid Composition (HC) or Virtual Display (VD) is not an option.** Standard Hybrid Composition and Virtual Display remain required across Android devices, API levels, and embedded view hierarchies (such as views containing `SurfaceView` or `TextureView` children where `PlatformViewsController` falls back from TLHC to Virtual Display). What this migration eliminates is **only the internal C++ `flutter::ExternalViewEmbedder` subclass (`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`) and its direct dependencies on `//flutter/flow` (`embedded_views.h`) and `fml::RasterThreadMerger`**, rather than any platform view composition mode. +**All four platform view composition modes are preserved; retiring Hybrid Composition (HC) or Virtual Display (VD) is not an option.** + +Standard Hybrid Composition and Virtual Display remain required across Android devices, API levels, and embedded view hierarchies (such as views containing `SurfaceView` or `TextureView` children where `PlatformViewsController` falls back from TLHC to Virtual Display). What this migration eliminates is **only the internal C++ `flutter::ExternalViewEmbedder` subclass (`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`) and its direct dependencies on `//flutter/flow` (`embedded_views.h`) and `fml::RasterThreadMerger`**, rather than any platform view composition mode. In the legacy architecture, [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) subclassed `flutter::ExternalViewEmbedder` directly and used [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) in [`PostPrerollAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213) to dynamically lease and merge the raster thread onto the platform thread during HC frames: @@ -727,74 +860,6 @@ Spawned shells share the parent engine's [`AndroidContext`](https://github.com/f Migrating the Android embedder onto `embedder.h` and `FlutterCompositor` unlocks both patterns: `FlutterEngineSpawn` supports `FlutterEngineGroup`, while per-view `FlutterCompositor` callbacks (`FlutterPresentViewInfo.view_id` and `FlutterBackingStoreConfig.view_id`) provide the foundation for single-isolate multiview and multi-window rendering. -### Renderer Availability, Multi-Window Lifecycle, and `FlutterViewId` Keying - -Android window surfaces are created and destroyed dynamically by the operating system during [`Activity`](https://developer.android.com/reference/android/app/Activity) transitions, configuration changes, or backgrounding. The embedder must coordinate surface availability and GPU context lifecycle without calling protected methods on [`PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L51). - -#### Orthogonality of Configuration and Availability - -A central design principle of the C-API architecture is that renderer configuration and surface availability are orthogonal concerns: - -1. Static graphics configuration (such as the Vulkan device, queue handles, or EGL context) persists inside [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) throughout the engine's lifetime. -2. Presentation targets ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) attach and detach across [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events. -3. GPU execution permission toggles between foreground and background states to satisfy OS process management policies. - -Attempting to recreate the entire `FlutterRendererConfig` during window lifecycle changes introduces severe driver overhead and destroys pipeline caches. Instead, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) decouples static configuration from dynamic surface availability: - -```c -/// GPU availability states for FlutterEngineSetGpuAvailability. -typedef enum { - /// GPU operations permitted. Rasterizer executes normally. - kFlutterGpuAvailabilityAvailable = 0, - /// Intermediate backgrounding state: synchronously flushes pending command - /// queues, drains resource deletions on the IO task runner, and marks the - /// GPU unavailable before process suspension. - kFlutterGpuAvailabilityFlushAndMakeUnavailable = 1, - /// Blocks raster thread from submitting commands to the GPU driver. - kFlutterGpuAvailabilityUnavailable = 2, -} FlutterGpuAvailability; - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineNotifyCreated( - FLUTTER_API_SYMBOL(FlutterEngine) engine); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineNotifyDestroyed( - FLUTTER_API_SYMBOL(FlutterEngine) engine); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineSetGpuAvailability( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - FlutterGpuAvailability availability); -``` - -> [!IMPORTANT] [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) is proposed, not existing API -> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are already in `embedder.h`. `FlutterEngineSetGpuAvailability` is not. It is proposed here as a thin exposure of [`Shell::SetGpuAvailability`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L404), which the engine already implements and which the iOS embedder already drives. The proposal adds no new engine behavior; it gives the C-API access to behavior that exists. - -#### Implicit Single-View vs. Explicit Multi-View Surface Lifecycle - -In `embedder.h`, `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` manage the surface lifecycle of the implicit default view (`kFlutterImplicitViewId = 0`). When an Android application attaches secondary `FlutterView` instances to the same `FlutterEngine` (such as foldable dual-screen postures, secondary [`android.app.Presentation`](https://developer.android.com/reference/android/app/Presentation) external displays, or multi-surface Add-to-App hosts), the embedder registers and removes secondary views via [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) and [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062): - -- `FlutterCompositor.present_view_callback` receives a [`FlutterPresentViewInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1498) struct carrying the target `FlutterViewId view_id`, and `FlutterCompositor.create_backing_store_callback` receives `FlutterBackingStoreConfig.view_id`. -- To support multi-view and multi-window rendering without cross-view state corruption, `FlutterEmbedderNative` and `AndroidSurfaceControl` key native window handles (`ANativeWindow*`) and `ASurfaceControl*` layer trees by `FlutterViewId` rather than storing a single engine-global surface pointer. - -#### Lifecycle Mapping and Synchronous Destruction Contract - -These entry points map to Android [`SurfaceHolder.Callback`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback) and [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events: - -- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder binds the native window ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) for the target `FlutterViewId` and calls `FlutterEngineNotifyCreated` (for the implicit view) or `FlutterEngineAddView` (for secondary views) to allocate swapchains and resume rendering. -- [`surfaceChanged`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder,%20int,%20int,%20int)): The embedder transmits physical dimension and scale changes to the engine via `FlutterEngineSendWindowMetricsEvent` with the target `view_id`. -- [`surfaceDestroyed`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)): **Hard Synchronous Contract**. The embedder calls `FlutterEngineNotifyDestroyed` (or `FlutterEngineRemoveView`). The engine flushes pending rasterization workloads, tears down on-screen swapchains, drains pending resource deletions on the IO thread, and releases native window references synchronously before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns to the Android OS. This prevents `SIGSEGV` or `EGL_BAD_NATIVE_WINDOW` errors caused by subsequent rasterization attempts on invalidated window handles. -- [`Activity`](https://developer.android.com/reference/android/app/Activity) [`onStop`](https://developer.android.com/reference/android/app/Activity#onStop()) / [`onStart`](https://developer.android.com/reference/android/app/Activity#onStart()): When an application is backgrounded, the OS may reclaim volatile GPU allocations. The embedder invokes `FlutterEngineSetGpuAvailability(engine, kFlutterGpuAvailabilityFlushAndMakeUnavailable)` followed by `kFlutterGpuAvailabilityUnavailable` to drain command buffers and suspend raster tasks without terminating the engine or tearing down isolate groups. When the [`Activity`](https://developer.android.com/reference/android/app/Activity) returns to the foreground, `FlutterEngineSetGpuAvailability(engine, kFlutterGpuAvailabilityAvailable)` restores raster scheduling. - -#### Cross-Platform Alignment with iOS - -This lifecycle mechanism establishes direct parity with the iOS engine backgrounding contract in [`shell/platform/darwin/ios/framework/Source/FlutterEngine.mm`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm#L857) and [`Shell::SetGpuAvailability`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L64): - -- On iOS, submitting GPU commands while in the background triggers fatal OS process termination (Jetsam `0x8badf00d`). iOS calls `SetGpuAvailability(kFlushAndMakeUnavailable)` upon backgrounding to flush pending work and prevent crashes. -- On Android, attempting GPU operations after window detachment risks driver timeouts (`VK_ERROR_DEVICE_LOST`) or memory corruption when the OS evicts application GPU memory. -- `FlutterEngineSetGpuAvailability` provides a unified, cross-platform C-API abstraction that satisfies both Android and iOS operational requirements. - ### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). From 4e2d3c2ba7867cc3e882c911e24a2984f8a7d49d Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 14 Oct 2026 15:33:08 -0700 Subject: [PATCH 11/43] remove unnecessary api is_valid_after_asset_manager_change --- rfc/410.0000-flutter-android-embedder-c-api-migration.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 10812d0..7945f5e 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -784,10 +784,7 @@ typedef bool (*FlutterAssetResolverGetAsMappingCallback)( typedef struct { size_t struct_size; void* user_data; - FlutterAssetResolverGetAsMappingCallback get_as_mapping_callback; - // Indicates if the resolver remains valid after the underlying Android - // AAssetManager instance changes or is recreated. - bool is_valid_after_asset_manager_change; + FlutterAssetResolverGetAsMappingCallback get_as_mapping_callback; VoidCallback release_callback; } FlutterCustomAssetResolver; From 9e6a0374196384f591ebe7ed9c4afe128a93e172 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 14 Oct 2026 15:42:16 -0700 Subject: [PATCH 12/43] section reordering - putting all platform view sections together --- ...lutter-android-embedder-c-api-migration.md | 280 +++++++++--------- 1 file changed, 141 insertions(+), 139 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 7945f5e..21fb38a 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -468,93 +468,72 @@ When Hybrid Composition is active on API 21–33 without `fml::RasterThreadMerge 3. **Atomic `ImageReader` Gate in `PlatformViewsController.onEndFrame()`**: During the platform-thread commit in [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391), `flutterView.acquireLatestImageViewFrame()` and `overlayView.acquireLatestImage()` pull the newly queued GPU buffers and invalidate the `FlutterImageView` instances. `finishFrame()` checks `isFrameRenderedUsingImageReaders` (verifying that the converted root `FlutterImageView` and every active `PlatformOverlayView` acquired a valid image) before setting `parentView.setVisibility(View.VISIBLE)`. Consequently, the base `FlutterImageView`, the `FlutterMutatorView` (wrapping the native `View`), and all `PlatformOverlayView` layers are drawn together in a single `ViewRootImpl.performTraversals()` pass on the Android UI thread. When the last HC view is disposed (`currentFrameUsedPlatformViewIds.isEmpty()`), `onEndFrame()` invokes `flutterView.revertImageView()` to return to direct `SurfaceView` presentation. -### External Textures (Zero-Copy Vulkan and OpenGL ES) - -Android provides two distinct mechanisms for external textures: -- [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline that imports [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles directly into Vulkan ([`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html)) or EGL ([`EGLImage`](https://registry.khronos.org/EGL/sdk/docs/man/html/eglCreateImage.xhtml)). -- [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture): A legacy OpenGL ES path where native frames render into a texture ID updated with [`SurfaceTexture.updateTexImage()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()). - -The public Embedder API previously lacked a way to deliver Vulkan-backed external images without depending on the internal [`flutter::Texture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L27) class. The design closes this with a pull-based frame callback on the renderer config rather than a push-based registration entry point. The embedder claims a texture ID through the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) and signals new content with [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250); the engine calls back to collect the backing image for the frame it is about to compose: +### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State -```c -/// Specifies the backing type of a Vulkan external texture. -typedef enum { - kFlutterVulkanExternalTextureTypeVkImage, - kFlutterVulkanExternalTextureTypeAHardwareBuffer, -} FlutterVulkanExternalTextureType; +Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). -typedef struct { - size_t struct_size; - size_t width; - size_t height; - /// VkFormat. May be 0 for an AHardwareBuffer, in which case the engine - /// queries the format from the buffer itself. - uint32_t format; - FlutterVulkanExternalTextureType type; - union { - FlutterVulkanImageHandle vk_image; - FlutterAHardwareBufferHandle hardware_buffer; - }; - /// VkImageLayout of the image. - uint32_t image_layout; - /// NULL for standard sampling; non-NULL for YCbCr camera and video buffers. - const FlutterVulkanYcbcrConversionInfo* ycbcr_conversion_info; - void* user_data; - VoidCallback destruction_callback; -} FlutterVulkanExternalTexture; +Unlike legacy Hybrid Composition (see Dynamic Raster Thread Merging), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: -/// Invoked on an engine-managed thread to obtain the texture for a frame. -typedef bool (*FlutterVulkanExternalTextureFrameCallback)( - void* user_data, - int64_t texture_identifier, - size_t width, - size_t height, - FlutterVulkanExternalTexture* texture_out); -``` +- Rasterization and Android view updates execute on separate threads without leasing or merging overhead. +- The embedder arranges the base Flutter surface, native platform views, and overlay slices into a tree of [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) nodes. +- Visual synchronization occurs at the GPU level via hardware sync fences ([`ASyncFence`](https://developer.android.com/ndk/reference/group/sync) or Vulkan binary semaphores) passed through [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits, synchronizing buffer availability without CPU blocking. +- **Per-`FlutterViewId` Value-Captured Frame State**: In a multiview or multi-window environment, the raster thread renders View A (`FlutterPresentViewInfo.view_id = 0`) and immediately begins rasterizing View B (`view_id = 1`) while the platform thread is still applying View A's platform view layout mutations. Storing per-frame composition state (such as `composition_order_`, `picture_bounds_`, or `views_to_recomposite_`) as mutable fields on a shared controller causes cross-thread data races between consecutive views. `AndroidPlatformViewsController` eliminates this hazard by keying active platform view hierarchies by `FlutterViewId` and capturing each view's ordered `FlutterLayer` slice and `FlutterPlatformViewMutation` parameters by value inside the `present_view_callback` closure before dispatching to the platform thread. -`FlutterVulkanRendererConfig` carries this callback as `external_texture_frame_callback`. A parallel [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es) sits on both [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) and [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693), so an [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) reaches either backend without an Android-specific engine entry point. +To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the public C-API provides multi-layer compositing primitives through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2322): -The discriminated union is what makes this workable on Android. [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) hands out [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles rather than [`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html) handles, and `ycbcr_conversion_info` carries the external format identifier and component swizzle that camera and video decoder buffers require. Vendor buffers with an external format cannot be sampled correctly without it. +1. **Compositor Backing Stores**: The embedder registers a `FlutterCompositor` configuration where `create_backing_store_callback` allocates [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains managed by an embedder-owned surface pool (`AndroidSurfaceControl`). +2. **Layer Slicing and Dispatch**: During frame presentation, `present_view_callback` (or `present_layers_callback`) delivers an ordered list of `FlutterLayer` items scoped to `FlutterPresentViewInfo.view_id`: + - `kFlutterLayerContentTypeBackingStore`: Rendered Flutter graphics slices (base surface or overlay surfaces). + - `kFlutterLayerContentTypePlatformView`: Embedded native Android [`View`](https://developer.android.com/reference/android/view/View) nodes, accompanied by [`FlutterPlatformViewIdentifier`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1883) and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) arrays (see Platform View Mutator Stack Serialization). +3. **Fence Transfer and Atomic Commit**: In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), extensions are added through [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198) instead of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L663). `FlutterBackingStorePresentInfo` is extended in place with a synchronization fence file descriptor: -For legacy [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) instances, the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) API with `kFlutterExternalTextureTypeOpenGL` is used. However, the existing [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1121) struct only specifies `target`, `name`, `format`, `width`, and `height`; it lacks a UV coordinate transformation matrix. On Android, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 matrix accounting for camera sensor rotation, video decoder padding, and coordinate inversion. In the existing architecture, [`SurfaceTextureExternalTexture::Update()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc#L134-L143) queries this matrix through [`PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1626-L1645) and returns an [`SkM44`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkM44.h) matrix directly: +```c +typedef struct { + /// The size of this struct. Must be sizeof(FlutterBackingStorePresentInfo). + size_t struct_size; -```cpp -// Existing UV transformation querying in shell/platform/android/surface_texture_external_texture.cc -void SurfaceTextureExternalTexture::Update() { - jni_facade_->SurfaceTextureUpdateTexImage( - fml::jni::ScopedJavaLocalRef(surface_texture_)); - transform_ = jni_facade_->SurfaceTextureGetTransformMatrix( - fml::jni::ScopedJavaLocalRef(surface_texture_)); -} + /// The area of the backing store that contains Flutter contents. + FlutterRegion* paint_region; -const SkM44& SurfaceTextureExternalTexture::GetCurrentUVTransformation() const { - return transform_; -} + /// File descriptor representing a sync_fence / ASyncFence. + /// When presenting on Android API 34+ via SurfaceControl.Transaction, + /// the engine passes this fence to the embedder. + /// + /// The embedder takes ownership of this file descriptor and must close it + /// (for example, by passing it to SurfaceControl.Transaction.setBuffer or + /// calling close()). A value of -1 indicates no fence. + int synchronization_fence_fd; +} FlutterBackingStorePresentInfo; ``` -Without this matrix, external video and camera frames render inverted or distorted. - -To resolve this gap, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es): +#### Synchronization Fence Lifecycle and Ownership -```c -typedef struct { - size_t struct_size; - uint32_t target; - uint32_t name; - uint32_t format; - void* user_data; - VoidCallback destruction_callback; - uint32_t width; - uint32_t height; - FlutterTransformation uv_transform; -} FlutterOpenGLTexture2; -``` +1. **GPU Fence Export**: When Impeller submits render passes for a backing store, it exports the GPU completion semaphore or fence into a POSIX `sync_fence` file descriptor using the [`VK_KHR_external_fence_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_fence_fd.html) extension (`vkGetFenceFdKHR`). +2. **Transfer to Embedder**: The engine populates `FlutterBackingStorePresentInfo.synchronization_fence_fd` and delivers the layers to `present_view_callback`. Ownership of the file descriptor transfers to the embedder. +3. **Buffer Attachment**: In HCPP mode, `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered `FlutterLayer` sequence directly into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`). The embedder attaches the backing store `AHardwareBuffer` and fence file descriptor to the layer via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)). Passing the fence to Android's transaction wraps or closes the descriptor; if unused or on error, the embedder must close it with `::close(fd)`. +4. **SurfaceFlinger Wait & Buffer Release**: Android's window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) waits on the acquire fence on the GPU before compositing. Once compositing completes, SurfaceFlinger signals the transaction's release fence callback, which enables the embedder to recycle the backing store back to its surface pool. -Under OpenGL ES, platform view compositing also faces constraints: because [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction sync fences depend on Vulkan hardware synchronization, OpenGL platform views must use Texture Layer Hybrid Composition (TLHC) or legacy Hybrid Composition rather than HCPP. +```mermaid +sequenceDiagram + autonumber + participant Impeller as Impeller Vulkan Backend + participant Engine as Flutter Engine Core + participant Embedder as Android Embedder C++ + participant SC as SurfaceControl.Transaction Java and NDK + participant SF as SurfaceFlinger Android OS -> [!NOTE] External Texture Simplification: Dropping Skia and Legacy OpenGL Textures -> Retiring legacy Skia and legacy OpenGL `SurfaceTexture` pathways in favor of Impeller Vulkan allows the embedder to eliminate legacy UV coordinate transform matrix conversions (`FlutterOpenGLTexture2`) and standardize directly on zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports through [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es). + Impeller->>Impeller: Submit Vulkan render commands + Impeller->>Impeller: Export VkFence / VkSemaphore to sync_fence fd via vkGetFenceFdKHR + Impeller->>Engine: Deliver backing store + fence fd + Engine->>Embedder: present_view_callback with view_id and synchronization_fence_fd + Embedder->>SC: setBuffer with surfaceControl, hardwareBuffer, fenceFd + Embedder->>SC: applyTransactions or swapTransactions + SC->>SF: Commit transaction with acquire fence + SF->>SF: GPU waits on acquire fence before compositing + SF-->>Embedder: Release fence signaled when buffer reading completes + Embedder->>Embedder: Return buffer to AndroidSurfaceControl for recycling +``` ### Platform View Mutator Stack Serialization @@ -638,6 +617,95 @@ The array is an array of pointers rather than an array of values. Indexing strid > [!NOTE] Mutator Simplification: Dropping Complex Path Clipping > If legacy Hybrid Composition under Skia is dropped, or if platform view clipping is restricted to standard geometric bounds, `kFlutterPlatformViewMutationTypeClipPath` and the whole `FlutterPath` family become dead weight. Mutations would then convey only affine transformations, bounding rectangles, and corner radii, all of which map directly to Android `SurfaceControl` or `View` properties. +### External Textures (Zero-Copy Vulkan and OpenGL ES) + +Android provides two distinct mechanisms for external textures: + +- [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline that imports [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles directly into Vulkan ([`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html)) or EGL ([`EGLImage`](https://registry.khronos.org/EGL/sdk/docs/man/html/eglCreateImage.xhtml)). +- [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture): A legacy OpenGL ES path where native frames render into a texture ID updated with [`SurfaceTexture.updateTexImage()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()). + +The public Embedder API previously lacked a way to deliver Vulkan-backed external images without depending on the internal [`flutter::Texture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L27) class. The design closes this with a pull-based frame callback on the renderer config rather than a push-based registration entry point. The embedder claims a texture ID through the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) and signals new content with [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250); the engine calls back to collect the backing image for the frame it is about to compose: + +```c +/// Specifies the backing type of a Vulkan external texture. +typedef enum { + kFlutterVulkanExternalTextureTypeVkImage, + kFlutterVulkanExternalTextureTypeAHardwareBuffer, +} FlutterVulkanExternalTextureType; + +typedef struct { + size_t struct_size; + size_t width; + size_t height; + /// VkFormat. May be 0 for an AHardwareBuffer, in which case the engine + /// queries the format from the buffer itself. + uint32_t format; + FlutterVulkanExternalTextureType type; + union { + FlutterVulkanImageHandle vk_image; + FlutterAHardwareBufferHandle hardware_buffer; + }; + /// VkImageLayout of the image. + uint32_t image_layout; + /// NULL for standard sampling; non-NULL for YCbCr camera and video buffers. + const FlutterVulkanYcbcrConversionInfo* ycbcr_conversion_info; + void* user_data; + VoidCallback destruction_callback; +} FlutterVulkanExternalTexture; + +/// Invoked on an engine-managed thread to obtain the texture for a frame. +typedef bool (*FlutterVulkanExternalTextureFrameCallback)( + void* user_data, + int64_t texture_identifier, + size_t width, + size_t height, + FlutterVulkanExternalTexture* texture_out); +``` + +`FlutterVulkanRendererConfig` carries this callback as `external_texture_frame_callback`. A parallel [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es) sits on both [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) and [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693), so an [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) reaches either backend without an Android-specific engine entry point. + +The discriminated union is what makes this workable on Android. [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) hands out [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles rather than [`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html) handles, and `ycbcr_conversion_info` carries the external format identifier and component swizzle that camera and video decoder buffers require. Vendor buffers with an external format cannot be sampled correctly without it. + +For legacy [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) instances, the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) API with `kFlutterExternalTextureTypeOpenGL` is used. However, the existing [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1121) struct only specifies `target`, `name`, `format`, `width`, and `height`; it lacks a UV coordinate transformation matrix. On Android, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 matrix accounting for camera sensor rotation, video decoder padding, and coordinate inversion. In the existing architecture, [`SurfaceTextureExternalTexture::Update()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc#L134-L143) queries this matrix through [`PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1626-L1645) and returns an [`SkM44`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkM44.h) matrix directly: + +```cpp +// Existing UV transformation querying in shell/platform/android/surface_texture_external_texture.cc +void SurfaceTextureExternalTexture::Update() { + jni_facade_->SurfaceTextureUpdateTexImage( + fml::jni::ScopedJavaLocalRef(surface_texture_)); + transform_ = jni_facade_->SurfaceTextureGetTransformMatrix( + fml::jni::ScopedJavaLocalRef(surface_texture_)); +} + +const SkM44& SurfaceTextureExternalTexture::GetCurrentUVTransformation() const { + return transform_; +} +``` + +Without this matrix, external video and camera frames render inverted or distorted. + +To resolve this gap, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es): + +```c +typedef struct { + size_t struct_size; + uint32_t target; + uint32_t name; + uint32_t format; + void* user_data; + VoidCallback destruction_callback; + uint32_t width; + uint32_t height; + FlutterTransformation uv_transform; +} FlutterOpenGLTexture2; +``` + +Under OpenGL ES, platform view compositing also faces constraints: because [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction sync fences depend on Vulkan hardware synchronization, OpenGL platform views must use Texture Layer Hybrid Composition (TLHC) or legacy Hybrid Composition rather than HCPP. + +> [!NOTE] External Texture Simplification: Dropping Skia and Legacy OpenGL Textures +> Retiring legacy Skia and legacy OpenGL `SurfaceTexture` pathways in favor of Impeller Vulkan allows the embedder to eliminate legacy UV coordinate transform matrix conversions (`FlutterOpenGLTexture2`) and standardize directly on zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports through [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es). + + ### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) In-Place Extension) Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in the original [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) struct, including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `identifier`, `traversal_parent`, and `hit_test_transform`. @@ -857,72 +925,6 @@ Spawned shells share the parent engine's [`AndroidContext`](https://github.com/f Migrating the Android embedder onto `embedder.h` and `FlutterCompositor` unlocks both patterns: `FlutterEngineSpawn` supports `FlutterEngineGroup`, while per-view `FlutterCompositor` callbacks (`FlutterPresentViewInfo.view_id` and `FlutterBackingStoreConfig.view_id`) provide the foundation for single-isolate multiview and multi-window rendering. -### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State - -Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). - -Unlike legacy Hybrid Composition (see Dynamic Raster Thread Merging), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: - -- Rasterization and Android view updates execute on separate threads without leasing or merging overhead. -- The embedder arranges the base Flutter surface, native platform views, and overlay slices into a tree of [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) nodes. -- Visual synchronization occurs at the GPU level via hardware sync fences ([`ASyncFence`](https://developer.android.com/ndk/reference/group/sync) or Vulkan binary semaphores) passed through [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits, synchronizing buffer availability without CPU blocking. -- **Per-`FlutterViewId` Value-Captured Frame State**: In a multiview or multi-window environment, the raster thread renders View A (`FlutterPresentViewInfo.view_id = 0`) and immediately begins rasterizing View B (`view_id = 1`) while the platform thread is still applying View A's platform view layout mutations. Storing per-frame composition state (such as `composition_order_`, `picture_bounds_`, or `views_to_recomposite_`) as mutable fields on a shared controller causes cross-thread data races between consecutive views. `AndroidPlatformViewsController` eliminates this hazard by keying active platform view hierarchies by `FlutterViewId` and capturing each view's ordered `FlutterLayer` slice and `FlutterPlatformViewMutation` parameters by value inside the `present_view_callback` closure before dispatching to the platform thread. - -To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the public C-API provides multi-layer compositing primitives through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2322): - -1. **Compositor Backing Stores**: The embedder registers a `FlutterCompositor` configuration where `create_backing_store_callback` allocates [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains managed by an embedder-owned surface pool (`AndroidSurfaceControl`). -2. **Layer Slicing and Dispatch**: During frame presentation, `present_view_callback` (or `present_layers_callback`) delivers an ordered list of `FlutterLayer` items scoped to `FlutterPresentViewInfo.view_id`: - - `kFlutterLayerContentTypeBackingStore`: Rendered Flutter graphics slices (base surface or overlay surfaces). - - `kFlutterLayerContentTypePlatformView`: Embedded native Android [`View`](https://developer.android.com/reference/android/view/View) nodes, accompanied by [`FlutterPlatformViewIdentifier`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1883) and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) arrays (see Platform View Mutator Stack Serialization). -3. **Fence Transfer and Atomic Commit**: In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), extensions are added through [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198) instead of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L663). `FlutterBackingStorePresentInfo` is extended in place with a synchronization fence file descriptor: - -```c -typedef struct { - /// The size of this struct. Must be sizeof(FlutterBackingStorePresentInfo). - size_t struct_size; - - /// The area of the backing store that contains Flutter contents. - FlutterRegion* paint_region; - - /// File descriptor representing a sync_fence / ASyncFence. - /// When presenting on Android API 34+ via SurfaceControl.Transaction, - /// the engine passes this fence to the embedder. - /// - /// The embedder takes ownership of this file descriptor and must close it - /// (for example, by passing it to SurfaceControl.Transaction.setBuffer or - /// calling close()). A value of -1 indicates no fence. - int synchronization_fence_fd; -} FlutterBackingStorePresentInfo; -``` - -#### Synchronization Fence Lifecycle and Ownership - -1. **GPU Fence Export**: When Impeller submits render passes for a backing store, it exports the GPU completion semaphore or fence into a POSIX `sync_fence` file descriptor using the [`VK_KHR_external_fence_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_fence_fd.html) extension (`vkGetFenceFdKHR`). -2. **Transfer to Embedder**: The engine populates `FlutterBackingStorePresentInfo.synchronization_fence_fd` and delivers the layers to `present_view_callback`. Ownership of the file descriptor transfers to the embedder. -3. **Buffer Attachment**: In HCPP mode, `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered `FlutterLayer` sequence directly into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`). The embedder attaches the backing store `AHardwareBuffer` and fence file descriptor to the layer via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)). Passing the fence to Android's transaction wraps or closes the descriptor; if unused or on error, the embedder must close it with `::close(fd)`. -4. **SurfaceFlinger Wait & Buffer Release**: Android's window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) waits on the acquire fence on the GPU before compositing. Once compositing completes, SurfaceFlinger signals the transaction's release fence callback, which enables the embedder to recycle the backing store back to its surface pool. - -```mermaid -sequenceDiagram - autonumber - participant Impeller as Impeller Vulkan Backend - participant Engine as Flutter Engine Core - participant Embedder as Android Embedder C++ - participant SC as SurfaceControl.Transaction Java and NDK - participant SF as SurfaceFlinger Android OS - - Impeller->>Impeller: Submit Vulkan render commands - Impeller->>Impeller: Export VkFence / VkSemaphore to sync_fence fd via vkGetFenceFdKHR - Impeller->>Engine: Deliver backing store + fence fd - Engine->>Embedder: present_view_callback with view_id and synchronization_fence_fd - Embedder->>SC: setBuffer with surfaceControl, hardwareBuffer, fenceFd - Embedder->>SC: applyTransactions or swapTransactions - SC->>SF: Commit transaction with acquire fence - SF->>SF: GPU waits on acquire fence before compositing - SF-->>Embedder: Release fence signaled when buffer reading completes - Embedder->>Embedder: Return buffer to AndroidSurfaceControl for recycling -``` - ### Task Runner Configuration, Thread Priorities, and Threading Modes The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (`platform_task_runner`) alongside dedicated worker task runners (`ui_task_runner`, `render_task_runner`, and `thread_priority_setter`). From 22d497eac84b84acc6ef61c15bfecab5af440035 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 10:34:10 -0700 Subject: [PATCH 13/43] more fixes --- rfc/410.0000-flutter-android-embedder-c-api-migration.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 21fb38a..3ad44fa 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -236,7 +236,7 @@ To allow the Android embedder to communicate with the engine exclusively through ### Graphics Surface Setup, Context Ownership, and Surface Lifecycle ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)) #### How `SetupImpellerContext` Works on Android Today -During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L275-L333) creates the `PlatformView` on the Android UI thread (`on_create_platform_view`) and then posts a `CreateImpellerContext` task to the raster task runner without blocking the UI thread: +During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L277-L335) creates the `PlatformView` on the Android UI thread (`on_create_platform_view`) and then posts a `CreateImpellerContext` task to the raster task runner without blocking the UI thread: ```cpp // Existing raster-thread task in shell/common/shell.cc (L316-L324) fml::TaskRunner::RunNowOrPostTask( @@ -265,13 +265,13 @@ void AndroidContextDynamicImpeller::SetupImpellerContext() { } } ``` -That single raster-thread call currently performs two distinct jobs inside `shell/platform/android/` (which is why `shell/platform/android/` includes 29 internal `//flutter/impeller` headers): +That single raster-thread call currently performs two distinct jobs inside `shell/platform/android/`: 1. **Choosing the backend and creating the underlying GPU device**: [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120) checks system properties and a known-bad SoC list, loads `libvulkan.so`, creates the [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html) and [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), checks Impeller's known-bad driver list ([`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)) in [`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.cc#L65-L69), and falls back to OpenGL ES if any step fails. 2. **Constructing the `impeller::Context`**: `AndroidContextVKImpeller` and `AndroidContextGLImpeller` call [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h) or [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h) to set up shader library mappings, the pipeline cache, the memory allocator, and descriptor/command pools. #### Why Moving to `embedder.h` Requires Changes on Both Sides When Android migrates to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `PlatformViewAndroid` and `AndroidContextDynamicImpeller` are removed. The Android embedder no longer creates or owns `impeller::Context`; it creates only raw Vulkan (`VkInstance`, `VkPhysicalDevice`, `VkDevice`, [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html)) or EGL (`EGLDisplay`, `EGLContext`, `EGLConfig`) handles and passes them in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) to [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). Inside the engine, [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h) (`EmbedderSurfaceVulkanImpeller` and `EmbedderSurfaceGLImpeller`) own the `impeller::Context`. Preserving Android's non-blocking startup behavior requires two changes: -**Backend selection and `VkDevice` creation must happen before `FlutterEngineInitialize`**, because `FlutterRendererConfig` requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when `FlutterEngineInitialize` is called—before `Shell::CreateWithSnapshot` or the raster thread runs. +1. **Backend selection and `VkDevice` creation must happen before `FlutterEngineInitialize`**, because `FlutterRendererConfig` requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when `FlutterEngineInitialize` is called—before `Shell::CreateWithSnapshot` or the raster thread runs. 2. **`PlatformViewEmbedder` and `EmbedderSurface` must be wired up to `SetupImpellerContext()` in `shell/platform/embedder/`**, because [#167765](https://github.com/flutter/flutter/pull/167765) only implemented `SetupImpellerContext()` on `PlatformViewAndroid`. In `shell/platform/embedder/` today: - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) does not override `SetupImpellerContext()`. It only overrides `GetImpellerContext()`, which calls `embedder_surface_->CreateImpellerContext()`. - Despite the name `CreateImpellerContext()`, both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L680-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L496-L499)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. Without changing `shell/platform/embedder/`, migrating Android to `embedder.h` would move `impeller::Context` construction back onto the UI thread. @@ -367,7 +367,7 @@ Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/ref #### Decoupling Configuration from Surface Availability Static graphics configuration (the Vulkan device and queue handles, or the EGL context and callbacks) persists in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) for the lifetime of the engine, while presentation surfaces attach and detach across `Activity` lifecycle events. Recreating `FlutterRendererConfig` on every window change would re-initialize the GPU context and discard Impeller pipeline caches. -In the legacy architecture, `PlatformViewAndroid` called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L200) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L232) directly as a `PlatformView` subclass. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, two new functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): +In the legacy architecture, `PlatformViewAndroid` called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L201) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L233) directly as a `PlatformView` subclass. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, two new functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): ```c /// Notify the engine that the render surface or native window was created. /// From 32ae0d5b356fe5727099ae6142de331d3f6aab95 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 17:53:17 +0000 Subject: [PATCH 14/43] Added struct_size and double pointers --- rfc/410.0000-flutter-android-embedder-c-api-migration.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 3ad44fa..f7e9bd5 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -582,6 +582,8 @@ typedef enum { } FlutterPathVerb; typedef struct { + /// The size of this struct. Must be sizeof(FlutterPathSegment). + size_t struct_size; FlutterPathVerb verb; /// Interpretation depends on verb; at most three points are used. FlutterPoint points[3]; @@ -593,8 +595,9 @@ typedef struct { size_t struct_size; FlutterPathFillType fill_type; size_t segments_count; + /// Array of segment pointers. Has length `segments_count`. /// Valid only for the duration of the frame presentation callback. - const FlutterPathSegment* segments; + const FlutterPathSegment** segments; } FlutterPath; ``` @@ -1152,8 +1155,8 @@ The following fields are appended to [`FlutterWindowMetricsEvent`](https://githu /// Number of display features in the `display_features` array. size_t display_features_count; - /// Array of physical display features (foldable hinges, cutouts). - const FlutterDisplayFeature* display_features; + /// Array of pointers to physical display features (foldable hinges, cutouts). + const FlutterDisplayFeature** display_features; ``` #### System Channels: Locale and Non-Linear Font Scaling From 1297b209580b7e11c6970cfabbe482a1df9a2c8e Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 18:07:09 +0000 Subject: [PATCH 15/43] Updated FlutterProjectArgs with callbacks and fixed default initialization mischaracterization. --- ...lutter-android-embedder-c-api-migration.md | 72 +++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index f7e9bd5..793ea0c 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -756,7 +756,8 @@ static void LoadDartDeferredLibrary(JNIEnv* env, /// This callback runs on the platform task runner thread. /// /// @param[in] loading_unit_id The ID of the loading unit to load. -/// @param[in] user_data The user data provided in `FlutterProjectArgs`. +/// @param[in] user_data The user data supplied to `FlutterEngineRun` or +/// `FlutterEngineInitialize`. typedef void (*FlutterRequestDartDeferredLibraryCallback)( intptr_t loading_unit_id, void* user_data); @@ -812,6 +813,19 @@ FlutterEngineResult FlutterEngineNotifyDartDeferredLibraryLoadError( const FlutterDartDeferredLibraryLoadError* error); ``` +In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841): + +```c + /// The callback invoked by the engine when Dart requests a deferred library + /// (loading unit). The callback is invoked from a task posted to the + /// platform thread. The embedder answers each request with + /// `FlutterEngineLoadDartDeferredLibrary` or + /// `FlutterEngineNotifyDartDeferredLibraryLoadError`. If null, the engine + /// fails every request with a non-transient load error. + FlutterRequestDartDeferredLibraryCallback + request_dart_deferred_library_callback; +``` + #### Deferred Library Memory Lifetime Management Because split APK assets on Android are loaded via `AAssetManager` or memory-mapped (`mmap`) from disk, buffer lifetime must be explicitly managed: @@ -875,6 +889,15 @@ FlutterEngineResult FlutterEngineUpdateAssetResolverByType( const FlutterAssetResolverRegistrationInfo* info); ``` +In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841): + +```c + /// An embedder-provided resolver that the engine queries for assets, + /// including `kernel_blob.bin` in JIT mode. When set, `assets_path` may be + /// null. If null, the engine loads assets from `assets_path`. + const FlutterCustomAssetResolver* custom_asset_resolver; +``` + A `FlutterCustomAssetResolver` passes into `FlutterProjectArgs.custom_asset_resolver` (or registers dynamically via `FlutterEngineRegisterAssetResolver`) so that the engine streams assets directly from the APK. During hot restart or dynamic feature installation, the engine calls [`UpdateResolverByType`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_manager.h#L67) via `FlutterEngineUpdateAssetResolverByType` to reload asset manifests and updated Dart bytecode bundles without restarting the native engine instance. ### Multi-Engine Spawning and Add-to-App (`FlutterEngineSpawn`) @@ -1037,39 +1060,32 @@ Returning `false` provides two critical architectural capabilities on mobile pla #### C-ABI Specification -To reconcile this discrepancy without breaking existing desktop embedders, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces an explicit configuration field and an extended callback: +To reconcile this discrepancy without breaking existing desktop embedders, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) appends an opt-in field to [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) and updates the threading documentation of the existing `platform_message_callback`: ```c -/// Callback invoked when the engine delivers a platform message from Dart. -/// When does_handle_platform_messages_on_platform_thread is false, this -/// callback may be invoked from any thread (typically the engine UI thread). -/// Embedders are responsible for dispatching to the appropriate task runner -/// or background worker pool. -typedef void (*FlutterPlatformMessageCallback2)( - const FlutterPlatformMessage* message, - void* user_data); -``` - -In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480): - -```c - /// When true (or zero-initialized default for legacy embedders), the engine - /// routes all incoming platform messages to the platform task runner before - /// invoking platform_message_callback. - /// - /// When false, the engine invokes platform_message_callback2 (or - /// platform_message_callback) directly on the originating thread - /// (typically the UI task runner) so the embedder can route - /// directly to background worker queues without platform thread hopping. - bool does_handle_platform_messages_on_platform_thread; + /// The callback invoked by the engine in order to give the embedder the + /// chance to respond to platform messages from the Dart application. + /// The callback will be invoked on the thread on which the `FlutterEngineRun` + /// call is made, unless `enable_direct_platform_message_dispatch` is true. + /// The second parameter, `user_data`, is supplied when `FlutterEngineRun` or + /// `FlutterEngineInitialize` is called. + FlutterPlatformMessageCallback platform_message_callback; + + // ... existing members ... + + /// If true, the engine invokes `platform_message_callback` directly on the + /// thread that sent the message (typically the UI thread) instead of posting + /// a task to the platform thread. Defaults to false, which preserves the + /// existing platform thread delivery. + bool enable_direct_platform_message_dispatch; ``` #### Android and iOS Cross-Platform Parity This configuration field satisfies both mobile embedder targets: -- **Zero-Initialization Safety**: Existing desktop embedders (Linux, macOS, Windows) zero-initialize `FlutterProjectArgs`, which defaults the field to `true` and preserves their single-threaded platform message assumptions. -- **Mobile Embedder Parity**: Both Android and iOS explicitly set `does_handle_platform_messages_on_platform_thread = false`. On iOS, `PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()` returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`FlutterPlatformMessageCallback2`](#platform-channel-messaging-background-handlers-and-task-queues) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): +- **Zero-Initialization Safety**: Existing desktop embedders (Linux, macOS, Windows) zero-initialize `FlutterProjectArgs`, which defaults the field to `false` and preserves their single-threaded platform message assumptions. +- **Mobile Embedder Parity**: Both Android and iOS explicitly set `enable_direct_platform_message_dispatch = true`. On iOS, `PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()` returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`platform_message_callback`](#platform-channel-messaging-background-handlers-and-task-queues) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): - If registered with a `BinaryMessenger.TaskQueue`, the task is dispatched directly to that queue's background executor pool. - If registered on the default queue, the task posts to the Android platform [`Looper`](https://developer.android.com/reference/android/os/Looper). - Responses submitted via [`FlutterEngineSendPlatformMessageResponse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3133) execute safely from any thread without main-thread hops. @@ -1324,12 +1340,12 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.does_handle_platform_messages_on_platform_thread`
- [`FlutterPlatformMessageCallback2`](#platform-channel-messaging-background-handlers-and-task-queues)
- `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`)
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation` and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) and [`FlutterVulkanExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | -| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolverByType` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | +| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolver`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolverByType` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | | **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | ## Phased Implementation Strategy From 60e35c49acc66425f4a3946a7b5d6fcb4e1d7240 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 18:15:45 +0000 Subject: [PATCH 16/43] Multiple asset providers and destruction_callback --- ...lutter-android-embedder-c-api-migration.md | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 793ea0c..4d4855a 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -868,14 +868,18 @@ typedef bool (*FlutterAssetResolverGetAsMappingCallback)( typedef struct { size_t struct_size; + /// Embedder-assigned identifier, unique within an engine instance. + /// `FlutterEngineUpdateAssetResolver` uses it to select the resolver to + /// replace. + int64_t identifier; void* user_data; FlutterAssetResolverGetAsMappingCallback get_as_mapping_callback; - VoidCallback release_callback; + VoidCallback destruction_callback; } FlutterCustomAssetResolver; typedef struct { size_t struct_size; - FlutterCustomAssetResolver resolver; + const FlutterCustomAssetResolver* resolver; } FlutterAssetResolverRegistrationInfo; FLUTTER_EXPORT @@ -884,7 +888,7 @@ FlutterEngineResult FlutterEngineRegisterAssetResolver( const FlutterAssetResolverRegistrationInfo* info); FLUTTER_EXPORT -FlutterEngineResult FlutterEngineUpdateAssetResolverByType( +FlutterEngineResult FlutterEngineUpdateAssetResolver( FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterAssetResolverRegistrationInfo* info); ``` @@ -892,13 +896,16 @@ FlutterEngineResult FlutterEngineUpdateAssetResolverByType( In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841): ```c - /// An embedder-provided resolver that the engine queries for assets, - /// including `kernel_blob.bin` in JIT mode. When set, `assets_path` may be - /// null. If null, the engine loads assets from `assets_path`. - const FlutterCustomAssetResolver* custom_asset_resolver; + /// The number of entries in `custom_asset_resolvers`. + size_t custom_asset_resolvers_count; + + /// Embedder-provided resolvers that the engine queries in array order for + /// assets, including `kernel_blob.bin` in JIT mode. When + /// `custom_asset_resolvers_count` is non-zero, `assets_path` may be null. + const FlutterCustomAssetResolver** custom_asset_resolvers; ``` -A `FlutterCustomAssetResolver` passes into `FlutterProjectArgs.custom_asset_resolver` (or registers dynamically via `FlutterEngineRegisterAssetResolver`) so that the engine streams assets directly from the APK. During hot restart or dynamic feature installation, the engine calls [`UpdateResolverByType`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_manager.h#L67) via `FlutterEngineUpdateAssetResolverByType` to reload asset manifests and updated Dart bytecode bundles without restarting the native engine instance. +`FlutterCustomAssetResolver` instances pass into `FlutterProjectArgs.custom_asset_resolvers` (or register dynamically via `FlutterEngineRegisterAssetResolver`) so that the engine streams assets directly from the APK. During hot restart or dynamic feature installation, `FlutterEngineUpdateAssetResolver` replaces the resolver with the matching `identifier` to reload asset manifests and updated Dart bytecode bundles without restarting the native engine instance. ### Multi-Engine Spawning and Add-to-App (`FlutterEngineSpawn`) @@ -1345,7 +1352,7 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`)
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation` and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) and [`FlutterVulkanExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | -| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolver`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolverByType` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | +| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolver` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | | **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | ## Phased Implementation Strategy From d8274478af87eabfa7ab64e35805360426bc682b Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 20:20:39 +0000 Subject: [PATCH 17/43] added is_root_layer --- ...lutter-android-embedder-c-api-migration.md | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 4d4855a..5fc0919 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -440,7 +440,31 @@ At the C-API boundary (`embedder.h`), the four composition modes partition into - **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L652) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanExternalTextureFrameCallback`, `FlutterHardwareBufferExternalTextureFrameCallback`, and `FlutterOpenGLTexture`) without creating `FlutterPlatformView` layers. - **Hybrid Composition (HC) and Hybrid Composition++ (HCPP) via `FlutterCompositor`**: The engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2117) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). `AndroidCompositor` and `AndroidPlatformViewsController` drive both HC and HCPP over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: -1. **Backing Store Allocation (`create_backing_store_callback`)**: For the primary Flutter layer (`platform_views_count == 0`), `AndroidCompositor::CreateBackingStore` returns the main surface framebuffer (`GetFBO()`). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above the platform view, subsequent `CreateBackingStore` calls within the same frame allocate offscreen framebuffers (`AcquireOffscreenFBO`). +1. **Backing Store Allocation (`create_backing_store_callback`)**: `FlutterBackingStoreConfig` is extended with `bool is_root_layer;` (and `FlutterCompositor.avoid_backing_store_cache` is set to `true` because the engine's render-target cache keys only on size). When `config->is_root_layer` is `true`—meaning the layer is the bottom-most layer of the frame for `view_id` with no platform view composited beneath it—`AndroidCompositor::CreateBackingStore` returns the main surface render target (`GetFBO()` on OpenGL ES, or the primary swapchain image on Vulkan). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above a platform view (`is_root_layer == false`), `CreateBackingStore` allocates an offscreen backing store (`AcquireOffscreenFBO` / overlay `AHardwareBuffer`): + + ```c + typedef struct { + /// The size of this struct. Must be sizeof(FlutterBackingStoreConfig). + size_t struct_size; + /// The size of the render target the engine expects to render into. + FlutterSize size; + /// The identifier for the view that the engine will use this backing store to + /// render into. + FlutterViewId view_id; + /// True if this backing store is for the bottom-most layer of the frame for + /// `view_id`, with no platform view composited beneath it. The engine + /// requests at most one such backing store per view per frame, and none when + /// all Flutter contents are composited above platform views. False for all + /// other layers. Read this field only if `struct_size` covers it, and treat + /// it as false otherwise. + /// + /// An embedder may return the view's on-screen render target (for example, + /// the next swapchain image or default framebuffer) when this is true. The + /// engine's backing store cache ignores this field, so such an embedder must + /// set `FlutterCompositor.avoid_backing_store_cache` to true. + bool is_root_layer; + } FlutterBackingStoreConfig; + ``` 2. **Layer and Overlay Presentation (`present_layers_callback` / `present_view_callback`)**: `AndroidCompositor::PresentLayers` begins the platform view frame (`OnBeginFrame`), iterates through the ordered `FlutterLayer` array, and dispatches each layer by type: - The base `kFlutterLayerContentTypeBackingStore` is presented to the primary `ANativeWindow`. - Each `kFlutterLayerContentTypePlatformView` layer forwards the view identifier, physical bounds (`offset`, `size`), and serialized `FlutterPlatformViewMutation` stack to `AndroidPlatformViewsController::OnDisplayPlatformView` (`PlatformViewsController.onDisplayPlatformView` in Java). @@ -1349,7 +1373,7 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | :--- | :--- | :--- | :--- | | **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | -| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`)
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation` and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | +| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation` and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) and [`FlutterVulkanExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | | **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolver` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | From a49bb897b9ecd7f761d1357a370e7a3f5d23b766 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 20:28:18 +0000 Subject: [PATCH 18/43] Update platform view flow --- ...lutter-android-embedder-c-api-migration.md | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 5fc0919..9a5c9ce 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -473,24 +473,25 @@ At the C-API boundary (`embedder.h`), the four composition modes partition into #### Frame Synchronization on API 21–33 Without `fml::RasterThreadMerger` -On Android 5.0 through Android 13 (API levels 21–33), NDK [`ASurfaceTransaction_setBuffer`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_setbuffer) and hardware sync fence handoff (HCPP) are unavailable. Two runtime paths can still prevent a platform view from using Texture Layer Hybrid Composition (TLHC) on these API levels: +On Android 5.0 through Android 13 (API levels 21–33), HCPP is unavailable because joining the Android View system's `SurfaceControl.Transaction` via NDK [`ASurfaceTransaction_fromJava`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_fromjava) requires API 34 (even though [`ASurfaceTransaction_setBuffer`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_setbuffer) itself was added in API 29). Two runtime paths can still prevent a platform view from using Texture Layer Hybrid Composition (TLHC) on these API levels: 1. **Explicit Hybrid Composition (`initExpensiveAndroidView`)**: The plugin requests `createForPlatformViewLayer`, routing directly to [`PlatformViewsController.configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L583). 2. **Embedded Child `SurfaceView` or `TextureView` (`VIEW_TYPES_REQUIRE_NON_TLHC`)**: When a plugin uses `initAndroidView` (`createForTextureLayer`) and the embedded Android `View` hierarchy contains a `SurfaceView` or `TextureView` child whose out-of-band buffer queue bypasses `ViewParent.onDescendantInvalidated()`, [`PlatformViewsController.createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L229-L246) detects `!supportsTextureLayerMode` and branches by `request.displayMode`: - - **`TEXTURE_WITH_VIRTUAL_FALLBACK` $\rightarrow$ Virtual Display (VD)**: Calls `configureForVirtualDisplay`, hosting the view and its child `SurfaceView` inside an offscreen `VirtualDisplay` backed by a `TextureRegistry` render target. Frame synchronization is handled entirely on the GPU/raster thread as a standard C-API external texture (`FlutterEngineRegisterExternalTexture`). - - **`TEXTURE_WITH_HYBRID_FALLBACK` $\rightarrow$ Hybrid Composition (HC)**: Calls `configureForHybridComposition` and returns `NON_TEXTURE_FALLBACK` (`-2`), instructing the Dart framework to composite the view via a `PlatformViewLayer` (`kFlutterLayerContentTypePlatformView`). + - **`TEXTURE_WITH_VIRTUAL_FALLBACK` (Virtual Display / VD)**: Calls `configureForVirtualDisplay`, hosting the view and its child `SurfaceView` inside an offscreen `VirtualDisplay` backed by a `TextureRegistry` render target. Frame synchronization is handled entirely on the GPU/raster thread as a standard C-API external texture (`FlutterEngineRegisterExternalTexture`). + - **`TEXTURE_WITH_HYBRID_FALLBACK` (Hybrid Composition / HC)**: Calls `configureForHybridComposition` and returns `NON_TEXTURE_FALLBACK` (`-2`), instructing the Dart framework to composite the view via a `PlatformViewLayer` (`kFlutterLayerContentTypePlatformView`). -When Hybrid Composition is active on API 21–33 without `fml::RasterThreadMerger`, frame synchronization between the Flutter rasterizer and the Android `View` hierarchy is enforced through a three-part lockstep protocol across `AndroidCompositor` and `PlatformViewsController`: +When Hybrid Composition is active on API 21–33 without `fml::RasterThreadMerger`, frame synchronization between the Flutter rasterizer and the Android `View` hierarchy is enforced through the following three mechanisms across `AndroidCompositor` and `PlatformViewsController`: 1. **`FlutterImageView` (`ImageReader`) Target Conversion**: Rendering the base Flutter layer into a `FlutterSurfaceView` (`SurfaceView`) during HC would present to `SurfaceFlinger` out of band from the Android `ViewRootImpl` UI traversal, causing visual lag against the `FlutterMutatorView`. When the first HC platform view is displayed (`onDisplayPlatformView`), `PlatformViewsController.initializeRootImageViewIfNeeded()` calls `flutterView.convertToImageView()`, switching the engine's primary `ANativeWindow` to an [`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)-backed `FlutterImageView`. Each overlay layer (`PlatformOverlayView`) is likewise backed by its own `ImageReader` `ANativeWindow`. -2. **Raster-Before-Acquire Ordering and Synchronous Platform-Thread Latch**: - In the legacy architecture, `AndroidExternalViewEmbedder` merged the raster thread onto the platform thread via `fml::RasterThreadMerger` only because it called JNI view hierarchy mutations inline during `SubmitFlutterView`. In `AndroidCompositor::PresentLayers`: - - On the raster thread, `surface_manager_->Present()` and `surface_manager_->BlitAndSwapOverlaySurface(...)` first complete `eglSwapBuffers` / `vkQueuePresentKHR` into the root `FlutterImageView` and `PlatformOverlayView` `ImageReader` `ANativeWindow` queues for Frame $N$. - - `AndroidCompositor` packages Frame $N$'s platform view geometry (`offset`, `size`), value-copied `AndroidMutatorsStack`, and overlay rectangles into a single atomic platform-thread frame transaction (`onBeginFrame` $\rightarrow$ `onDisplayPlatformView` $\rightarrow$ `onDisplayOverlaySurface` $\rightarrow$ `onEndFrame`). - - When `platform_views_count > 0` and HCPP is not active (`!IsHcppEnabled()`), if `PresentLayers` is not already running on the platform thread (`!platform_runner->RunsTasksOnCurrentThread()`), `PresentLayers` synchronizes on a frame-completion latch (`fml::AutoResetWaitableEvent`) until the platform thread finishes `onEndFrame()` (with a teardown guard to prevent ANRs if the surface detaches concurrently). Waiting on this latch prevents the raster thread from queuing Frame $N+1$ into the `ImageReader` before the platform thread has consumed Frame $N$, guaranteeing that `ImageReader.acquireLatestImage()` pairs Frame $N$'s rasterized pixels 1:1 with Frame $N$'s `FlutterMutatorView` scroll offset and mutator matrix. -3. **Atomic `ImageReader` Gate in `PlatformViewsController.onEndFrame()`**: - During the platform-thread commit in [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391), `flutterView.acquireLatestImageViewFrame()` and `overlayView.acquireLatestImage()` pull the newly queued GPU buffers and invalidate the `FlutterImageView` instances. `finishFrame()` checks `isFrameRenderedUsingImageReaders` (verifying that the converted root `FlutterImageView` and every active `PlatformOverlayView` acquired a valid image) before setting `parentView.setVisibility(View.VISIBLE)`. Consequently, the base `FlutterImageView`, the `FlutterMutatorView` (wrapping the native `View`), and all `PlatformOverlayView` layers are drawn together in a single `ViewRootImpl.performTraversals()` pass on the Android UI thread. When the last HC view is disposed (`currentFrameUsedPlatformViewIds.isEmpty()`), `onEndFrame()` invokes `flutterView.revertImageView()` to return to direct `SurfaceView` presentation. +2. **One Platform-Thread Task per Frame and Raster-Thread Wait for `onEndFrame()`**: + In the legacy architecture, `AndroidExternalViewEmbedder` merged the raster thread onto the platform thread via `fml::RasterThreadMerger` so that `onBeginFrame`, base/overlay rendering and swaps in `SubmitFlutterView`, inline `onDisplayPlatformView` and `onDisplayOverlaySurface` calls, and `onEndFrame` all executed inside one platform-thread task. `AndroidCompositor::PresentLayers` preserves that ordering without `fml::RasterThreadMerger`: + - On the raster thread, `surface_manager_->Present()` and `surface_manager_->BlitAndSwapOverlaySurface(...)` first complete `eglSwapBuffers` / `vkQueuePresentKHR` into the root `FlutterImageView` and `PlatformOverlayView` `ImageReader` `ANativeWindow` queues for frame N. + - `AndroidCompositor` captures frame N's platform view geometry (`offset`, `size`), value-copied `AndroidMutatorsStack`, and overlay rectangles by value and posts a single task (`PostTask`) to the platform thread that executes `onBeginFrame`, every `onDisplayPlatformView`, every `onDisplayOverlaySurface`, and `onEndFrame` in order. Because the Android UI `Looper` runs one message at a time and `ViewRootImpl` schedules view traversals in a separate `Choreographer.CALLBACK_TRAVERSAL` callback, no Android view traversal can run mid-task and observe a partially applied frame (the same single-`PostTask` pattern used by [`AndroidExternalViewEmbedder2::SubmitFlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc#L190-L229)). This is purely a CPU-side `Looper` ordering mechanism; it does not use `synchronization_fence_fd`, which exists only on the HCPP `SurfaceControl.Transaction` path. + - When `platform_views_count > 0` and HCPP is not active (`!IsHcppEnabled()`), if `PresentLayers` was called on the raster thread (`!platform_runner->RunsTasksOnCurrentThread()`), the raster thread waits on a frame-completion latch (`fml::AutoResetWaitableEvent`) until the platform thread finishes `onEndFrame()` (polling a surface-attached flag that the platform thread clears before calling `FlutterEngineNotifyDestroyed` so teardown never deadlocks). Waiting on this latch is required because `onEndFrame()` calls `ImageReader.acquireLatestImage()`, which drops older queued images; without the wait, the raster thread could queue frame N+1 before the platform thread runs frame N's task, pairing frame N+1's pixels with frame N's `FlutterMutatorView` geometry. + - Two mid-frame steps still perform a synchronous hop to the platform thread: allocating a new overlay surface (`createOverlaySurface`), and the first frame that displays an HC view (`initializeRootImageViewIfNeeded` calling `convertToImageView`), where the root layer is blitted from an offscreen backing store into the newly created `FlutterImageView` `ImageReader` surface. +3. **`ImageReader` Gate in `PlatformViewsController.onEndFrame()`**: + During [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391) on the platform thread, `flutterView.acquireLatestImageViewFrame()` and `overlayView.acquireLatestImage()` pull the newly queued GPU buffers and invalidate the `FlutterImageView` instances. `finishFrame()` checks `isFrameRenderedUsingImageReaders` (verifying that the converted root `FlutterImageView` and every active `PlatformOverlayView` acquired a valid image) before setting `parentView.setVisibility(View.VISIBLE)`. Consequently, the base `FlutterImageView`, the `FlutterMutatorView` (wrapping the native `View`), and all `PlatformOverlayView` layers are drawn together in a single `ViewRootImpl.performTraversals()` pass on the Android UI thread. When the last HC view is disposed (`currentFrameUsedPlatformViewIds.isEmpty()`), `onEndFrame()` invokes `flutterView.revertImageView()` to return to direct `SurfaceView` presentation. ### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State From 9280c6e01e85960f1f66552191761b007553ad3b Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 20:32:39 +0000 Subject: [PATCH 19/43] Add notes about backdrop filters. --- rfc/410.0000-flutter-android-embedder-c-api-migration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 9a5c9ce..7c9466c 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -642,6 +642,8 @@ typedef struct { The array is an array of pointers rather than an array of values. Indexing strides by pointer width instead of `sizeof(FlutterPlatformViewMutation)`, so a future mutation type that widens the union does not shift the stride out from under an already-compiled embedder. This is the same technique [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) uses. +Android does not apply backdrop filter mutators over HC or HCPP platform views today: neither `AndroidExternalViewEmbedder` nor `AndroidExternalViewEmbedder2` overrides the no-op [`ExternalViewEmbedder::PushFilterToVisitedPlatformViews`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L538-L558), and both JNI serializers skip `kBackdropFilter` and the backdrop clip mutators ([HC](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1840-L1849), [HCPP](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L2355-L2362)). TLHC and VD render the platform view as an external texture into Flutter's own canvas ([`TextureLayer::Paint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/texture_layer.cc#L48-L67)), so backdrop filters already work in those modes without platform view mutators. The Android embedder skips `FlutterPlatformViewMutationType` values it does not handle, and any future backdrop-filter support over HC or HCPP views will consume the backdrop-blur mutation added by the iOS embedder migration RFC rather than defining an Android-specific mutation type. + > [!NOTE] Mutator Simplification: Dropping Complex Path Clipping > If legacy Hybrid Composition under Skia is dropped, or if platform view clipping is restricted to standard geometric bounds, `kFlutterPlatformViewMutationTypeClipPath` and the whole `FlutterPath` family become dead weight. Mutations would then convey only affine transformations, bounding rectangles, and corner radii, all of which map directly to Android `SurfaceControl` or `View` properties. From 29e6df7356ad9853d647286ed0c93c8e23c7dd6d Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 20:43:35 +0000 Subject: [PATCH 20/43] Updates to the clip paths. --- ...lutter-android-embedder-c-api-migration.md | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 7c9466c..7a00f29 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -572,7 +572,6 @@ typedef enum { kFlutterPlatformViewMutationTypeClipRect, kFlutterPlatformViewMutationTypeClipRoundedRect, kFlutterPlatformViewMutationTypeTransformation, - kFlutterPlatformViewMutationTypeClipRoundSuperellipse, kFlutterPlatformViewMutationTypeClipPath, } FlutterPlatformViewMutationType; @@ -583,13 +582,14 @@ typedef struct { FlutterRect clip_rect; FlutterRoundedRect clip_rounded_rect; FlutterTransformation transformation; - FlutterRoundSuperellipse clip_round_superellipse; - FlutterPath clip_path; + const FlutterPath* clip_path; }; } FlutterPlatformViewMutation; ``` -Arbitrary clips need a path representation that does not drag [`SkPath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkPath.h) across the boundary. In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), [`FlutterPath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L757) is a flat segment array with an explicit fill rule: +Rounded-superellipse clips (`MutatorType::kClipRSE`) continue to be approximated as `kFlutterPlatformViewMutationTypeClipRoundedRect` by the engine ([`embedder_layers.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_layers.cc#L162-L167)), matching what Android HC and HCPP do today (`DlRoundSuperellipse::ToApproximateRoundRect()`). + +Arbitrary clips need a path representation that does not drag [`SkPath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkPath.h) across the boundary. In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `FlutterPath` uses a structure-of-arrays layout with an explicit fill rule and conservative control-point bounds: ```c typedef enum { @@ -607,25 +607,51 @@ typedef enum { } FlutterPathVerb; typedef struct { - /// The size of this struct. Must be sizeof(FlutterPathSegment). - size_t struct_size; - FlutterPathVerb verb; - /// Interpretation depends on verb; at most three points are used. - FlutterPoint points[3]; - /// Weight for the rational quadratic when verb is kFlutterPathVerbConic. - double conic_weight; -} FlutterPathSegment; - -typedef struct { + /// The size of this struct. Must be sizeof(FlutterPath). size_t struct_size; + /// The fill rule for this path. FlutterPathFillType fill_type; - size_t segments_count; - /// Array of segment pointers. Has length `segments_count`. - /// Valid only for the duration of the frame presentation callback. - const FlutterPathSegment** segments; + /// The number of entries in `verbs`. + size_t verbs_count; + /// The path verbs, in drawing order. Every contour begins with + /// `kFlutterPathVerbMove`. Valid only for the duration of the frame + /// presentation callback. + const FlutterPathVerb* verbs; + /// The number of entries in `points`. Equals the sum of the points used by + /// each verb: move 1, line 1, quad 2, conic 2, cubic 3, close 0. + size_t points_count; + /// The points, consumed in verb order. Valid only for the duration of the + /// frame presentation callback. + const FlutterPoint* points; + /// The number of entries in `conic_weights`. Equals the number of + /// `kFlutterPathVerbConic` verbs. + size_t conic_weights_count; + /// The conic weights, consumed in verb order. Valid only for the duration of + /// the frame presentation callback. + const double* conic_weights; + /// Conservative control-point bounds containing all points of the path, in + /// the same coordinate space. An embedder that cannot clip to a path clips + /// to this rectangle. + FlutterRect bounds; } FlutterPath; + +/// Optional platform view mutation features that a compositor can consume. +/// +/// The engine only emits a mutation type or path verb that is gated by one of +/// these bits when the compositor sets that bit in +/// `FlutterCompositor.platform_view_mutation_capabilities`. +typedef enum { + /// Only the default mutation types and path verbs are emitted. + kFlutterPlatformViewMutationCapabilityDefault = 0, + /// Clip paths may contain `kFlutterPathVerbConic` verbs. Without this bit, + /// the engine converts conics to quadratic Bezier curves before handing + /// paths to the embedder. + kFlutterPlatformViewMutationCapabilityPathConics = 1 << 0, +} FlutterPlatformViewMutationCapability; ``` +`FlutterCompositor` is extended with `uint64_t platform_view_mutation_capabilities;` (a bitmask of `FlutterPlatformViewMutationCapability` values, defaulting to `0` when omitted by older embedders). On Android API 34+, where `android.graphics.Path.conicTo` is available and HCPP applies path clips to `FlutterMutatorView`, the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` so conic arcs are passed through without quadratic approximation. + Mutations ride on [`FlutterPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1525), which the engine hands to the embedder inside a [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L663) through the compositor's [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1494): ```c @@ -644,9 +670,6 @@ The array is an array of pointers rather than an array of values. Indexing strid Android does not apply backdrop filter mutators over HC or HCPP platform views today: neither `AndroidExternalViewEmbedder` nor `AndroidExternalViewEmbedder2` overrides the no-op [`ExternalViewEmbedder::PushFilterToVisitedPlatformViews`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L538-L558), and both JNI serializers skip `kBackdropFilter` and the backdrop clip mutators ([HC](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1840-L1849), [HCPP](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L2355-L2362)). TLHC and VD render the platform view as an external texture into Flutter's own canvas ([`TextureLayer::Paint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/texture_layer.cc#L48-L67)), so backdrop filters already work in those modes without platform view mutators. The Android embedder skips `FlutterPlatformViewMutationType` values it does not handle, and any future backdrop-filter support over HC or HCPP views will consume the backdrop-blur mutation added by the iOS embedder migration RFC rather than defining an Android-specific mutation type. -> [!NOTE] Mutator Simplification: Dropping Complex Path Clipping -> If legacy Hybrid Composition under Skia is dropped, or if platform view clipping is restricted to standard geometric bounds, `kFlutterPlatformViewMutationTypeClipPath` and the whole `FlutterPath` family become dead weight. Mutations would then convey only affine transformations, bounding rectangles, and corner radii, all of which map directly to Android `SurfaceControl` or `View` properties. - ### External Textures (Zero-Copy Vulkan and OpenGL ES) Android provides two distinct mechanisms for external textures: @@ -1376,7 +1399,7 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | :--- | :--- | :--- | :--- | | **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | -| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation` and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | +| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) and [`FlutterVulkanExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | | **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolver` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | From add7de6c361594d51d080538ee9a239ce1e8aded Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 21:19:53 +0000 Subject: [PATCH 21/43] Remove AHardareBuffer from the embedder api. --- ...lutter-android-embedder-c-api-migration.md | 150 +++++++++++------- 1 file changed, 89 insertions(+), 61 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 7a00f29..4a63174 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -437,7 +437,7 @@ PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( At the C-API boundary (`embedder.h`), the four composition modes partition into two external-texture modes and two compositor-layer modes: -- **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L652) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanExternalTextureFrameCallback`, `FlutterHardwareBufferExternalTextureFrameCallback`, and `FlutterOpenGLTexture`) without creating `FlutterPlatformView` layers. +- **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L652) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanTextureFrameCallback`, and `FlutterOpenGLTextureFrameCallback`) without creating `FlutterPlatformView` layers. - **Hybrid Composition (HC) and Hybrid Composition++ (HCPP) via `FlutterCompositor`**: The engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2117) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). `AndroidCompositor` and `AndroidPlatformViewsController` drive both HC and HCPP over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: 1. **Backing Store Allocation (`create_backing_store_callback`)**: `FlutterBackingStoreConfig` is extended with `bool is_root_layer;` (and `FlutterCompositor.avoid_backing_store_cache` is set to `true` because the engine's render-target cache keys only on size). When `config->is_root_layer` is `true`—meaning the layer is the bottom-most layer of the frame for `view_id` with no platform view composited beneath it—`AndroidCompositor::CreateBackingStore` returns the main surface render target (`GetFBO()` on OpenGL ES, or the primary swapchain image on Vulkan). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above a platform view (`is_root_layer == false`), `CreateBackingStore` allocates an offscreen backing store (`AcquireOffscreenFBO` / overlay `AHardwareBuffer`): @@ -674,90 +674,118 @@ Android does not apply backdrop filter mutators over HC or HCPP platform views t Android provides two distinct mechanisms for external textures: -- [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline that imports [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles directly into Vulkan ([`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html)) or EGL ([`EGLImage`](https://registry.khronos.org/EGL/sdk/docs/man/html/eglCreateImage.xhtml)). -- [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture): A legacy OpenGL ES path where native frames render into a texture ID updated with [`SurfaceTexture.updateTexImage()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()). +- [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline backed by `ImageReader` and [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) on API 29+ (falling back to `SurfaceTexture` below API 29 and on Huawei devices at API 29). +- [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture): An OpenGL ES consumer path where native frames latch into a `GL_TEXTURE_EXTERNAL_OES` texture via [`SurfaceTexture.updateTexImage()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()). -The public Embedder API previously lacked a way to deliver Vulkan-backed external images without depending on the internal [`flutter::Texture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L27) class. The design closes this with a pull-based frame callback on the renderer config rather than a push-based registration entry point. The embedder claims a texture ID through the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) and signals new content with [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250); the engine calls back to collect the backing image for the frame it is about to compose: +Because the Android embedder owns the `VkDevice` on Vulkan and the `EGLContext` on OpenGL ES, it imports `AHardwareBuffer` frames into backend-native GPU handles itself (`VkImage` via `VK_ANDROID_external_memory_android_hardware_buffer` on Vulkan, and `EGLImage` / `GL_TEXTURE_EXTERNAL_OES` via `eglCreateImageKHR(EGL_NATIVE_BUFFER_ANDROID)` on OpenGL ES, mirroring [`ImageExternalTextureGL`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture_gl.cc#L93-L113)). This keeps `embedder.h` free of Android-specific `AHardwareBuffer` types. + +On Vulkan, [`flutter/flutter#188855`](https://github.com/flutter/flutter/pull/188855) added [`FlutterVulkanExternalTexture`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/shell/platform/embedder/embedder.h#L971-L1001), `FlutterVulkanTextureFrameCallback`, and `FlutterVulkanRendererConfig.external_texture_frame_callback`. However, camera and video decoder `AHardwareBuffer`s on Android frequently use vendor-specific external YCbCr formats (`VkAndroidHardwareBufferFormatPropertiesANDROID.format == VK_FORMAT_UNDEFINED`) that cannot be sampled without a `VkSamplerYcbcrConversion`. To support multi-planar and external-format `VkImage`s without leaking `AHardwareBuffer` into `embedder.h`, `FlutterVulkanExternalTexture` is extended with an optional `FlutterVulkanYcbcrConversionInfo` pointer: ```c -/// Specifies the backing type of a Vulkan external texture. +/// VkComponentSwizzle values for FlutterVulkanComponentMapping. typedef enum { - kFlutterVulkanExternalTextureTypeVkImage, - kFlutterVulkanExternalTextureTypeAHardwareBuffer, -} FlutterVulkanExternalTextureType; - + kFlutterVulkanComponentSwizzleIdentity = 0, + kFlutterVulkanComponentSwizzleZero = 1, + kFlutterVulkanComponentSwizzleOne = 2, + kFlutterVulkanComponentSwizzleR = 3, + kFlutterVulkanComponentSwizzleG = 4, + kFlutterVulkanComponentSwizzleB = 5, + kFlutterVulkanComponentSwizzleA = 6, +} FlutterVulkanComponentSwizzle; + +/// Equivalent to VkComponentMapping for YCbCr conversion. typedef struct { + FlutterVulkanComponentSwizzle r; + FlutterVulkanComponentSwizzle g; + FlutterVulkanComponentSwizzle b; + FlutterVulkanComponentSwizzle a; +} FlutterVulkanComponentMapping; + +/// Parameters for creating a VkSamplerYcbcrConversion when sampling an +/// external-format or YCbCr-encoded VkImage (mirrors +/// VkSamplerYcbcrConversionCreateInfo and VkExternalFormatANDROID). +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanYcbcrConversionInfo). size_t struct_size; - size_t width; - size_t height; - /// VkFormat. May be 0 for an AHardwareBuffer, in which case the engine - /// queries the format from the buffer itself. + /// The VkFormat of the image, or VK_FORMAT_UNDEFINED (0) when using + /// `external_format`. uint32_t format; - FlutterVulkanExternalTextureType type; - union { - FlutterVulkanImageHandle vk_image; - FlutterAHardwareBufferHandle hardware_buffer; - }; - /// VkImageLayout of the image. - uint32_t image_layout; - /// NULL for standard sampling; non-NULL for YCbCr camera and video buffers. - const FlutterVulkanYcbcrConversionInfo* ycbcr_conversion_info; - void* user_data; - VoidCallback destruction_callback; -} FlutterVulkanExternalTexture; - -/// Invoked on an engine-managed thread to obtain the texture for a frame. -typedef bool (*FlutterVulkanExternalTextureFrameCallback)( - void* user_data, - int64_t texture_identifier, - size_t width, - size_t height, - FlutterVulkanExternalTexture* texture_out); -``` + /// VkSamplerYcbcrModelConversion value. + uint32_t ycbcr_model; + /// VkSamplerYcbcrRange value. + uint32_t ycbcr_range; + /// Component swizzle mapping. + FlutterVulkanComponentMapping components; + /// VkChromaLocation for X chroma samples. + uint32_t x_chroma_offset; + /// VkChromaLocation for Y chroma samples. + uint32_t y_chroma_offset; + /// VkFilter for chroma reconstruction. + uint32_t chroma_filter; + /// Whether explicit chroma reconstruction is forced (VkBool32). + bool force_explicit_reconstruction; + /// VkFormatFeatureFlags for the external format. + uint32_t format_features; + /// Platform-defined external format identifier (for example from + /// VkAndroidHardwareBufferFormatPropertiesANDROID.externalFormat), or 0 + /// if `format` is a known Vulkan format. + uint64_t external_format; +} FlutterVulkanYcbcrConversionInfo; -`FlutterVulkanRendererConfig` carries this callback as `external_texture_frame_callback`. A parallel [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es) sits on both [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L695) and [`FlutterOpenGLRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L693), so an [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) reaches either backend without an Android-specific engine entry point. - -The discriminated union is what makes this workable on Android. [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) hands out [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) handles rather than [`VkImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html) handles, and `ycbcr_conversion_info` carries the external format identifier and component swizzle that camera and video decoder buffers require. Vendor buffers with an external format cannot be sampled correctly without it. - -For legacy [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) instances, the existing [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) API with `kFlutterExternalTextureTypeOpenGL` is used. However, the existing [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1121) struct only specifies `target`, `name`, `format`, `width`, and `height`; it lacks a UV coordinate transformation matrix. On Android, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 matrix accounting for camera sensor rotation, video decoder padding, and coordinate inversion. In the existing architecture, [`SurfaceTextureExternalTexture::Update()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.cc#L134-L143) queries this matrix through [`PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1626-L1645) and returns an [`SkM44`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkM44.h) matrix directly: - -```cpp -// Existing UV transformation querying in shell/platform/android/surface_texture_external_texture.cc -void SurfaceTextureExternalTexture::Update() { - jni_facade_->SurfaceTextureUpdateTexImage( - fml::jni::ScopedJavaLocalRef(surface_texture_)); - transform_ = jni_facade_->SurfaceTextureGetTransformMatrix( - fml::jni::ScopedJavaLocalRef(surface_texture_)); -} +typedef struct { + // ... existing members from flutter/flutter#188855 (struct_size, image, + // format, user_data, destruction_callback, width, height) ... -const SkM44& SurfaceTextureExternalTexture::GetCurrentUVTransformation() const { - return transform_; -} + /// Optional YCbCr sampler conversion info for multi-planar or + /// external-format images. NULL for standard RGBA sampling. + const FlutterVulkanYcbcrConversionInfo* ycbcr_conversion_info; +} FlutterVulkanExternalTexture; ``` -Without this matrix, external video and camera frames render inverted or distorted. +For [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture)-backed textures—used by the public `TextureRegistry.createSurfaceTexture()` / `registerSurfaceTexture()` APIs, by `SurfaceProducer` below API 29 (and on Huawei devices at API 29), and by TLHC/VD render targets below API 29—[`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 UV coordinate transformation matrix accounting for camera sensor rotation, video decoder padding, and vertical coordinate inversion (`ImageReader` buffers carry no matrix because `handlesCropAndRotation()` is `false`). Under Vulkan, `SurfaceTexture` requires an OpenGL ES context to latch frames, so the GL-to-Vulkan blit into an `AHardwareBuffer` ([`SurfaceTextureExternalTextureVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc#L152-L161)) runs inside the Android embedder with the UV matrix baked into the blit. Under OpenGL ES, however, the engine samples the texture directly and needs the UV matrix from the embedder. -To resolve this gap, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) introduces [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es): +Because [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L671-L691) has no `struct_size` and is embedded by value inside [`FlutterOpenGLBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1982-L1996) as a render target, it stays unchanged and undeprecated. Instead, `embedder.h` introduces `FlutterOpenGLExternalTexture` (mirroring `FlutterMetalExternalTexture` and `FlutterVulkanExternalTexture`) and appends `FlutterOpenGLTextureFrameCallback external_texture_frame_callback;` to `FlutterOpenGLRendererConfig` (marking `gl_external_texture_frame_callback` `@deprecated` in documentation and rejecting configs that set both): ```c typedef struct { + /// The size of this struct. Must be sizeof(FlutterOpenGLExternalTexture). size_t struct_size; + /// Target texture of the active texture unit (for example GL_TEXTURE_2D or + /// GL_TEXTURE_EXTERNAL_OES). uint32_t target; + /// The name of the texture. uint32_t name; + /// The texture format (for example GL_RGBA8). Use GL_RGBA8 when `target` + /// is GL_TEXTURE_EXTERNAL_OES. uint32_t format; + /// User data to be returned on the invocation of the destruction callback. void* user_data; + /// Callback invoked (on an engine-managed thread) that asks the embedder + /// to collect the texture. VoidCallback destruction_callback; - uint32_t width; - uint32_t height; + /// Width of the texture in pixels. + size_t width; + /// Height of the texture in pixels. + size_t height; + /// Transformation applied to normalized texture coordinates before + /// sampling, for example from SurfaceTexture.getTransformMatrix(). An + /// all-zero or identity matrix draws the texture untransformed. FlutterTransformation uv_transform; -} FlutterOpenGLTexture2; +} FlutterOpenGLExternalTexture; + +typedef bool (*FlutterOpenGLTextureFrameCallback)( + void* /* user data */, + int64_t /* texture identifier */, + size_t /* width */, + size_t /* height */, + FlutterOpenGLExternalTexture* /* texture out */); + +// Appended to FlutterOpenGLRendererConfig: +FlutterOpenGLTextureFrameCallback external_texture_frame_callback; ``` Under OpenGL ES, platform view compositing also faces constraints: because [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction sync fences depend on Vulkan hardware synchronization, OpenGL platform views must use Texture Layer Hybrid Composition (TLHC) or legacy Hybrid Composition rather than HCPP. -> [!NOTE] External Texture Simplification: Dropping Skia and Legacy OpenGL Textures -> Retiring legacy Skia and legacy OpenGL `SurfaceTexture` pathways in favor of Impeller Vulkan allows the embedder to eliminate legacy UV coordinate transform matrix conversions (`FlutterOpenGLTexture2`) and standardize directly on zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports through [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es). - ### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) In-Place Extension) @@ -1346,7 +1374,7 @@ In the completed target architecture, `AndroidShellHolder`, `PlatformViewAndroid - `JniRouter`, `JniDelegate`, and `JvmInvoker` (`jni_router.h`, `jni_delegate.h`, `jvm_invoker.h`): Decouple outbound C++-to-Java JNI invocations from live Android `JNIEnv*` state through injectable delegate and provider interfaces (`FlutterJniProvider`, `HardwareBufferProvider`, `SurfaceControlProvider`, `SurfaceTransactionProvider`, and `ChoreographerProvider`). This allows the entire C-API embedder (`flutter_embedder_native_unittests`) to be unit-tested on the host machine without an Android JVM or device. - `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, enforces synchronous surface destruction contracts before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns, and suspends GPU command submission during application backgrounding via `FlutterEngineSetGpuAvailability`. - `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+) and Texture Layer Hybrid Composition (TLHC), deserialize `FlutterPlatformViewMutation` arrays, capture per-`FlutterViewId` frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). -- `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` (`android_hardware_buffer.cc`, `android_vulkan_external_texture.cc`): Manage [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (OpenGL ES) with 4x4 UV transformation matrices (`FlutterOpenGLTexture2`) and zero-copy [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) ([`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) / Vulkan) texture callbacks (`FlutterVulkanExternalTextureFrameCallback` and `FlutterHardwareBufferExternalTextureFrameCallback`). +- `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` (`android_hardware_buffer.cc`, `android_vulkan_external_texture.cc`): Manage [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) and `EGLImageKHR` (OpenGL ES) with UV transformation matrices (`FlutterOpenGLExternalTexture` and `FlutterOpenGLTextureFrameCallback`) and zero-copy [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) ([`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imported into `VkImage` on Vulkan) texture callbacks (`FlutterVulkanExternalTexture`, `FlutterVulkanYcbcrConversionInfo`, and `FlutterVulkanTextureFrameCallback`). - `AndroidSemanticsMapper` and `AndroidWindowMetricsMapper` (`android_semantics_mapper.cc`, `android_window_metrics_mapper.cc`): Translate `FlutterSemanticsUpdate2` trees into JNI byte buffers for Java `AccessibilityBridge` and pack viewport metrics, insets, corner radii, and foldable display features into `FlutterWindowMetricsEvent`. - `AndroidVsyncWaiter` (`android_vsync_waiter.cc`): Bridges Android NDK [`AChoreographer`](https://developer.android.com/ndk/reference/group/choreographer) and Java [`Choreographer`](https://developer.android.com/reference/android/view/Choreographer) frame callbacks to `FlutterProjectArgs.vsync_callback` and forwards frame pacing timestamps via `FlutterEngineOnVsync`. - `AndroidVMInit` and `AndroidEngineGroup` (`android_vm_init.cc`, `android_engine_group.cc`): Coordinate initial Dart VM argument synthesis, Linux thread priority tuning (`thread_priority_setter`), and multi-engine group spawning via `FlutterEngineSpawn`. @@ -1400,7 +1428,7 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | -| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) and [`FlutterVulkanExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterHardwareBufferExternalTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLTexture2`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` Vulkan texture import and delivers 4x4 UV transformation matrices for `SurfaceTexture` camera and video streams. | +| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | | **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolver` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | | **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | From 7be704a92f7c58a03f47e0bc29a60b246d6f7686 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 21:28:53 +0000 Subject: [PATCH 22/43] Updated semantics, edge insets and scaled font callbacks. --- ...lutter-android-embedder-c-api-migration.md | 139 ++++++++++++------ 1 file changed, 96 insertions(+), 43 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 4a63174..af6617d 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -789,21 +789,64 @@ Under OpenGL ES, platform view compositing also faces constraints: because [`Sur ### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) In-Place Extension) -Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in the original [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) struct, including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `identifier`, `traversal_parent`, and `hit_test_transform`. +Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677), including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count` (`FlutterSemanticsNode2.child_count` is populated from `childrenInTraversalOrder.size()`, which can differ from `childrenInHitTestOrder.size()` when the framework grafts nodes such as `OverlayPortal`). -**Missing Android Accessibility Members**: The remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677): +**Missing Android Accessibility Members**: `FlutterSemanticsRole` is defined to mirror [`flutter::SemanticsRole`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/lib/ui/semantics/semantics_node.h#L107-L110), and the remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677): - ```c - // Appended to FlutterSemanticsNode2 in embedder.h: - int32_t max_value_length; - int32_t current_value_length; - const char* link_url; - const char* locale; - double min_value; - double max_value; - int32_t traversal_parent; - FlutterTransformation hit_test_transform; - ``` +```c +typedef enum { + kFlutterSemanticsRoleNone = 0, + kFlutterSemanticsRoleTab = 1, + kFlutterSemanticsRoleTabBar = 2, + kFlutterSemanticsRoleTabPanel = 3, + kFlutterSemanticsRoleDialog = 4, + kFlutterSemanticsRoleAlertDialog = 5, + kFlutterSemanticsRoleTable = 6, + kFlutterSemanticsRoleCell = 7, + kFlutterSemanticsRoleRow = 8, + kFlutterSemanticsRoleColumnHeader = 9, + kFlutterSemanticsRoleDragHandle = 10, + kFlutterSemanticsRoleSpinButton = 11, + kFlutterSemanticsRoleComboBox = 12, + kFlutterSemanticsRoleMenuBar = 13, + kFlutterSemanticsRoleMenu = 14, + kFlutterSemanticsRoleMenuItem = 15, + kFlutterSemanticsRoleMenuItemCheckbox = 16, + kFlutterSemanticsRoleMenuItemRadio = 17, + kFlutterSemanticsRoleList = 18, + kFlutterSemanticsRoleListItem = 19, + kFlutterSemanticsRoleForm = 20, + kFlutterSemanticsRoleTooltip = 21, + kFlutterSemanticsRoleLoadingSpinner = 22, + kFlutterSemanticsRoleProgressBar = 23, + kFlutterSemanticsRoleHotKey = 24, + kFlutterSemanticsRoleRadioGroup = 25, + kFlutterSemanticsRoleStatus = 26, + kFlutterSemanticsRoleAlert = 27, + kFlutterSemanticsRoleComplementary = 28, + kFlutterSemanticsRoleContentInfo = 29, + kFlutterSemanticsRoleMain = 30, + kFlutterSemanticsRoleNavigation = 31, + kFlutterSemanticsRoleRegion = 32, + kFlutterSemanticsRoleSearch = 33, + kFlutterSemanticsRoleSlider = 34, + kFlutterSemanticsRoleListBox = 35, + kFlutterSemanticsRoleOption = 36, +} FlutterSemanticsRole; + +// Appended to FlutterSemanticsNode2 in embedder.h: +int32_t max_value_length; +int32_t current_value_length; +const char* link_url; +const char* locale; +const char* min_value; +const char* max_value; +FlutterTransformation hit_test_transform; +FlutterSemanticsRole role; +/// The number of entries in `children_in_hit_test_order` (which may differ +/// from `child_count`, the length of `children_in_traversal_order`). +size_t hit_test_child_count; +``` ### Dart Deferred Library Loading @@ -1207,12 +1250,24 @@ Rather than creating a separate struct, `FlutterWindowMetricsEvent` is extended 3. Engine implementations use `SAFE_ACCESS(event, member, default_value)` to read new fields, which guarantees that existing desktop embedders passing the baseline struct size continue to work without modification. ```c +/// Distances from each edge of a rectangle, in physical pixels. +/// Frozen value struct (like `FlutterRect`) embedded by value. typedef struct { double left; double top; double right; double bottom; -} FlutterRectInsets; +} FlutterEdgeInsets; + +/// Radii of the four display corners, in physical pixels. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterDisplayCornerRadii). + size_t struct_size; + double top_left; + double top_right; + double bottom_right; + double bottom_left; +} FlutterDisplayCornerRadii; typedef enum { kFlutterDisplayFeatureTypeUnknown = 0, @@ -1239,19 +1294,20 @@ The following fields are appended to [`FlutterWindowMetricsEvent`](https://githu ```c /// Physical window padding (for example, status bar, navigation bar, cutouts). - FlutterRectInsets physical_padding; + FlutterEdgeInsets physical_padding; /// System gesture insets (for example, Android 10+ edge back gestures). - FlutterRectInsets physical_system_gesture_insets; + FlutterEdgeInsets physical_system_gesture_insets; - /// Physical touch slop threshold for scroll disambiguation. + /// Physical touch slop threshold in physical pixels for scroll + /// disambiguation. A non-positive value (<= 0.0, including zero-initialized + /// structs) indicates that the platform does not specify a custom touch slop + /// and the framework default should be used. double physical_touch_slop; - /// Display corner radii in physical pixels. - double display_corner_radius_top_left; - double display_corner_radius_top_right; - double display_corner_radius_bottom_right; - double display_corner_radius_bottom_left; + /// Display corner radii in physical pixels, or NULL when unknown or + /// unsupported (for example on Android < API 31 or desktop platforms). + const FlutterDisplayCornerRadii* display_corner_radii; /// Number of display features in the `display_features` array. size_t display_features_count; @@ -1260,32 +1316,29 @@ The following fields are appended to [`FlutterWindowMetricsEvent`](https://githu const FlutterDisplayFeature** display_features; ``` -#### System Channels: Locale and Non-Linear Font Scaling - -Features that communicate standard framework and platform configuration use platform message channels rather than custom additions to `embedder.h`: +#### System Channels and Non-Linear Font Scaling 1. **Locale Configuration**: The embedder uses the standard `flutter/localization` platform message channel ([`LocalizationChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java#L61-L97)). In the engine ([`embedder.cc:3423-3480`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3423-L3480)), [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3552) serializes locale data into a JSON message on this channel. No new C-API entry point is added for locale settings. -2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses non-linear font scaling curves so that large typography scales at lower ratios than body text, which prevents UI clipping. In the legacy architecture, this was serviced through a synchronous JNI query ([`PlatformDispatcher`](https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher-class.html).`_getScaledFontSize` -> [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h)), which does not exist in `PlatformViewEmbedder` and triggers `FML_UNREACHABLE()`. +2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses non-linear font scaling curves so that large typography scales at lower ratios than body text, preventing UI clipping. In `dart:ui`, [`PlatformDispatcher.scaleFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1553-L1612) queries integer font sizes via `@Native PlatformConfigurationNativeApi::GetScaledFontSize` ([`PlatformView::GetScaledFontSize(double unscaled_font_size, int configuration_id)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L118)), which `PlatformViewEmbedder` did not previously override. - To support non-linear scaling without synchronous engine queries or new C-APIs, the existing `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) is extended: + Following the existing pattern of [`compute_platform_resolved_locale_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L193-L203), `embedder.h` adds a synchronous UI-thread callback on `FlutterProjectArgs` that `PlatformViewEmbedder::GetScaledFontSize` forwards to: - - During startup and configuration changes, the embedder extracts curve sample points from Android's [`FontScaleConverter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/FontScaleConverter.java) and includes them in the `flutter/settings` message payload: - - ```json - { - "textScaleFactor": 1.15, - "platformBrightness": "light", - "alwaysUse24HourFormat": false, - "fontScaleCurves": [ - {"unscaled": 8.0, "scaled": 9.2}, - {"unscaled": 14.0, "scaled": 15.4}, - {"unscaled": 20.0, "scaled": 21.0} - ] - } - ``` + ```c + /// Callback invoked synchronously on the UI thread by the engine to compute + /// a scaled font size (in logical pixels) for a given `unscaled_font_size` + /// and `configuration_id`. Returns a negative value (for example -1.0) if + /// `configuration_id` is unknown or non-linear scaling is unavailable. + typedef double (*FlutterGetScaledFontSizeCallback)( + double unscaled_font_size, + int configuration_id, + void* user_data); + + // Appended to FlutterProjectArgs: + FlutterGetScaledFontSizeCallback get_scaled_font_size_callback; + ``` - - Dart's [`PlatformDispatcher`](https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher-class.html) caches these sample points and interpolates scaled font sizes locally in Dart. This avoids synchronous JNI calls during text layout and resolves the `FML_UNREACHABLE()` abort under `embedder.h`. + When `get_scaled_font_size_callback` is null, `PlatformViewEmbedder::GetScaledFontSize` returns `-1.0`. This preserves `PlatformDispatcher.scaleFontSize`, `_updateUserSettingsData`, and [`SettingsChannel`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)'s `ConfigurationQueue` without any `dart:ui` or channel protocol changes. ## Target Architecture and Design Principles @@ -1429,7 +1482,7 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | -| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterRectInsets`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | +| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`)
- `FlutterProjectArgs.get_scaled_font_size_callback` (`FlutterGetScaledFontSizeCallback`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge`, foldable/edge-to-edge metrics, and Android 14+ non-linear font scaling to the engine without coupling to internal Flow or `lib/ui` headers. | | **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolver` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | | **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | From 1f0f7a3cafe25a82d33c8030974a03b7e8474488 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 21:38:28 +0000 Subject: [PATCH 23/43] Keep scaled font as a system channel --- ...lutter-android-embedder-c-api-migration.md | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index af6617d..09ab037 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -1316,29 +1316,41 @@ The following fields are appended to [`FlutterWindowMetricsEvent`](https://githu const FlutterDisplayFeature** display_features; ``` -#### System Channels and Non-Linear Font Scaling +#### System Channels: Locale and Non-Linear Font Scaling -1. **Locale Configuration**: The embedder uses the standard `flutter/localization` platform message channel ([`LocalizationChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java#L61-L97)). In the engine ([`embedder.cc:3423-3480`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3423-L3480)), [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3552) serializes locale data into a JSON message on this channel. No new C-API entry point is added for locale settings. - -2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses non-linear font scaling curves so that large typography scales at lower ratios than body text, preventing UI clipping. In `dart:ui`, [`PlatformDispatcher.scaleFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1553-L1612) queries integer font sizes via `@Native PlatformConfigurationNativeApi::GetScaledFontSize` ([`PlatformView::GetScaledFontSize(double unscaled_font_size, int configuration_id)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L118)), which `PlatformViewEmbedder` did not previously override. - - Following the existing pattern of [`compute_platform_resolved_locale_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L193-L203), `embedder.h` adds a synchronous UI-thread callback on `FlutterProjectArgs` that `PlatformViewEmbedder::GetScaledFontSize` forwards to: +Features that communicate standard framework and platform configuration use platform message channels rather than custom additions to `embedder.h`: - ```c - /// Callback invoked synchronously on the UI thread by the engine to compute - /// a scaled font size (in logical pixels) for a given `unscaled_font_size` - /// and `configuration_id`. Returns a negative value (for example -1.0) if - /// `configuration_id` is unknown or non-linear scaling is unavailable. - typedef double (*FlutterGetScaledFontSizeCallback)( - double unscaled_font_size, - int configuration_id, - void* user_data); - - // Appended to FlutterProjectArgs: - FlutterGetScaledFontSizeCallback get_scaled_font_size_callback; - ``` +1. **Locale Configuration**: The embedder uses the standard `flutter/localization` platform message channel ([`LocalizationChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java#L61-L97)). In the engine ([`embedder.cc:3423-3480`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3423-L3480)), [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3552) serializes locale data into a JSON message on this channel. No new C-API entry point is added for locale settings. - When `get_scaled_font_size_callback` is null, `PlatformViewEmbedder::GetScaledFontSize` returns `-1.0`. This preserves `PlatformDispatcher.scaleFontSize`, `_updateUserSettingsData`, and [`SettingsChannel`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)'s `ConfigurationQueue` without any `dart:ui` or channel protocol changes. +2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses piecewise-linear font scaling curves (`FontScaleConverter`) so that large typography scales at lower ratios than body text, preventing UI clipping. In the legacy architecture, this was serviced through a synchronous JNI query ([`PlatformDispatcher._getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1648-L1649) -> [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L118) -> [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1534-L1549)) paired with [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176). Because `PlatformViewEmbedder` does not override `GetScaledFontSize`, invoking `_getScaledFontSize` under `embedder.h` hits the base `PlatformView::GetScaledFontSize` implementation (`FML_UNREACHABLE()`). + + To support non-linear scaling without synchronous UI-thread JNI queries, `ConfigurationQueue` generation tracking, or new C-APIs in `embedder.h`, the existing `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) is extended: + + - During startup and configuration changes on API 34+ (when `SettingsChannel.hasNonlinearTextScalingSupport()` is true and `textScaleFactor != 1.0`), `SettingsChannel.MessageBuilder.send()` samples `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, displayMetrics) / displayMetrics.density` across Android's standard piecewise-linear lookup table breakpoints (`8, 10, 12, 14, 18, 20, 24, 30, 100` sp) and includes the resulting `(unscaled, scaled)` curve points in the `flutter/settings` JSON payload: + + ```json + { + "textScaleFactor": 1.15, + "platformBrightness": "light", + "alwaysUse24HourFormat": false, + "fontScaleCurves": [ + {"unscaled": 8.0, "scaled": 9.2}, + {"unscaled": 10.0, "scaled": 11.5}, + {"unscaled": 12.0, "scaled": 13.8}, + {"unscaled": 14.0, "scaled": 16.4}, + {"unscaled": 18.0, "scaled": 20.6}, + {"unscaled": 20.0, "scaled": 22.4}, + {"unscaled": 24.0, "scaled": 26.2}, + {"unscaled": 30.0, "scaled": 31.8}, + {"unscaled": 100.0, "scaled": 100.0} + ] + } + ``` + + - In `dart:ui`, [`PlatformDispatcher._updateUserSettingsData`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1294-L1367) parses `fontScaleCurves` into `_PlatformConfiguration` and compares it against `previousConfiguration.fontScaleCurves`. When either `textScaleFactorChanged` or `fontScaleCurvesChanged` is true, `_updateUserSettingsData` invalidates `_cachedFontSizes` and invokes `onTextScaleFactorChanged` (in addition to `onPlatformConfigurationChanged`). + - [`PlatformDispatcher.scaleFontSize(double unscaledFontSize)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1568-L1592) evaluates scaled sizes purely in Dart from `_configuration.fontScaleCurves`: + - If `textScaleFactor == 1.0` or `_configuration.fontScaleCurves` is null/empty, it returns `unscaledFontSize * textScaleFactor`. + - Otherwise, it binary-searches the sorted `(unscaled, scaled)` breakpoints in `fontScaleCurves` and linearly interpolates between the two bounding points (`lower.scaled + (upper.scaled - lower.scaled) * ((unscaledFontSize - lower.unscaled) / (upper.unscaled - lower.unscaled))`), interpolating from `(0.0, 0.0)` below the first breakpoint and scaling proportionally (`last.scaled * (unscaledFontSize / last.unscaled)`) above the final breakpoint to match Android's `FontScaleConverterImpl`. This removes synchronous JNI round-trips during text layout and eliminates `SettingsChannel.ConfigurationQueue`. ## Target Architecture and Design Principles @@ -1482,7 +1494,7 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | -| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`)
- `FlutterProjectArgs.get_scaled_font_size_callback` (`FlutterGetScaledFontSizeCallback`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge`, foldable/edge-to-edge metrics, and Android 14+ non-linear font scaling to the engine without coupling to internal Flow or `lib/ui` headers. | +| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | | **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolver` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | | **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | From 46a01c714f9a37b1e476268a711b0e657ca46ca4 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 21:48:45 +0000 Subject: [PATCH 24/43] Updated out-of-date statements. --- ...lutter-android-embedder-c-api-migration.md | 179 ++++++++++++------ 1 file changed, 123 insertions(+), 56 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 09ab037..eb36723 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -971,7 +971,7 @@ class APKAssetProvider final : public AssetResolver { Passing file paths would require extracting all assets to disk during startup, which causes latency and disk consumption. -[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives a custom asset resolver callback interface that maps assets into memory directly from the APK archive: +[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives a custom asset resolver callback interface on [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) that maps assets into memory directly from the APK archive: ```c typedef struct { @@ -983,35 +983,16 @@ typedef struct { } FlutterAssetMapping; typedef bool (*FlutterAssetResolverGetAsMappingCallback)( - void* user_data, const char* asset_name, + void* user_data, FlutterAssetMapping* mapping_out); typedef struct { size_t struct_size; - /// Embedder-assigned identifier, unique within an engine instance. - /// `FlutterEngineUpdateAssetResolver` uses it to select the resolver to - /// replace. - int64_t identifier; void* user_data; - FlutterAssetResolverGetAsMappingCallback get_as_mapping_callback; + FlutterAssetResolverGetAsMappingCallback get_as_mapping_callback; VoidCallback destruction_callback; } FlutterCustomAssetResolver; - -typedef struct { - size_t struct_size; - const FlutterCustomAssetResolver* resolver; -} FlutterAssetResolverRegistrationInfo; - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineRegisterAssetResolver( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterAssetResolverRegistrationInfo* info); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineUpdateAssetResolver( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterAssetResolverRegistrationInfo* info); ``` In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841): @@ -1026,7 +1007,9 @@ In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072 const FlutterCustomAssetResolver** custom_asset_resolvers; ``` -`FlutterCustomAssetResolver` instances pass into `FlutterProjectArgs.custom_asset_resolvers` (or register dynamically via `FlutterEngineRegisterAssetResolver`) so that the engine streams assets directly from the APK. During hot restart or dynamic feature installation, `FlutterEngineUpdateAssetResolver` replaces the resolver with the matching `identifier` to reload asset manifests and updated Dart bytecode bundles without restarting the native engine instance. +Resolvers configured via `FlutterProjectArgs.custom_asset_resolvers` remain active across the lifetime of the engine, including hot reload, hot restart, and dynamic feature installation: +- **Hot Restart and Hot Reload**: [`Shell::OnServiceProtocolRunInView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L2034-L2043) and [`Shell::OnServiceProtocolSetAssetBundlePath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L2194-L2203) automatically re-attach every existing resolver whose `IsValidAfterAssetManagerChange()` returns `true` (which the engine's `FlutterCustomAssetResolver` wrapper implements), so the engine continues invoking `get_as_mapping_callback` alongside the updated `devFS` directory. +- **Play Store Deferred Component Split Installation**: When [`PlayStoreDeferredComponentManager.loadAssets`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java#L383-L399) creates a refreshed package context after a split APK install and calls `FlutterJNI.updateJavaAssetManager`, the Android embedder atomically swaps the `AAssetManager*` and global Java `AssetManager` reference inside the struct pointed to by the resolver's `user_data`. ### Multi-Engine Spawning and Add-to-App (`FlutterEngineSpawn`) @@ -1129,29 +1112,120 @@ Internal consumers relying on the `kMergeAfterLaunch` threading model are suppor ### Process-Scoped Utilities, Image Decoding, and Diagnostics C-APIs -Several Android JNI entry points in `FlutterMain` and `FlutterJNI` interact with engine utilities outside the scope of a single frame or require decoding and diagnostic primitives that previously pulled `//flutter/lib/ui`, `//flutter/txt`, `//flutter/runtime`, and `//flutter/skia` headers into `shell/platform/android/`. To sever those dependencies completely, `embedder.h` exposes five focused C-API groups: +Four groups of Android entry points in `FlutterMain` and `FlutterJNI` reach engine state outside the frame pipeline, and today they pull `//flutter/lib/ui`, `//flutter/txt`, and `//flutter/runtime` headers into `shell/platform/android/`. `embedder.h` covers them with four focused C-API groups. The Dart callback cache and the default font manager are process-wide singletons that Android uses before any engine exists, so their functions take no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`. -1. **Process-Scoped Callback Cache (`//flutter/lib/ui/plugins/callback_cache.h` Severance)**: Background plugins that execute Dart entrypoints without an active `FlutterView` (such as background alarm or geofencing isolates) resolve Dart function handles via [`FlutterCallbackCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h). `embedder.h` exposes process-scoped C entry points (`FlutterEngineSetCallbackCachePath`, `FlutterEngineLoadCallbackCache`, `FlutterEngineGetCallbackInformation`, and `FlutterEngineGetCallbackHandle`) along with `FlutterCallbackInformation` (`name`, `class_name`, `library_path`) so `FlutterEmbedderNative` and `FlutterMain` can query and persist callback handles without including `//flutter/lib/ui`. -2. **Hardware-Accelerated Image Decoding (`//flutter/lib/ui/painting/image_generator.h` and `//flutter/skia` Severance)**: Android API 28+ supports hardware-accelerated image decoding via `ImageDecoder`. To register platform image decoders without subclassing internal C++ `flutter::ImageGenerator` or `SkImageGenerator` types, `embedder.h` adds `FlutterEngineRegisterImageGenerator` (`FlutterImageGeneratorRegistrationInfo`, `FlutterImageGeneratorFactoryCallback`, `FlutterImageGeneratorGetInfoCallback`, and `FlutterImageGeneratorDecodeCallback`) backed by an internal `EmbedderImageLRU` cache inside `shell/platform/embedder/`. -3. **Font Manager Prefetching (`//flutter/txt` Severance)**: During `FlutterMain.init`, Android warms up the default system font manager on a background worker. `FlutterEnginePrefetchDefaultFontManager()` exposes this one-time initialization in `embedder.h` so `flutter_main.cc` no longer includes `flutter/txt/src/txt/platform.h`. -4. **VM Service URI Discovery (`//flutter/runtime/dart_service_isolate.h` Severance)**: Tooling and integration tests discover the Dart VM Service observatory URL via `FlutterJNI.getVMServiceUri`. `FlutterEngineRegisterVMServiceUriCallback` and `FlutterEngineDeregisterVMServiceUriCallback` deliver the URI string asynchronously via a `FlutterVMServiceUriCallback` handle, removing the last `//flutter/runtime` header from `shell/platform/android/`. +1. **Process-Scoped Callback Cache (`//flutter/lib/ui/plugins/callback_cache.h` Severance)**: [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L148-L157) sets the cache directory and loads persisted handles during process initialization, and [`FlutterCallbackInformation.lookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java#L27-L29) resolves handles for background-execution plugins (`workmanager`, `android_alarm_manager_plus`). Because the iOS framework already exports an Objective-C class named [`FlutterCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h#L16-L17), the C struct is named `FlutterDartCallbackInformation`: -### Platform Channel Messaging, Background Handlers, and Task Queues + ```c + typedef struct { + /// The size of this struct. Must be sizeof(FlutterDartCallbackInformation). + size_t struct_size; + /// Null-terminated UTF-8 name of the Dart function (owned by the engine). + const char* name; + /// Null-terminated UTF-8 declaring class name, or empty string for a + /// top-level function. Never null. + const char* class_name; + /// Null-terminated UTF-8 URI of the declaring Dart library. + const char* library_path; + } FlutterDartCallbackInformation; + + FLUTTER_EXPORT + FlutterEngineResult FlutterEngineInitializeCallbackCache( + const char* directory); + + FLUTTER_EXPORT + FlutterEngineResult FlutterEngineGetCallbackInformation( + int64_t handle, + FlutterDartCallbackInformation* info_out); + ``` -In the existing Android architecture, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false` to permit message dispatch from arbitrary threads: +2. **Embedder Image Decoders (`//flutter/lib/ui/painting/image_generator.h` Severance)**: [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L173-L178) registers a fallback decoder backed by Android's [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (API 28+, using [`ALLOCATOR_SOFTWARE`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/image/ImageDecoderDefaultImpl.java#L49-L53)) at priority `-1` for formats the built-in decoders reject, such as HEIF. Because the `ImageGeneratorRegistry` belongs to each `Engine`, decoders are configured via `FlutterProjectArgs.image_generators` (`image_generators_count`), and the engine keeps the encoded byte buffer alive until the generator's `destruction_callback` runs so the embedder can wrap it in a direct `ByteBuffer` without copying: -```cpp -// Existing Android message dispatch in shell/platform/android/platform_message_handler_android.h -class PlatformMessageHandlerAndroid : public PlatformMessageHandler { - public: - bool DoesHandlePlatformMessageOnPlatformThread() const override { - return false; - } - void HandlePlatformMessage(std::unique_ptr message) override; -}; -``` + ```c + typedef enum { + kFlutterImageGeneratorColorTypeRGBA8888, + kFlutterImageGeneratorColorTypeBGRA8888, + } FlutterImageGeneratorColorType; + + typedef enum { + kFlutterImageGeneratorAlphaTypeOpaque, + kFlutterImageGeneratorAlphaTypePremul, + kFlutterImageGeneratorAlphaTypeUnpremul, + } FlutterImageGeneratorAlphaType; + + typedef struct { + size_t struct_size; + uint32_t width; + uint32_t height; + FlutterImageGeneratorColorType color_type; + FlutterImageGeneratorAlphaType alpha_type; + } FlutterImageGeneratorInfo; + + typedef bool (*FlutterImageGeneratorGetInfoCallback)( + void* user_data, + FlutterImageGeneratorInfo* info_out); + + typedef bool (*FlutterImageGeneratorGetPixelsCallback)( + void* user_data, + const FlutterImageGeneratorInfo* info, + void* pixels, + size_t row_bytes); -In [`platform_message_handler_android.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.cc), [`HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h) dispatches the message directly across JNI without posting to the platform thread first: + typedef struct { + size_t struct_size; + void* user_data; + FlutterImageGeneratorGetInfoCallback get_info; + FlutterImageGeneratorGetPixelsCallback get_pixels; + VoidCallback destruction_callback; + } FlutterImageGenerator; + + typedef bool (*FlutterImageGeneratorFactoryCallback)( + void* user_data, + const uint8_t* data, + size_t data_size, + FlutterImageGenerator* generator_out); + + typedef struct { + size_t struct_size; + FlutterImageGeneratorFactoryCallback create_generator; + int32_t priority; + void* user_data; + VoidCallback destruction_callback; + } FlutterImageGeneratorRegistration; + + // Appended to FlutterProjectArgs: + size_t image_generators_count; + const FlutterImageGeneratorRegistration** image_generators; + ``` + +3. **Font Manager Prefetching (`//flutter/txt` Severance)**: During [`FlutterLoader.initAsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L276-L278), Android warms up the process-wide default system font manager on a background worker before `FlutterEngineInitialize` and passes `--prefetched-default-font-manager` in `command_line_argv`: + + ```c + FLUTTER_EXPORT + FlutterEngineResult FlutterEnginePrefetchDefaultFontManager(void); + ``` + +4. **VM Service URI Discovery (`//flutter/runtime/dart_service_isolate.h` Severance)**: Tooling and integration tests (such as the `espresso` package) read the Dart VM Service URL via `FlutterJNI.getVMServiceUri()`. `FlutterProjectArgs` adds `vm_service_uri_callback`, invoked on the platform thread once the VM service is listening (and never invoked after `FlutterEngineShutdown`): + + ```c + typedef struct { + size_t struct_size; + const char* uri; + } FlutterVMServiceUriInfo; + + typedef void (*FlutterVMServiceUriCallback)( + const FlutterVMServiceUriInfo* info, + void* user_data); + + // Appended to FlutterProjectArgs: + FlutterVMServiceUriCallback vm_service_uri_callback; + ``` + +**Platform-Side Screenshot Capture (`FlutterRenderer.getBitmap()`)**: No screenshot entry point is added to `embedder.h`. Engine-side `Rasterizer::Screenshot` re-rasterizes the last layer tree with a null `ExternalViewEmbedder` ([`rasterizer.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L910-L920)), which omits HC and HCPP platform views. Instead, the Android embedder implements `FlutterRenderer.getBitmap()` in platform code using [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) (available from `minSdk` 24), matching how the macOS embedder serves `flutter/screenshot` from its front `IOSurface`. + +### Platform Channel Messaging, Background Handlers, and Task Queues + +In the existing Android architecture, [`PlatformMessageHandlerAndroid::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.cc#L56-L67) invokes `FlutterJNI.handlePlatformMessage` synchronously on the calling thread—either the UI thread (via [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1542-L1579)) or a background Dart isolate's thread (via [`UIDartState::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L202-L220)): ```cpp // Existing Android message dispatch in shell/platform/android/platform_message_handler_android.cc @@ -1168,23 +1242,16 @@ void PlatformMessageHandlerAndroid::HandlePlatformMessage( } ``` -In contrast, desktop embedders using [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) return `true` (enforcing platform-thread handling in [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L34-L36)): - -```cpp -// Desktop embedder behavior in shell/platform/embedder/platform_view_embedder.cc -virtual bool DoesHandlePlatformMessageOnPlatformThread() const { - return true; -} -``` +Inside Java, [`DartMessenger.dispatchMessageToQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L299-L327) inspects the handler's registered `TaskQueue` and either dispatches to a background `SerialTaskQueue` / `ConcurrentTaskQueue` executor (`makeBackgroundTaskQueue()`) or posts to the main `Looper`'s `PlatformTaskQueue`. Meanwhile, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false`, which tells [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1553-L1571) to bounce messages emitted during engine startup (`route_messages_through_platform_thread_`) through the platform thread before dispatching them, ensuring plugins registered immediately after `FlutterEngineRun` in the same platform-thread turn do not miss messages sent during `main()`. -Forcing mobile platforms to conform to desktop's `true` model would introduce severe performance and architectural regressions. +In contrast, [`EmbedderPlatformMessageHandler::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) in `shell/platform/embedder/` unconditionally posts every message to `platform_task_runner_` and returns `true` from `DoesHandlePlatformMessageOnPlatformThread()`. -#### Architectural Rationale for Off-Platform-Thread Dispatch +#### Rationale for Direct Platform Message Dispatch -Returning `false` provides two critical architectural capabilities on mobile platforms: +Although the platform and UI threads are merged by default on Android ([`settings.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/settings.h#L381-L382))—meaning root-isolate platform messages already originate on the Android main thread and `makeBackgroundTaskQueue()` handlers still run on a background executor either way—invoking `platform_message_callback` directly in `HandlePlatformMessage` (and returning `false` from `DoesHandlePlatformMessageOnPlatformThread()`) preserves behavioral parity in two ways: -1. **Zero Main-Thread Contention for Background Channels**: Flutter plugins handling high-bandwidth or compute-heavy workloads (such as camera stream ingestion, local database operations via SQLite, network responses, and cryptography) register background task queues via `BinaryMessenger.makeBackgroundTaskQueue()`. Because message dispatch does not force an intermediate hop to the platform thread, this traffic bypasses Android's main [`Looper`](https://developer.android.com/reference/android/os/Looper) entirely. The platform main thread remains dedicated to view layout, input processing, and frame presentation. -2. **Elimination of Latency and Head-of-Line Blocking**: Forcing a hop from the engine UI thread through the platform thread before dispatching to a background worker pool introduces an extra event loop dispatch cycle and thread context switch. If the Android main thread is blocked performing expensive view inflation or layout measurement passes, background channel execution stalls behind that work, which negates the advantage of offloading work to background workers. +1. **Avoiding an Extra Main-`Looper` Turn for Root-Isolate Messages**: With direct dispatch, `DartMessenger.handleMessageFromDart` runs synchronously inside the send call instead of queuing an extra `PostTask` at the tail of the main `Looper`. Handlers registered with `makeBackgroundTaskQueue()` are submitted to their background executor immediately while the current frame finishes on the main thread, and default-queue handlers take one main-`Looper` post (via `PlatformTaskQueue`) instead of two—matching the iOS embedder migration RFC. +2. **Keeping Background-Isolate Messages Off the Main Thread**: Background isolates registered via `PlatformDispatcher.registerBackgroundIsolate` invoke `UIDartState::HandlePlatformMessage` on their own isolate thread ([`ui_dart_state.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L202-L220)). Direct dispatch allows a background isolate communicating with a `makeBackgroundTaskQueue()` plugin handler to dispatch directly from the background isolate thread to the plugin's background executor without bouncing through the main `Looper`. #### C-ABI Specification @@ -1490,12 +1557,12 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnConfig`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterEngineRegisterImageGenerator`
- `FlutterEngineGetCallbackInformation`, `SetCallbackCachePath`, `LoadCallbackCache`, `GetCallbackHandle`
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterEngineRegisterVMServiceUriCallback` | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnInfo`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`)
- `FlutterEngineInitializeCallbackCache` & `FlutterEngineGetCallbackInformation` (`FlutterDartCallbackInformation`)
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriCallback`) | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | -| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetResolverRegistrationInfo`
- `FlutterAssetMapping`
- `FlutterEngineRegisterAssetResolver` & `FlutterEngineUpdateAssetResolver` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping. | +| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetMapping` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping across hot restarts and split APK installs. | | **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | ## Phased Implementation Strategy From 472f570e296f9355283ede46b1890684f57be54d Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 22:01:08 +0000 Subject: [PATCH 25/43] deferred surface creation and alternative --- ...lutter-android-embedder-c-api-migration.md | 254 ++++++++++++++++-- 1 file changed, 228 insertions(+), 26 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index eb36723..05ae9d4 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -235,10 +235,13 @@ This level of coupling creates several concrete maintenance problems: To allow the Android embedder to communicate with the engine exclusively through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the C-API must be extended to cover Android-specific subsystem requirements. Each proposed extension corresponds directly to uncoupling one of the internal subsystems identified in the dependency audit and supporting a target embedder component (`FlutterEmbedderNative`, `JniRouter` / `JniDelegate` / `JvmInvoker`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17)). ### Graphics Surface Setup, Context Ownership, and Surface Lifecycle ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)) + #### How `SetupImpellerContext` Works on Android Today + During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L277-L335) creates the `PlatformView` on the Android UI thread (`on_create_platform_view`) and then posts a `CreateImpellerContext` task to the raster task runner without blocking the UI thread: + ```cpp -// Existing raster-thread task in shell/common/shell.cc (L316-L324) +// Existing raster-thread task in shell/common/shell.cc (L314-L333) fml::TaskRunner::RunNowOrPostTask( task_runners.GetRasterTaskRunner(), fml::MakeCopyable([..., platform_view_ptr]() mutable { @@ -249,7 +252,9 @@ fml::TaskRunner::RunNowOrPostTask( ... })); ``` + `PlatformView::SetupImpellerContext()` was added (in [#167765](https://github.com/flutter/flutter/pull/167765)) because Vulkan driver and context initialization can take 100+ ms on some Android devices. In the current Android embedder, `PlatformViewAndroid` constructs [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) as an empty shell on the UI thread and defers all graphics work until `CreateImpellerContext` runs [`PlatformViewAndroid::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570) on the raster thread: + ```cpp // Existing deferred graphics resolution in shell/platform/android/android_context_dynamic_impeller.cc void AndroidContextDynamicImpeller::SetupImpellerContext() { @@ -265,20 +270,51 @@ void AndroidContextDynamicImpeller::SetupImpellerContext() { } } ``` + That single raster-thread call currently performs two distinct jobs inside `shell/platform/android/`: + 1. **Choosing the backend and creating the underlying GPU device**: [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120) checks system properties and a known-bad SoC list, loads `libvulkan.so`, creates the [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html) and [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), checks Impeller's known-bad driver list ([`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)) in [`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.cc#L65-L69), and falls back to OpenGL ES if any step fails. 2. **Constructing the `impeller::Context`**: `AndroidContextVKImpeller` and `AndroidContextGLImpeller` call [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h) or [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h) to set up shader library mappings, the pipeline cache, the memory allocator, and descriptor/command pools. + #### Why Moving to `embedder.h` Requires Changes on Both Sides + When Android migrates to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `PlatformViewAndroid` and `AndroidContextDynamicImpeller` are removed. The Android embedder no longer creates or owns `impeller::Context`; it creates only raw Vulkan (`VkInstance`, `VkPhysicalDevice`, `VkDevice`, [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html)) or EGL (`EGLDisplay`, `EGLContext`, `EGLConfig`) handles and passes them in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) to [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). Inside the engine, [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h) (`EmbedderSurfaceVulkanImpeller` and `EmbedderSurfaceGLImpeller`) own the `impeller::Context`. -Preserving Android's non-blocking startup behavior requires two changes: -1. **Backend selection and `VkDevice` creation must happen before `FlutterEngineInitialize`**, because `FlutterRendererConfig` requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when `FlutterEngineInitialize` is called—before `Shell::CreateWithSnapshot` or the raster thread runs. + +Preserving Android's non-blocking startup behavior while staying on the existing two-phase initialization API (`FlutterEngineInitialize` and `FlutterEngineRunInitialized`) requires three coordinated changes: + +1. **Backend selection and `VkDevice` creation must run asynchronously before `FlutterEngineInitialize`**, because `FlutterRendererConfig` requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when `FlutterEngineInitialize` is called—before `Shell::CreateWithSnapshot` or the raster thread runs. 2. **`PlatformViewEmbedder` and `EmbedderSurface` must be wired up to `SetupImpellerContext()` in `shell/platform/embedder/`**, because [#167765](https://github.com/flutter/flutter/pull/167765) only implemented `SetupImpellerContext()` on `PlatformViewAndroid`. In `shell/platform/embedder/` today: - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) does not override `SetupImpellerContext()`. It only overrides `GetImpellerContext()`, which calls `embedder_surface_->CreateImpellerContext()`. - - Despite the name `CreateImpellerContext()`, both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L680-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L496-L499)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. Without changing `shell/platform/embedder/`, migrating Android to `embedder.h` would move `impeller::Context` construction back onto the UI thread. + - Despite the name `CreateImpellerContext()`, both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L680-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L496-L499)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. +3. **`FlutterEngineRunInitialized` must not call `NotifyCreated()` before a window surface exists.** In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2580-L2591) today, `FlutterEngineRunInitialized` calls `embedder_engine->LaunchShell()` and immediately calls `embedder_engine->NotifyCreated()`: + ```cpp + // Existing implementation in shell/platform/embedder/embedder.cc (L2580-L2599) + // Step 1: Launch the shell. + if (!embedder_engine->LaunchShell()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, ...); + } + + // Step 2: Tell the platform view to initialize itself. + if (!embedder_engine->NotifyCreated()) { + return LOG_EMBEDDER_ERROR(kInternalInconsistency, ...); + } + + // Step 3: Launch the root isolate. + if (!embedder_engine->RunRootIsolate()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, ...); + } + ``` + Because [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) posts `CreateRenderingSurface()` to the FIFO raster task runner—queued **behind** `CreateImpellerContext`—and blocks the UI thread on a latch (`latch.Wait()`), calling `NotifyCreated()` inside `FlutterEngineRunInitialized` forces the UI thread to wait for `SetupImpellerContext()` to finish anyway, and creates a premature surface before Android's `SurfaceHolder.Callback.surfaceCreated` has provided an `ANativeWindow`. + #### Backend Selection and Background Vulkan Probe Before `FlutterEngineInitialize` -To choose between `kVulkan` and `kOpenGL` before `FlutterEngineInitialize` without blocking the Android UI thread, the embedder runs a two-stage probe on the background executor task already started by [`FlutterLoader.startInitialization`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L197-L291) and joins that task in `FlutterLoader.ensureInitializationComplete`: + +In [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L62-L63) and [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L388-L389), `FlutterLoader.startInitialization()` and `FlutterLoader.ensureInitializationComplete()` are called on consecutive lines on the UI thread unless the host application pre-warms `FlutterLoader` in `Application.onCreate`. Joining the Vulkan probe inside `ensureInitializationComplete()` would therefore block the UI thread immediately with zero overlap. + +Instead, the embedder follows the same pattern [`FlutterLoader.initTask`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L278) uses for `prefetchDefaultFontManager`: as soon as `flutterJNI.loadLibrary(appContext)` completes on `initTask`, `FlutterLoader` submits the process-wide two-stage Vulkan probe as a separate task on `executorService` that `ensureInitializationComplete()` does **not** wait on. The embedder only joins the probe future right before constructing `FlutterRendererConfig` for `FlutterEngineInitialize`, overlapping the probe with `FlutterMain.init`, `FlutterEngine` Java subsystem construction, plugin registration, and initial channel setup: + - **Stage 1 (Android system-property and SoC deny-list)**: Before loading `libvulkan.so`, the Android embedder runs the checks from [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120): known emulators, Huawei devices (`ro.com.google.clientidbase`), MediaTek devices with `ro.vendor.build.version.sdk < 32`, and the `ro.product.board` deny-list ([`IsKnownBadSOC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L68-L77)). Matching devices select `kOpenGL` immediately without loading Vulkan. - **Stage 2 (Impeller known-bad driver query via `embedder.h`)**: If Stage 1 passes, the background task loads Vulkan, selects a physical device, and reads `VkPhysicalDeviceProperties`. To check those properties against Impeller's [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377) while keeping the driver blocklist in one place inside Impeller, `embedder.h` adds a stateless query function: + ```c typedef struct { /// The size of this struct. Must be sizeof(FlutterVulkanDriverProperties). @@ -295,18 +331,25 @@ typedef struct { /// VkPhysicalDeviceProperties::deviceName; only read during the call. const char* device_name; } FlutterVulkanDriverProperties; + FLUTTER_EXPORT FlutterEngineResult FlutterEngineQueryVulkanDriverSupport( const FlutterVulkanDriverProperties* properties, bool* out_is_known_bad); ``` -The embedder initializes `is_known_bad = true` before calling `FlutterEngineQueryVulkanDriverSupport`. If the call fails or leaves `is_known_bad` set to `true` (unless Vulkan was explicitly forced in a non-release build), or if `VkDevice` creation fails, the embedder tears down its Vulkan handles and falls back to `kOpenGL`. -#### Deferring `impeller::Context` Creation in `shell/platform/embedder/` (`SetupImpellerContext`) -To keep `impeller::ContextVK::Create` and `impeller::ContextGLES::Create` on the raster thread instead of running them in the `EmbedderSurface` constructors on the UI thread, `shell/platform/embedder/` is updated as follows: + +The embedder initializes `is_known_bad = true` before calling `FlutterEngineQueryVulkanDriverSupport`. If the call fails or leaves `is_known_bad` set to `true` (unless Vulkan was explicitly forced in a non-release build), or if `VkDevice` creation fails, the embedder tears down its Vulkan handles and falls back to `kOpenGL`. Because the probed `VkInstance` and `VkDevice` are held by a process-wide singleton (`VulkanDeviceOwner`), subsequent `FlutterEngine` instances reuse the initialized handles with zero probe cost. + +#### Deferring `impeller::Context` and Surface Creation in `shell/platform/embedder/` (`SetupImpellerContext`) + +To keep `impeller::ContextVK::Create` and `impeller::ContextGLES::Create` on the raster thread instead of running them in the `EmbedderSurface` constructors on the UI thread, and to prevent `FlutterEngineRunInitialized` from blocking on `PlatformView::NotifyCreated()` before a window surface exists, `shell/platform/embedder/` is updated as follows: + - [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) adds `virtual void SetupImpellerContext() {}`. - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) overrides `PlatformView::SetupImpellerContext()` and forwards the call to `embedder_surface_->SetupImpellerContext()`. - [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L20-L95) validates the `VulkanDispatchTable` callbacks and stores `impeller::ContextVK::Settings` (including `EmbedderData` handles and extension lists) in its constructor without creating the Impeller context. It overrides `SetupImpellerContext()` to run `context_ = impeller::ContextVK::Create(std::move(settings_))` on the raster thread when `Shell::CreateWithSnapshot` executes the `CreateImpellerContext` task. - [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L77-L132) likewise validates `gl_dispatch_table_` in its constructor and moves `gl_make_current_callback()`, `ProcTableGLES` creation, `impeller::ContextGLES::Create(...)`, and `gl_clear_current_callback()` into `EmbedderSurfaceGLImpeller::SetupImpellerContext()` on the raster thread. +- [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) records `SAFE_ACCESS(args, defer_surface_creation, false)` during [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2549-L2563). When `defer_surface_creation` is `true`, [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2566-L2600) executes `LaunchShell()` and `RunRootIsolate()` and skips the implicit `NotifyCreated()` call, leaving surface creation to `FlutterEngineNotifyCreated`. + ```cpp // In shell/platform/embedder/platform_view_embedder.cc void PlatformViewEmbedder::SetupImpellerContext() { @@ -314,6 +357,7 @@ void PlatformViewEmbedder::SetupImpellerContext() { embedder_surface_->SetupImpellerContext(); } } + std::shared_ptr PlatformViewEmbedder::GetImpellerContext() const { if (embedder_surface_ == nullptr) { @@ -321,6 +365,7 @@ std::shared_ptr PlatformViewEmbedder::GetImpellerContext() } return embedder_surface_->CreateImpellerContext(); } + // In shell/platform/embedder/embedder_surface_vulkan_impeller.cc void EmbedderSurfaceVulkanImpeller::SetupImpellerContext() { if (context_ || !valid_) { @@ -332,7 +377,62 @@ void EmbedderSurfaceVulkanImpeller::SetupImpellerContext() { valid_ = false; } } + +// In shell/platform/embedder/embedder.cc +// Inside FlutterEngineInitialize: +bool defer_surface_creation = + SAFE_ACCESS(args, defer_surface_creation, false); + +auto embedder_engine = std::make_unique( + std::move(thread_host), // + std::move(task_runners), // + std::move(settings), // + std::move(run_configuration), // + on_create_platform_view, // + on_create_rasterizer, // + std::move(external_texture_resolver), // + defer_surface_creation // +); + +// Inside FlutterEngineRunInitialized: +FlutterEngineResult FlutterEngineRunInitialized( + FLUTTER_API_SYMBOL(FlutterEngine) engine) { + if (!engine) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); + } + + auto embedder_engine = reinterpret_cast(engine); + if (embedder_engine->IsValid()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); + } + + // Step 1: Launch the shell (posts CreateImpellerContext to the raster thread). + if (!embedder_engine->LaunchShell()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, + "Could not launch the engine using supplied " + "initialization arguments."); + } + + // Step 2: Tell the platform view to initialize itself unless the embedder + // defers surface creation until FlutterEngineNotifyCreated. + if (!embedder_engine->DeferSurfaceCreation() && + !embedder_engine->NotifyCreated()) { + return LOG_EMBEDDER_ERROR(kInternalInconsistency, + "Could not create platform view components."); + } + + // Step 3: Launch the root isolate. + if (!embedder_engine->RunRootIsolate()) { + return LOG_EMBEDDER_ERROR( + kInvalidArguments, + "Could not run the root isolate of the Flutter application using the " + "project arguments specified."); + } + + return kSuccess; +} ``` + ```mermaid sequenceDiagram autonumber @@ -340,35 +440,57 @@ sequenceDiagram participant Bg as "FlutterLoader Executor" participant Emb as "PlatformViewEmbedder & EmbedderSurface" participant Raster as "Engine Raster Thread" - UI->>Bg: FlutterLoader.startInitialization() - Bg->>Bg: Stage 1: check system properties & SoC deny-list - Bg->>Bg: Create VkInstance & read VkPhysicalDeviceProperties - Bg->>Emb: FlutterEngineQueryVulkanDriverSupport(props, &is_known_bad) - Emb-->>Bg: kSuccess (is_known_bad == false) - Bg->>Bg: Create VkDevice & VkQueue (or fall back to EGL if known-bad) - UI->>Bg: FlutterLoader.ensureInitializationComplete() (join probe) + + UI->>Bg: FlutterLoader.startInitialization() (loadLibrary) + Note over Bg: After loadLibrary, initTask spawns
non-blocking executorService probe + par Background Vulkan Probe + Bg->>Bg: Stage 1: check system properties & SoC deny-list + Bg->>Bg: Create VkInstance & read VkPhysicalDeviceProperties + Bg->>Emb: FlutterEngineQueryVulkanDriverSupport(props, &is_known_bad) + Emb-->>Bg: kSuccess (is_known_bad == false) + Bg->>Bg: Create VkDevice & VkQueue (or fall back to EGL if known-bad) + and UI Thread Continues + UI->>UI: ensureInitializationComplete() + FlutterEngine. + initial channels + end + UI->>Bg: Join Vulkan probe future before FlutterEngineInitialize Bg-->>UI: Resolved backend & device handles - UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig) + UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig, defer_surface_creation=true) + UI->>Emb: FlutterEngineRunInitialized() -> LaunchShell() + RunRootIsolate() Emb->>Emb: Construct EmbedderSurfaceVulkanImpeller (store settings_ only) - UI->>Emb: FlutterEngineRunInitialized() -> Shell::CreateWithSnapshot() - Emb->>Raster: Post CreateImpellerContext task + Emb-)Raster: Post CreateImpellerContext task (no NotifyCreated latch wait) Emb-->>UI: Return without waiting for ContextVK - Raster->>Emb: PlatformViewEmbedder::SetupImpellerContext() - Emb->>Emb: EmbedderSurfaceVulkanImpeller::SetupImpellerContext() - Emb->>Emb: context_ = impeller::ContextVK::Create(std::move(settings_)) - Raster->>Emb: PlatformViewEmbedder::GetImpellerContext() - Emb-->>Raster: context_ + par Raster Thread Initializes Impeller Context + Raster->>Emb: PlatformViewEmbedder::SetupImpellerContext() + Emb->>Emb: EmbedderSurfaceVulkanImpeller::SetupImpellerContext() + Emb->>Emb: context_ = impeller::ContextVK::Create(std::move(settings_)) + Raster->>Emb: PlatformViewEmbedder::GetImpellerContext() + Emb-->>Raster: context_ + and UI Thread Inflates Activity View Hierarchy + UI->>UI: Activity.onStart() / onResume() / WindowManager layout + end + UI->>Emb: surfaceCreated(ANativeWindow*) -> FlutterEngineNotifyCreated(engine) + Emb->>Raster: CreateRenderingSurface() (ContextVK already initialized) ``` + Once the context is initialized, window surface attachments ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and swapchains are created and destroyed across Android [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events (`FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` - see next section) without recreating the `VkDevice`, `EGLContext`, or `impeller::Context`. ### Renderer Availability and Surface Lifecycle + Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) are created and destroyed by the OS during [`Activity`](https://developer.android.com/reference/android/app/Activity) transitions, configuration changes, and backgrounding while the `FlutterEngine` remains alive. #### Decoupling Configuration from Surface Availability Static graphics configuration (the Vulkan device and queue handles, or the EGL context and callbacks) persists in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) for the lifetime of the engine, while presentation surfaces attach and detach across `Activity` lifecycle events. Recreating `FlutterRendererConfig` on every window change would re-initialize the GPU context and discard Impeller pipeline caches. -In the legacy architecture, `PlatformViewAndroid` called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L201) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L233) directly as a `PlatformView` subclass. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, two new functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): + +In the legacy architecture, `PlatformViewAndroid` called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L201) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L233) directly as a `PlatformView` subclass only when an `ANativeWindow` was attached or detached. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) adds `bool defer_surface_creation` (instructing `FlutterEngineRunInitialized` not to call `NotifyCreated` prematurely when no window surface exists yet) and two surface lifecycle functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): + ```c +// In FlutterProjectArgs: +/// When true, `FlutterEngineRunInitialized` launches the shell and root isolate +/// without calling `NotifyCreated`. The embedder must subsequently call +/// `FlutterEngineNotifyCreated` once a render surface or native window exists. +bool defer_surface_creation; + /// Notify the engine that the render surface or native window was created. /// /// Must be called on the platform thread after the embedder's window surface @@ -388,16 +510,96 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineNotifyDestroyed( FLUTTER_API_SYMBOL(FlutterEngine) engine); ``` + +In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc), `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` delegate directly to `EmbedderEngine::NotifyCreated` and `EmbedderEngine::NotifyDestroyed`: + +```cpp +FlutterEngineResult FlutterEngineNotifyCreated( + FLUTTER_API_SYMBOL(FlutterEngine) engine) { + if (!engine) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); + } + auto embedder_engine = reinterpret_cast(engine); + if (!embedder_engine->NotifyCreated()) { + return LOG_EMBEDDER_ERROR(kInternalInconsistency, + "Could not create platform view components."); + } + return kSuccess; +} + +FlutterEngineResult FlutterEngineNotifyDestroyed( + FLUTTER_API_SYMBOL(FlutterEngine) engine) { + if (!engine) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); + } + auto embedder_engine = reinterpret_cast(engine); + if (!embedder_engine->NotifyDestroyed()) { + return LOG_EMBEDDER_ERROR(kInternalInconsistency, + "Could not destroy platform view components."); + } + return kSuccess; +} +``` + > [!NOTE] **New Surface Lifecycle Exports** -> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are new additions to `embedder.h` exposing `EmbedderEngine::NotifyCreated` and `EmbedderEngine::NotifyDestroyed` (`PlatformView::NotifyCreated` and `PlatformView::NotifyDestroyed`). +> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are new additions to `embedder.h` exposing `EmbedderEngine::NotifyCreated` and `EmbedderEngine::NotifyDestroyed` (`PlatformView::NotifyCreated` and `PlatformView::NotifyDestroyed`). Setting `FlutterProjectArgs.defer_surface_creation = true` prevents `FlutterEngineRunInitialized` from calling `NotifyCreated` before the first `SurfaceHolder.Callback.surfaceCreated` event. #### Lifecycle Mapping and Synchronous Destruction Contract These functions map to Android [`SurfaceHolder.Callback`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback) events on the platform thread: -- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder binds the [`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window) and swapchain surface first, then calls `FlutterEngineNotifyCreated` so the rasterizer can allocate its GPU surface and resume rendering. + +- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder binds the [`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window) and swapchain surface first, then calls `FlutterEngineNotifyCreated` so the rasterizer can allocate its GPU surface and begin rendering. - [`surfaceChanged`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder,%20int,%20int,%20int)): The embedder sends the updated physical dimensions and device pixel ratio via `FlutterEngineSendWindowMetricsEvent`. - [`surfaceDestroyed`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)): The embedder calls `FlutterEngineNotifyDestroyed` before releasing the `ANativeWindow`. Inside the engine, [`Shell::OnPlatformViewDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1066-L1129) posts teardown to the raster thread, chains an unref-queue drain on the IO thread, and waits on a latch before returning to the platform thread. Only after `FlutterEngineNotifyDestroyed` returns does the embedder destroy its swapchain and release the `ANativeWindow` back to the OS, preventing `SIGSEGV` or `EGL_BAD_NATIVE_WINDOW` errors from in-flight raster work. +#### Alternative Considered: Splitting Startup into Separate Shell and Isolate Launch APIs + +An alternative design was prototyped that split `FlutterEngineRunInitialized` into separate shell-creation and isolate-launch C APIs so that `FlutterJNI.attachToNative()` could create the `Shell` before `DartExecutor.executeDartEntrypoint()` (`FlutterJNI.runBundleAndSnapshotFromLibrary()`) specified the Dart entrypoint: + +```c +// Alternative C-API additions (not adopted): +// In FlutterProjectArgs: +bool defer_root_isolate_launch; +const char* initial_lifecycle_state; + +typedef struct { + size_t struct_size; + const char* assets_path; + const char* custom_dart_entrypoint; + const char* dart_entrypoint_library_uri; + size_t dart_entrypoint_argc; + const char* const* dart_entrypoint_argv; + size_t custom_asset_resolvers_count; + const FlutterCustomAssetResolver** custom_asset_resolvers; +} FlutterEngineRunConfiguration; + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineLaunchShell( + FLUTTER_API_SYMBOL(FlutterEngine) engine); + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineLaunchEngine( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterEngineRunConfiguration* config); +``` + +Under this alternative, `attachToNative()` called `FlutterEngineInitialize` (with `defer_root_isolate_launch = true` and `initial_lifecycle_state = "AppLifecycleState.detached"`) followed by `FlutterEngineLaunchShell`, and `runBundleAndSnapshotFromLibrary()` later called `FlutterEngineLaunchEngine` with `FlutterEngineRunConfiguration`. The motivation was to mirror legacy `AndroidShellHolder` startup ordering so that platform messages (`flutter/localization`, `flutter/navigation` `setInitialRoute`, `flutter/settings`), viewport metrics, and external textures sent between `attachToNative()` and `executeDartEntrypoint()` could be buffered directly inside the engine's `RuntimeController`, and so `Shell::CreateWithSnapshot` could post `CreateImpellerContext` to the raster thread during `attachToNative()`. + +This alternative was rejected in favor of keeping the existing two-phase `FlutterEngineInitialize` and `FlutterEngineRunInitialized` sequence (paired with `defer_surface_creation` and `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`) for four reasons: + +1. **Avoids a fragile four-stage initialization state machine in `embedder.h`**: `embedder.h` already separates startup into `FlutterEngineInitialize` and `FlutterEngineRunInitialized` so embedders can obtain the `FlutterEngine` handle and bind custom task runners before any tasks are posted. Splitting startup further into `FlutterEngineInitialize` → `FlutterEngineLaunchShell` → `FlutterEngineLaunchEngine` → `FlutterEngineNotifyCreated` introduces intermediate partially-initialized `EmbedderEngine` states, duplicates `FlutterProjectArgs` fields (`assets_path`, `custom_dart_entrypoint`, `dart_entrypoint_argv`, `custom_asset_resolvers`) into `FlutterEngineRunConfiguration`, and requires seeding `initial_lifecycle_state` to keep the shell from scheduling frames before an isolate exists. +2. **Preserves a larger overlap window for the background Vulkan probe**: Because `FlutterRendererConfig` requires a resolved backend (`kVulkan` or `kOpenGL`) and live `VkInstance`/`VkDevice` handles when `FlutterEngineInitialize` is called, invoking `FlutterEngineInitialize` earlier in `attachToNative()` forces the UI thread to join the background `FlutterLoader` Vulkan probe earlier. Calling `FlutterEngineInitialize` and `FlutterEngineRunInitialized` together in `runBundleAndSnapshotFromLibrary()` allows the background Vulkan probe to overlap with `FlutterEngine` Java subsystem construction, plugin registration, and initial system channel configuration. +3. **Pre-run buffering is self-contained in `FlutterEmbedderNative`**: Queueing platform messages, initial route, viewport metrics, and external texture registrations in `FlutterEmbedderNative` between `attachToNative()` and `runBundleAndSnapshotFromLibrary()`—and flushing them immediately after `FlutterEngineRunInitialized`—requires no new `embedder.h` entry points or `RuntimeController` lifecycle flags. +4. **`defer_surface_creation` already moves `SetupImpellerContext()` off the UI thread**: Because `runBundleAndSnapshotFromLibrary()` executes during `Activity.onCreate` / `onStart` well before WindowManager dispatches `SurfaceHolder.Callback.surfaceCreated`, skipping the implicit `NotifyCreated()` call inside `FlutterEngineRunInitialized` already gives the raster thread the entire window between `runBundleAndSnapshotFromLibrary()` and `surfaceCreated()` to complete `SetupImpellerContext()` asynchronously. + +| Dimension | Proposed: Two-Phase Init (`FlutterEngineInitialize` + `FlutterEngineRunInitialized` + `defer_surface_creation`) | Alternative: Split Shell & Isolate Launch (`FlutterEngineLaunchShell` + `FlutterEngineLaunchEngine`) | +| :--- | :--- | :--- | +| **Public C-API Surface (`embedder.h`)** | **Pro:** Reuses existing `FlutterEngineInitialize` and `FlutterEngineRunInitialized`; adds only `bool defer_surface_creation` on `FlutterProjectArgs` plus `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`. | **Con:** Adds two new functions (`FlutterEngineLaunchShell`, `FlutterEngineLaunchEngine`), a new `FlutterEngineRunConfiguration` struct duplicating `FlutterProjectArgs` fields, and two `FlutterProjectArgs` fields (`defer_root_isolate_launch`, `initial_lifecycle_state`). | +| **`EmbedderEngine` State Machine** | **Pro:** Preserves the existing two-state model (initialized → running) used by all desktop embedders and `FlutterEngineSpawn`. | **Con:** Introduces a four-stage startup state machine (initialized → shell launched without isolate → isolate launched → surface created) that must also be mirrored in `FlutterEngineSpawnInfo`. | +| **Background Vulkan Probe Overlap (`VkInstance` + `VkDevice`)** | **Pro:** Longer overlap window—the UI thread does not join the `FlutterLoader` Vulkan probe until `runBundleAndSnapshotFromLibrary()`, overlapping the probe with `FlutterEngine` Java subsystem construction, plugin registration, and initial channel setup. | **Con:** Shorter overlap window—calling `FlutterEngineInitialize` inside `attachToNative()` forces the UI thread to join the Vulkan probe before `FlutterEngine` Java construction and plugin registration run. | +| **Raster-Thread `SetupImpellerContext()` Overlap (`ContextVK` / `ContextGLES`)** | **Pro:** Runs asynchronously on the raster thread across the entire `runBundleAndSnapshotFromLibrary()` → `surfaceCreated()` window without blocking the UI thread. | **Pro:** Starts slightly earlier (at `attachToNative()` rather than `runBundleAndSnapshotFromLibrary()`), though both complete well before `surfaceCreated()`. | +| **Pre-Run State (`flutter/localization`, `setInitialRoute`, `flutter/settings`, Textures)** | **Con:** `FlutterEmbedderNative` must queue messages, viewport metrics, and external textures sent between `attachToNative()` and `runBundleAndSnapshotFromLibrary()` and flush them right after `FlutterEngineRunInitialized`.
**Pro:** Keeps pre-run queueing internal to the Android embedder without exposing `RuntimeController` state in `embedder.h`. | **Pro:** `Shell` and `RuntimeController` exist at `attachToNative()`, so pre-run platform messages and viewport metrics can be dispatched directly into the engine before the isolate launches.
**Con:** Requires `initial_lifecycle_state = "AppLifecycleState.detached"` in `FlutterProjectArgs` to prevent premature frame scheduling while the shell has no isolate. | + ### Replacing Internal [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) with `FlutterCompositor` (Preserving Hybrid Composition) Android supports four platform view composition modes: From c1886b7fa96f95866c1409e7426968fdd2605589 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 15 Oct 2026 22:27:47 +0000 Subject: [PATCH 26/43] fix links and lint --- ...lutter-android-embedder-c-api-migration.md | 326 +++++++++--------- 1 file changed, 164 insertions(+), 162 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 05ae9d4..d6d74c1 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -61,7 +61,7 @@ All direct dependencies on internal engine subsystems ([`//flutter/shell/common` The Android embedder is located in [`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android). It does not use [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). Instead, it is a customized implementation that interacts directly with core engine components. -### Current Production Architecture ([`flutter_shell_native`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L75)) +### Current Production Architecture ([`flutter_shell_native`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L69-L75)) In the existing architecture, the Java embedding coordinates with native code through JNI interfaces, primarily [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108), [`PlatformViewAndroidJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h#L38), and [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38). When initializing the engine from the platform side, [`FlutterJNI.attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) invokes native attachment via [`nativeAttach`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L449), which dispatches across the JNI registration table ([`RegisterApi`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L754-L760)) into [`AttachJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L185-L197) to instantiate the shell holder: @@ -82,7 +82,7 @@ static jlong AttachJNI(JNIEnv* env, jclass clazz, jobject flutterJNI) { } ``` -[`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) manages the lifecycle of the engine. It creates a [`flutter::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21) on the [native side](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L98-L113), instantiates a [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) object [directly](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L158-L164) through [`flutter::Shell::Create(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L137), and [owns an instance](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L117-L128) of [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43): +[`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) manages the lifecycle of the engine. It creates a [`flutter::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21) on the [native side](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L98-L113), instantiates a [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) object [directly](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L158-L164) through [`flutter::Shell::Create(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L170-L176), and [owns an instance](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L117-L128) of [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43): ```cpp // Existing Shell and ThreadHost instantiation in shell/platform/android/android_shell_holder.cc @@ -111,7 +111,7 @@ shell_ = Shell::Create(GetDefaultPlatformData(), // window data ); ``` -To start execution, [`FlutterJNI.runBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) forwards to [`RunBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L298-L318). This function calls [`AndroidShellHolder::Launch`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L287-L305), which instructs the shell to [run the engine](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L304) with the resolved [`RunConfiguration`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h#L44). +To start execution, [`FlutterJNI.runBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) forwards to [`RunBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L298-L318). This function calls [`AndroidShellHolder::Launch`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L287-L305), which instructs the shell to [run the engine](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L304) with the resolved [`RunConfiguration`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h#L29). [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) [subclasses the engine's internal `flutter::PlatformView` class](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L151-L160) (defined in [`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L51)): @@ -129,7 +129,7 @@ class PlatformViewAndroid final : public PlatformView { }; ``` -This inheritance allows Android to interact directly with internal classes such as [`flutter::Rasterizer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h#L114) (created via [rasterizer factory callbacks](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L130-L132)), [`flutter::Surface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h#L26) (created directly by [`PlatformViewAndroid::CreateRenderingSurface()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L426-L431)), [`flutter::TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L68) (manipulated via [`PlatformViewAndroid::RegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L380)), and [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211) (dispatched across JNI via [`PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1748-L1775)). In addition, window and surface lifecycle events flow from Android callbacks directly into [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L248): [`SurfaceCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L264-L269) calls [`NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L219) to bind the native window with a synchronous waitable event latch: +This inheritance allows Android to interact directly with internal classes such as [`flutter::Rasterizer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h#L114) (created via [rasterizer factory callbacks](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L130-L132)), [`flutter::Surface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h#L26) (created directly by [`PlatformViewAndroid::CreateRenderingSurface()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L426-L431)), [`flutter::TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L68) (manipulated via [`PlatformViewAndroid::RegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L380)), and [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211) (dispatched across JNI via [`PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1748-L1775)). In addition, window and surface lifecycle events flow from Android callbacks directly into [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L248): [`SurfaceCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L258-L269) calls [`NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L219) to bind the native window with a synchronous waitable event latch: ```cpp // Existing synchronous surface lifecycle blocking in shell/platform/android/platform_view_android.cc @@ -205,7 +205,7 @@ sequenceDiagram ### Dependency Entanglement in [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) -Because [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) reach directly into internal engine classes, [`flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L95) in [`engine/src/flutter/shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) depends on internal engine targets. +Because [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) reach directly into internal engine classes, [`flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) in [`engine/src/flutter/shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) depends on internal engine targets. An audit of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) reveals 98 internal engine headers included across Android shell files, categorized across 9 distinct subsystems: @@ -214,7 +214,7 @@ An audit of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/ | **Impeller & Graphics Context Setup**
(29 headers) | [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller)
[`//flutter/impeller/toolkit/android`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/android)
[`//flutter/impeller/toolkit/egl`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/egl)
[`//flutter/impeller/toolkit/gles`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/gles)
[`//flutter/impeller/toolkit/glvk`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/glvk) | [`renderer/backend/vulkan/context_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h)
[`surface_context_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.h)
[`renderer/backend/gles/context_gles.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h)
[`toolkit/android/hardware_buffer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/android/hardware_buffer.h)
[`renderer/backend/vulkan/android/ahb_texture_source_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h) | Initializing rendering contexts on the raster thread ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)), creating Vulkan and EGL swapchains, and managing zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports. | | **Platform Views & Layer Compositing**
(2 headers) | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) | [`flow/embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h)
[`flow/surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h) | Platform view compositing across Hybrid Composition++ (HCPP via [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl)), Hybrid Composition (HC), Texture Layer Hybrid Composition (TLHC), and Virtual Displays (VD); managing geometric clipping and transforms via [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211), view slicing, surface pooling ([`surface_pool.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h)), and multi-surface composition frame results ([`external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). | | **External Textures & Surface Control**
(5 headers) | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics)
[`//flutter/display_list`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list) | [`common/graphics/texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h)
[`common/graphics/gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)
[`display_list/image/dl_image_skia.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list/image/dl_image_skia.h)
[`display_list/geometry/dl_geometry_types.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list/geometry/dl_geometry_types.h) | Texture lifecycle and frame updates for Android [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (OpenGL ES) and [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (Vulkan); feeding decoded video and camera frames into the [`DisplayList`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/display_list/display_list.h) compositor. | -| **Engine Core, Lifecycle & Threading**
(11 headers) | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) | [`shell/common/shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h)
[`thread_host.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h)
[`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h)
[`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h)
[`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h)
[`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h)
[`snapshot_surface_producer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/snapshot_surface_producer.h) | Direct instantiation of [`flutter::Shell::Create(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L137), thread host allocation (Platform, UI, Raster, IO), vsync scheduling, snapshot generation, and rasterizer coordination. | +| **Engine Core, Lifecycle & Threading**
(11 headers) | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) | [`shell/common/shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h)
[`thread_host.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h)
[`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h)
[`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h)
[`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h)
[`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h)
[`snapshot_surface_producer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/snapshot_surface_producer.h) | Direct instantiation of [`flutter::Shell::Create(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L170-L176), thread host allocation (Platform, UI, Raster, IO), vsync scheduling, snapshot generation, and rasterizer coordination. | | **UI Layer & Message Dispatch**
(5 headers) | [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) | [`lib/ui/window/platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h)
[`platform_message_response.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message_response.h)
[`lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h)
[`lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h) | Binary message encoding and decoding for platform channels, background Dart entrypoint lookup ([`PluginUtilities.getCallbackHandle`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins.dart#L54) via [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h)), and native platform image decoding. | | **Dart VM, Runtime & Service Isolate**
(2 headers) | [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime)
[`//flutter/runtime:libdart`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/BUILD.gn) | [`runtime/dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h)
[`runtime/dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h) | Managing the Dart VM lifecycle, configuring concurrent message loops, isolate creation, and Dart service isolate initialization. | | **Asset Management & Packaging**
(1 header) | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) | [`assets/asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h) | Resolving assets and kernel blobs packaged within the Android APK via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17). | @@ -238,7 +238,7 @@ To allow the Android embedder to communicate with the engine exclusively through #### How `SetupImpellerContext` Works on Android Today -During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L277-L335) creates the `PlatformView` on the Android UI thread (`on_create_platform_view`) and then posts a `CreateImpellerContext` task to the raster task runner without blocking the UI thread: +During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) creates the `PlatformView` on the Android UI thread (`on_create_platform_view`) and then posts a `CreateImpellerContext` task to the raster task runner without blocking the UI thread: ```cpp // Existing raster-thread task in shell/common/shell.cc (L314-L333) @@ -253,7 +253,7 @@ fml::TaskRunner::RunNowOrPostTask( })); ``` -`PlatformView::SetupImpellerContext()` was added (in [#167765](https://github.com/flutter/flutter/pull/167765)) because Vulkan driver and context initialization can take 100+ ms on some Android devices. In the current Android embedder, `PlatformViewAndroid` constructs [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) as an empty shell on the UI thread and defers all graphics work until `CreateImpellerContext` runs [`PlatformViewAndroid::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570) on the raster thread: +[`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added (in [#167765](https://github.com/flutter/flutter/pull/167765)) because Vulkan driver and context initialization can take 100+ ms on some Android devices. In the current Android embedder, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) constructs [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) as an empty shell on the UI thread and defers all graphics work until `CreateImpellerContext` runs [`PlatformViewAndroid::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570) on the raster thread: ```cpp // Existing deferred graphics resolution in shell/platform/android/android_context_dynamic_impeller.cc @@ -271,22 +271,23 @@ void AndroidContextDynamicImpeller::SetupImpellerContext() { } ``` -That single raster-thread call currently performs two distinct jobs inside `shell/platform/android/`: +That single raster-thread call currently performs two distinct jobs inside [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android): 1. **Choosing the backend and creating the underlying GPU device**: [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120) checks system properties and a known-bad SoC list, loads `libvulkan.so`, creates the [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html) and [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), checks Impeller's known-bad driver list ([`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)) in [`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.cc#L65-L69), and falls back to OpenGL ES if any step fails. -2. **Constructing the `impeller::Context`**: `AndroidContextVKImpeller` and `AndroidContextGLImpeller` call [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h) or [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h) to set up shader library mappings, the pipeline cache, the memory allocator, and descriptor/command pools. +2. **Constructing the [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70)**: [`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.h#L16) and [`AndroidContextGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h#L15) call [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h) or [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h) to set up shader library mappings, the pipeline cache, the memory allocator, and descriptor/command pools. #### Why Moving to `embedder.h` Requires Changes on Both Sides When Android migrates to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `PlatformViewAndroid` and `AndroidContextDynamicImpeller` are removed. The Android embedder no longer creates or owns `impeller::Context`; it creates only raw Vulkan (`VkInstance`, `VkPhysicalDevice`, `VkDevice`, [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html)) or EGL (`EGLDisplay`, `EGLContext`, `EGLConfig`) handles and passes them in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) to [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). Inside the engine, [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h) (`EmbedderSurfaceVulkanImpeller` and `EmbedderSurfaceGLImpeller`) own the `impeller::Context`. -Preserving Android's non-blocking startup behavior while staying on the existing two-phase initialization API (`FlutterEngineInitialize` and `FlutterEngineRunInitialized`) requires three coordinated changes: +Preserving Android's non-blocking startup behavior while staying on the existing two-phase initialization API ([`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007)) requires three coordinated changes: -1. **Backend selection and `VkDevice` creation must run asynchronously before `FlutterEngineInitialize`**, because `FlutterRendererConfig` requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when `FlutterEngineInitialize` is called—before `Shell::CreateWithSnapshot` or the raster thread runs. -2. **`PlatformViewEmbedder` and `EmbedderSurface` must be wired up to `SetupImpellerContext()` in `shell/platform/embedder/`**, because [#167765](https://github.com/flutter/flutter/pull/167765) only implemented `SetupImpellerContext()` on `PlatformViewAndroid`. In `shell/platform/embedder/` today: +1. **Backend selection and `VkDevice` creation must run asynchronously before [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975)**, because [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1042-L1055) requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) is called—before [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) or the raster thread runs. +2. **[`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) must be wired up to [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) in [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder)**, because [#167765](https://github.com/flutter/flutter/pull/167765) only implemented [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) on [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43). In [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder) today: - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) does not override `SetupImpellerContext()`. It only overrides `GetImpellerContext()`, which calls `embedder_surface_->CreateImpellerContext()`. - - Despite the name `CreateImpellerContext()`, both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L680-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L496-L499)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. -3. **`FlutterEngineRunInitialized` must not call `NotifyCreated()` before a window surface exists.** In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2580-L2591) today, `FlutterEngineRunInitialized` calls `embedder_engine->LaunchShell()` and immediately calls `embedder_engine->NotifyCreated()`: + - Despite the name [`CreateImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L25), both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L619-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L307-L500)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. +3. **[`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) must not call [`NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) before a window surface exists.** In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2515-L2526) today, `FlutterEngineRunInitialized` calls `embedder_engine->LaunchShell()` and immediately calls `embedder_engine->NotifyCreated()`: + ```cpp // Existing implementation in shell/platform/embedder/embedder.cc (L2580-L2599) // Step 1: Launch the shell. @@ -304,16 +305,17 @@ Preserving Android's non-blocking startup behavior while staying on the existing return LOG_EMBEDDER_ERROR(kInvalidArguments, ...); } ``` + Because [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) posts `CreateRenderingSurface()` to the FIFO raster task runner—queued **behind** `CreateImpellerContext`—and blocks the UI thread on a latch (`latch.Wait()`), calling `NotifyCreated()` inside `FlutterEngineRunInitialized` forces the UI thread to wait for `SetupImpellerContext()` to finish anyway, and creates a premature surface before Android's `SurfaceHolder.Callback.surfaceCreated` has provided an `ANativeWindow`. #### Backend Selection and Background Vulkan Probe Before `FlutterEngineInitialize` In [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L62-L63) and [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L388-L389), `FlutterLoader.startInitialization()` and `FlutterLoader.ensureInitializationComplete()` are called on consecutive lines on the UI thread unless the host application pre-warms `FlutterLoader` in `Application.onCreate`. Joining the Vulkan probe inside `ensureInitializationComplete()` would therefore block the UI thread immediately with zero overlap. -Instead, the embedder follows the same pattern [`FlutterLoader.initTask`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L278) uses for `prefetchDefaultFontManager`: as soon as `flutterJNI.loadLibrary(appContext)` completes on `initTask`, `FlutterLoader` submits the process-wide two-stage Vulkan probe as a separate task on `executorService` that `ensureInitializationComplete()` does **not** wait on. The embedder only joins the probe future right before constructing `FlutterRendererConfig` for `FlutterEngineInitialize`, overlapping the probe with `FlutterMain.init`, `FlutterEngine` Java subsystem construction, plugin registration, and initial channel setup: +Instead, the embedder follows the same pattern [`FlutterLoader.initTask`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L197-L291) uses for `prefetchDefaultFontManager`: as soon as `flutterJNI.loadLibrary(appContext)` completes on `initTask`, `FlutterLoader` submits the process-wide two-stage Vulkan probe as a separate task on `executorService` that `ensureInitializationComplete()` does **not** wait on. The embedder only joins the probe future right before constructing `FlutterRendererConfig` for `FlutterEngineInitialize`, overlapping the probe with `FlutterMain.init`, `FlutterEngine` Java subsystem construction, plugin registration, and initial channel setup: - **Stage 1 (Android system-property and SoC deny-list)**: Before loading `libvulkan.so`, the Android embedder runs the checks from [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120): known emulators, Huawei devices (`ro.com.google.clientidbase`), MediaTek devices with `ro.vendor.build.version.sdk < 32`, and the `ro.product.board` deny-list ([`IsKnownBadSOC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L68-L77)). Matching devices select `kOpenGL` immediately without loading Vulkan. -- **Stage 2 (Impeller known-bad driver query via `embedder.h`)**: If Stage 1 passes, the background task loads Vulkan, selects a physical device, and reads `VkPhysicalDeviceProperties`. To check those properties against Impeller's [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377) while keeping the driver blocklist in one place inside Impeller, `embedder.h` adds a stateless query function: +- **Stage 2 (Impeller known-bad driver query via [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h))**: If Stage 1 passes, the background task loads Vulkan, selects a physical device, and reads `VkPhysicalDeviceProperties`. To check those properties against Impeller's [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377) while keeping the driver blocklist in one place inside Impeller, `embedder.h` adds a stateless query function: ```c typedef struct { @@ -338,17 +340,17 @@ FlutterEngineResult FlutterEngineQueryVulkanDriverSupport( bool* out_is_known_bad); ``` -The embedder initializes `is_known_bad = true` before calling `FlutterEngineQueryVulkanDriverSupport`. If the call fails or leaves `is_known_bad` set to `true` (unless Vulkan was explicitly forced in a non-release build), or if `VkDevice` creation fails, the embedder tears down its Vulkan handles and falls back to `kOpenGL`. Because the probed `VkInstance` and `VkDevice` are held by a process-wide singleton (`VulkanDeviceOwner`), subsequent `FlutterEngine` instances reuse the initialized handles with zero probe cost. +The embedder initializes `is_known_bad = true` before calling `FlutterEngineQueryVulkanDriverSupport`. If the call fails or leaves `is_known_bad` set to `true` (unless Vulkan was explicitly forced in a non-release build), or if `VkDevice` creation fails, the embedder tears down its Vulkan handles and falls back to `kOpenGL`. Because the probed `VkInstance` and `VkDevice` are held by a process-wide singleton (`VulkanDeviceOwner`), subsequent [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) instances reuse the initialized handles with zero probe cost. #### Deferring `impeller::Context` and Surface Creation in `shell/platform/embedder/` (`SetupImpellerContext`) -To keep `impeller::ContextVK::Create` and `impeller::ContextGLES::Create` on the raster thread instead of running them in the `EmbedderSurface` constructors on the UI thread, and to prevent `FlutterEngineRunInitialized` from blocking on `PlatformView::NotifyCreated()` before a window surface exists, `shell/platform/embedder/` is updated as follows: +To keep [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L106) and [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L27-L30) on the raster thread instead of running them in the [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) constructors on the UI thread, and to prevent [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) from blocking on [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) before a window surface exists, [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder) is updated as follows: - [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) adds `virtual void SetupImpellerContext() {}`. - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) overrides `PlatformView::SetupImpellerContext()` and forwards the call to `embedder_surface_->SetupImpellerContext()`. - [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L20-L95) validates the `VulkanDispatchTable` callbacks and stores `impeller::ContextVK::Settings` (including `EmbedderData` handles and extension lists) in its constructor without creating the Impeller context. It overrides `SetupImpellerContext()` to run `context_ = impeller::ContextVK::Create(std::move(settings_))` on the raster thread when `Shell::CreateWithSnapshot` executes the `CreateImpellerContext` task. - [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L77-L132) likewise validates `gl_dispatch_table_` in its constructor and moves `gl_make_current_callback()`, `ProcTableGLES` creation, `impeller::ContextGLES::Create(...)`, and `gl_clear_current_callback()` into `EmbedderSurfaceGLImpeller::SetupImpellerContext()` on the raster thread. -- [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) records `SAFE_ACCESS(args, defer_surface_creation, false)` during [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2549-L2563). When `defer_surface_creation` is `true`, [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2566-L2600) executes `LaunchShell()` and `RunRootIsolate()` and skips the implicit `NotifyCreated()` call, leaving surface creation to `FlutterEngineNotifyCreated`. +- [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) records `SAFE_ACCESS(args, defer_surface_creation, false)` during [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2023-L2499). When `defer_surface_creation` is `true`, [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2501-L2537) executes `LaunchShell()` and `RunRootIsolate()` and skips the implicit `NotifyCreated()` call, leaving surface creation to `FlutterEngineNotifyCreated`. ```cpp // In shell/platform/embedder/platform_view_embedder.cc @@ -482,7 +484,7 @@ Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/ref Static graphics configuration (the Vulkan device and queue handles, or the EGL context and callbacks) persists in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) for the lifetime of the engine, while presentation surfaces attach and detach across `Activity` lifecycle events. Recreating `FlutterRendererConfig` on every window change would re-initialize the GPU context and discard Impeller pipeline caches. -In the legacy architecture, `PlatformViewAndroid` called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L201) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L233) directly as a `PlatformView` subclass only when an `ANativeWindow` was attached or detached. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) adds `bool defer_surface_creation` (instructing `FlutterEngineRunInitialized` not to call `NotifyCreated` prematurely when no window surface exists yet) and two surface lifecycle functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): +In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89) directly as a `PlatformView` subclass only when an `ANativeWindow` was attached or detached. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) adds `bool defer_surface_creation` (instructing `FlutterEngineRunInitialized` not to call `NotifyCreated` prematurely when no window surface exists yet) and two surface lifecycle functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): ```c // In FlutterProjectArgs: @@ -542,7 +544,7 @@ FlutterEngineResult FlutterEngineNotifyDestroyed( ``` > [!NOTE] **New Surface Lifecycle Exports** -> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are new additions to `embedder.h` exposing `EmbedderEngine::NotifyCreated` and `EmbedderEngine::NotifyDestroyed` (`PlatformView::NotifyCreated` and `PlatformView::NotifyDestroyed`). Setting `FlutterProjectArgs.defer_surface_creation = true` prevents `FlutterEngineRunInitialized` from calling `NotifyCreated` before the first `SurfaceHolder.Callback.surfaceCreated` event. +> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are new additions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) exposing [`EmbedderEngine::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.cc#L124-L131) and [`EmbedderEngine::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.cc#L133-L140) ([`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89)). Setting `FlutterProjectArgs.defer_surface_creation = true` prevents [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) from calling [`NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) before the first `SurfaceHolder.Callback.surfaceCreated` event. #### Lifecycle Mapping and Synchronous Destruction Contract @@ -554,7 +556,7 @@ These functions map to Android [`SurfaceHolder.Callback`](https://developer.andr #### Alternative Considered: Splitting Startup into Separate Shell and Isolate Launch APIs -An alternative design was prototyped that split `FlutterEngineRunInitialized` into separate shell-creation and isolate-launch C APIs so that `FlutterJNI.attachToNative()` could create the `Shell` before `DartExecutor.executeDartEntrypoint()` (`FlutterJNI.runBundleAndSnapshotFromLibrary()`) specified the Dart entrypoint: +An alternative design was prototyped that split [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) into separate shell-creation and isolate-launch C APIs so that [`FlutterJNI.attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) could create the [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) before [`DartExecutor.executeDartEntrypoint()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java#L133-L143) ([`FlutterJNI.runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)) specified the Dart entrypoint: ```c // Alternative C-API additions (not adopted): @@ -583,22 +585,22 @@ FlutterEngineResult FlutterEngineLaunchEngine( const FlutterEngineRunConfiguration* config); ``` -Under this alternative, `attachToNative()` called `FlutterEngineInitialize` (with `defer_root_isolate_launch = true` and `initial_lifecycle_state = "AppLifecycleState.detached"`) followed by `FlutterEngineLaunchShell`, and `runBundleAndSnapshotFromLibrary()` later called `FlutterEngineLaunchEngine` with `FlutterEngineRunConfiguration`. The motivation was to mirror legacy `AndroidShellHolder` startup ordering so that platform messages (`flutter/localization`, `flutter/navigation` `setInitialRoute`, `flutter/settings`), viewport metrics, and external textures sent between `attachToNative()` and `executeDartEntrypoint()` could be buffered directly inside the engine's `RuntimeController`, and so `Shell::CreateWithSnapshot` could post `CreateImpellerContext` to the raster thread during `attachToNative()`. +Under this alternative, [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) called [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) (with `defer_root_isolate_launch = true` and `initial_lifecycle_state = "AppLifecycleState.detached"`) followed by `FlutterEngineLaunchShell`, and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) later called `FlutterEngineLaunchEngine` with `FlutterEngineRunConfiguration`. The motivation was to mirror legacy [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) startup ordering so that platform messages (`flutter/localization`, `flutter/navigation` `setInitialRoute`, `flutter/settings`), viewport metrics, and external textures sent between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`executeDartEntrypoint()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java#L133-L143) could be buffered directly inside the engine's [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52), and so [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) could post [`CreateImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L25) to the raster thread during [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442). -This alternative was rejected in favor of keeping the existing two-phase `FlutterEngineInitialize` and `FlutterEngineRunInitialized` sequence (paired with `defer_surface_creation` and `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`) for four reasons: +This alternative was rejected in favor of keeping the existing two-phase [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) sequence (paired with `defer_surface_creation` and `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`) for four reasons: -1. **Avoids a fragile four-stage initialization state machine in `embedder.h`**: `embedder.h` already separates startup into `FlutterEngineInitialize` and `FlutterEngineRunInitialized` so embedders can obtain the `FlutterEngine` handle and bind custom task runners before any tasks are posted. Splitting startup further into `FlutterEngineInitialize` → `FlutterEngineLaunchShell` → `FlutterEngineLaunchEngine` → `FlutterEngineNotifyCreated` introduces intermediate partially-initialized `EmbedderEngine` states, duplicates `FlutterProjectArgs` fields (`assets_path`, `custom_dart_entrypoint`, `dart_entrypoint_argv`, `custom_asset_resolvers`) into `FlutterEngineRunConfiguration`, and requires seeding `initial_lifecycle_state` to keep the shell from scheduling frames before an isolate exists. -2. **Preserves a larger overlap window for the background Vulkan probe**: Because `FlutterRendererConfig` requires a resolved backend (`kVulkan` or `kOpenGL`) and live `VkInstance`/`VkDevice` handles when `FlutterEngineInitialize` is called, invoking `FlutterEngineInitialize` earlier in `attachToNative()` forces the UI thread to join the background `FlutterLoader` Vulkan probe earlier. Calling `FlutterEngineInitialize` and `FlutterEngineRunInitialized` together in `runBundleAndSnapshotFromLibrary()` allows the background Vulkan probe to overlap with `FlutterEngine` Java subsystem construction, plugin registration, and initial system channel configuration. -3. **Pre-run buffering is self-contained in `FlutterEmbedderNative`**: Queueing platform messages, initial route, viewport metrics, and external texture registrations in `FlutterEmbedderNative` between `attachToNative()` and `runBundleAndSnapshotFromLibrary()`—and flushing them immediately after `FlutterEngineRunInitialized`—requires no new `embedder.h` entry points or `RuntimeController` lifecycle flags. -4. **`defer_surface_creation` already moves `SetupImpellerContext()` off the UI thread**: Because `runBundleAndSnapshotFromLibrary()` executes during `Activity.onCreate` / `onStart` well before WindowManager dispatches `SurfaceHolder.Callback.surfaceCreated`, skipping the implicit `NotifyCreated()` call inside `FlutterEngineRunInitialized` already gives the raster thread the entire window between `runBundleAndSnapshotFromLibrary()` and `surfaceCreated()` to complete `SetupImpellerContext()` asynchronously. +1. **Avoids a fragile four-stage initialization state machine in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)**: [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) already separates startup into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) so embedders can obtain the [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and bind custom task runners before any tasks are posted. Splitting startup further into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) → `FlutterEngineLaunchShell` → `FlutterEngineLaunchEngine` → `FlutterEngineNotifyCreated` introduces intermediate partially-initialized [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) states, duplicates [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`assets_path`, [`custom_dart_entrypoint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2687), [`dart_entrypoint_argv`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2765), `custom_asset_resolvers`) into `FlutterEngineRunConfiguration`, and requires seeding `initial_lifecycle_state` to keep the shell from scheduling frames before an isolate exists. +2. **Preserves a larger overlap window for the background Vulkan probe**: Because [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1042-L1055) requires a resolved backend (`kVulkan` or `kOpenGL`) and live `VkInstance`/`VkDevice` handles when [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) is called, invoking [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) earlier in [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) forces the UI thread to join the background [`FlutterLoader`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L41) Vulkan probe earlier. Calling [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) together in [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) allows the background Vulkan probe to overlap with [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) Java subsystem construction, plugin registration, and initial system channel configuration. +3. **Pre-run buffering is self-contained in `FlutterEmbedderNative`**: Queueing platform messages, initial route, viewport metrics, and external texture registrations in `FlutterEmbedderNative` between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)—and flushing them immediately after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007)—requires no new [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) entry points or [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) lifecycle flags. +4. **`defer_surface_creation` already moves [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) off the UI thread**: Because [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) executes during `Activity.onCreate` / `onStart` well before WindowManager dispatches `SurfaceHolder.Callback.surfaceCreated`, skipping the implicit [`NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) call inside [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) already gives the raster thread the entire window between [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) and `surfaceCreated()` to complete [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) asynchronously. -| Dimension | Proposed: Two-Phase Init (`FlutterEngineInitialize` + `FlutterEngineRunInitialized` + `defer_surface_creation`) | Alternative: Split Shell & Isolate Launch (`FlutterEngineLaunchShell` + `FlutterEngineLaunchEngine`) | +| Dimension | Proposed: Two-Phase Init ([`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) + [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) + `defer_surface_creation`) | Alternative: Split Shell & Isolate Launch (`FlutterEngineLaunchShell` + `FlutterEngineLaunchEngine`) | | :--- | :--- | :--- | -| **Public C-API Surface (`embedder.h`)** | **Pro:** Reuses existing `FlutterEngineInitialize` and `FlutterEngineRunInitialized`; adds only `bool defer_surface_creation` on `FlutterProjectArgs` plus `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`. | **Con:** Adds two new functions (`FlutterEngineLaunchShell`, `FlutterEngineLaunchEngine`), a new `FlutterEngineRunConfiguration` struct duplicating `FlutterProjectArgs` fields, and two `FlutterProjectArgs` fields (`defer_root_isolate_launch`, `initial_lifecycle_state`). | -| **`EmbedderEngine` State Machine** | **Pro:** Preserves the existing two-state model (initialized → running) used by all desktop embedders and `FlutterEngineSpawn`. | **Con:** Introduces a four-stage startup state machine (initialized → shell launched without isolate → isolate launched → surface created) that must also be mirrored in `FlutterEngineSpawnInfo`. | -| **Background Vulkan Probe Overlap (`VkInstance` + `VkDevice`)** | **Pro:** Longer overlap window—the UI thread does not join the `FlutterLoader` Vulkan probe until `runBundleAndSnapshotFromLibrary()`, overlapping the probe with `FlutterEngine` Java subsystem construction, plugin registration, and initial channel setup. | **Con:** Shorter overlap window—calling `FlutterEngineInitialize` inside `attachToNative()` forces the UI thread to join the Vulkan probe before `FlutterEngine` Java construction and plugin registration run. | -| **Raster-Thread `SetupImpellerContext()` Overlap (`ContextVK` / `ContextGLES`)** | **Pro:** Runs asynchronously on the raster thread across the entire `runBundleAndSnapshotFromLibrary()` → `surfaceCreated()` window without blocking the UI thread. | **Pro:** Starts slightly earlier (at `attachToNative()` rather than `runBundleAndSnapshotFromLibrary()`), though both complete well before `surfaceCreated()`. | -| **Pre-Run State (`flutter/localization`, `setInitialRoute`, `flutter/settings`, Textures)** | **Con:** `FlutterEmbedderNative` must queue messages, viewport metrics, and external textures sent between `attachToNative()` and `runBundleAndSnapshotFromLibrary()` and flush them right after `FlutterEngineRunInitialized`.
**Pro:** Keeps pre-run queueing internal to the Android embedder without exposing `RuntimeController` state in `embedder.h`. | **Pro:** `Shell` and `RuntimeController` exist at `attachToNative()`, so pre-run platform messages and viewport metrics can be dispatched directly into the engine before the isolate launches.
**Con:** Requires `initial_lifecycle_state = "AppLifecycleState.detached"` in `FlutterProjectArgs` to prevent premature frame scheduling while the shell has no isolate. | +| **Public C-API Surface ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h))** | **Pro:** Reuses existing [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007); adds only `bool defer_surface_creation` on [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) plus `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`. | **Con:** Adds two new functions (`FlutterEngineLaunchShell`, `FlutterEngineLaunchEngine`), a new `FlutterEngineRunConfiguration` struct duplicating [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields, and two [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`defer_root_isolate_launch`, `initial_lifecycle_state`). | +| **[`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) State Machine** | **Pro:** Preserves the existing two-state model (initialized → running) used by all desktop embedders and `FlutterEngineSpawn`. | **Con:** Introduces a four-stage startup state machine (initialized → shell launched without isolate → isolate launched → surface created) that must also be mirrored in `FlutterEngineSpawnInfo`. | +| **Background Vulkan Probe Overlap (`VkInstance` + `VkDevice`)** | **Pro:** Longer overlap window—the UI thread does not join the [`FlutterLoader`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L41) Vulkan probe until [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089), overlapping the probe with [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) Java subsystem construction, plugin registration, and initial channel setup. | **Con:** Shorter overlap window—calling [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) inside [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) forces the UI thread to join the Vulkan probe before [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) Java construction and plugin registration run. | +| **Raster-Thread [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) Overlap ([`ContextVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L64) / [`ContextGLES`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L23))** | **Pro:** Runs asynchronously on the raster thread across the entire [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) → `surfaceCreated()` window without blocking the UI thread. | **Pro:** Starts slightly earlier (at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) rather than [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)), though both complete well before `surfaceCreated()`. | +| **Pre-Run State (`flutter/localization`, `setInitialRoute`, `flutter/settings`, Textures)** | **Con:** `FlutterEmbedderNative` must queue messages, viewport metrics, and external textures sent between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) and flush them right after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007).
**Pro:** Keeps pre-run queueing internal to the Android embedder without exposing [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) state in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). | **Pro:** [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) and [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) exist at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442), so pre-run platform messages and viewport metrics can be dispatched directly into the engine before the isolate launches.
**Con:** Requires `initial_lifecycle_state = "AppLifecycleState.detached"` in [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) to prevent premature frame scheduling while the shell has no isolate. | ### Replacing Internal [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) with `FlutterCompositor` (Preserving Hybrid Composition) @@ -606,12 +608,12 @@ Android supports four platform view composition modes: 1. **Hybrid Composition (HC)**: Places the native Android [`View`](https://developer.android.com/reference/android/view/View) directly in the view hierarchy (`createForPlatformViewLayer`) and composites Flutter UI layers above the platform view into Android overlay surfaces (`FlutterImageView` / `PlatformViewWrapper` via `createOverlaySurface` and `onDisplayOverlaySurface`). 2. **Hybrid Composition++ (HCPP)**: Uses NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU acquire/release sync fences on Android 14+ (API level 34+) (`createPlatformViewHcpp`). -3. **Texture Layer Hybrid Composition (TLHC)**: Attaches the Android `View` to the view hierarchy (`PlatformViewWrapper`) while redirecting its draw output into an offscreen [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) or [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) render target registered as a Flutter external texture (`createForTextureLayer` / `configureForTextureLayerComposition`). +3. **Texture Layer Hybrid Composition (TLHC)**: Attaches the Android `View` to the view hierarchy ([`PlatformViewWrapper`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L40)) while redirecting its draw output into an offscreen [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) or [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) render target registered as a Flutter external texture (`createForTextureLayer` / `configureForTextureLayerComposition`). 4. **Virtual Display (VD)**: Hosts the Android `View` inside an offscreen [`VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay) (`VirtualDisplayController`) backed by a `TextureRegistry` render target (`SurfaceProducer` or `SurfaceTexture`) and composites its output as a Flutter external texture (`createForTextureLayer` / `configureForVirtualDisplay`). -**All four platform view composition modes are preserved; retiring Hybrid Composition (HC) or Virtual Display (VD) is not an option.** +**All four platform view composition modes are preserved; retiring Hybrid Composition (HC) or Virtual Display (VD) is not an option.** -Standard Hybrid Composition and Virtual Display remain required across Android devices, API levels, and embedded view hierarchies (such as views containing `SurfaceView` or `TextureView` children where `PlatformViewsController` falls back from TLHC to Virtual Display). What this migration eliminates is **only the internal C++ `flutter::ExternalViewEmbedder` subclass (`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`) and its direct dependencies on `//flutter/flow` (`embedded_views.h`) and `fml::RasterThreadMerger`**, rather than any platform view composition mode. +Standard Hybrid Composition and Virtual Display remain required across Android devices, API levels, and embedded view hierarchies (such as views containing `SurfaceView` or `TextureView` children where [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L54) falls back from TLHC to Virtual Display). What this migration eliminates is **only the internal C++ [`flutter::ExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L432) subclass ([`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29)) and its direct dependencies on [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h)) and [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31)**, rather than any platform view composition mode. In the legacy architecture, [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) subclassed `flutter::ExternalViewEmbedder` directly and used [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) in [`PostPrerollAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213) to dynamically lease and merge the raster thread onto the platform thread during HC frames: @@ -637,12 +639,12 @@ PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( #### How All Four Platform View Modes Work in the C-API Architecture -At the C-API boundary (`embedder.h`), the four composition modes partition into two external-texture modes and two compositor-layer modes: +At the C-API boundary ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)), the four composition modes partition into two external-texture modes and two compositor-layer modes: -- **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L652) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanTextureFrameCallback`, and `FlutterOpenGLTextureFrameCallback`) without creating `FlutterPlatformView` layers. -- **Hybrid Composition (HC) and Hybrid Composition++ (HCPP) via `FlutterCompositor`**: The engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2117) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). `AndroidCompositor` and `AndroidPlatformViewsController` drive both HC and HCPP over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: +- **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L643-L685) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanTextureFrameCallback`, and `FlutterOpenGLTextureFrameCallback`) without creating `FlutterPlatformView` layers. +- **Hybrid Composition (HC) and Hybrid Composition++ (HCPP) via [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322)**: The engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). `AndroidCompositor` and `AndroidPlatformViewsController` drive both HC and HCPP over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: -1. **Backing Store Allocation (`create_backing_store_callback`)**: `FlutterBackingStoreConfig` is extended with `bool is_root_layer;` (and `FlutterCompositor.avoid_backing_store_cache` is set to `true` because the engine's render-target cache keys only on size). When `config->is_root_layer` is `true`—meaning the layer is the bottom-most layer of the frame for `view_id` with no platform view composited beneath it—`AndroidCompositor::CreateBackingStore` returns the main surface render target (`GetFBO()` on OpenGL ES, or the primary swapchain image on Vulkan). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above a platform view (`is_root_layer == false`), `CreateBackingStore` allocates an offscreen backing store (`AcquireOffscreenFBO` / overlay `AHardwareBuffer`): +1. **Backing Store Allocation ([`create_backing_store_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2284))**: [`FlutterBackingStoreConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2160-L2168) is extended with `bool is_root_layer;` (and [`FlutterCompositor.avoid_backing_store_cache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2312) is set to `true` because the engine's render-target cache keys only on size). When `config->is_root_layer` is `true`—meaning the layer is the bottom-most layer of the frame for `view_id` with no platform view composited beneath it—`AndroidCompositor::CreateBackingStore` returns the main surface render target (`GetFBO()` on OpenGL ES, or the primary swapchain image on Vulkan). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above a platform view (`is_root_layer == false`), `CreateBackingStore` allocates an offscreen backing store (`AcquireOffscreenFBO` / overlay `AHardwareBuffer`): ```c typedef struct { @@ -667,35 +669,35 @@ At the C-API boundary (`embedder.h`), the four composition modes partition into bool is_root_layer; } FlutterBackingStoreConfig; ``` -2. **Layer and Overlay Presentation (`present_layers_callback` / `present_view_callback`)**: `AndroidCompositor::PresentLayers` begins the platform view frame (`OnBeginFrame`), iterates through the ordered `FlutterLayer` array, and dispatches each layer by type: - - The base `kFlutterLayerContentTypeBackingStore` is presented to the primary `ANativeWindow`. - - Each `kFlutterLayerContentTypePlatformView` layer forwards the view identifier, physical bounds (`offset`, `size`), and serialized `FlutterPlatformViewMutation` stack to `AndroidPlatformViewsController::OnDisplayPlatformView` (`PlatformViewsController.onDisplayPlatformView` in Java). - - Any `kFlutterLayerContentTypeBackingStore` layer positioned above a platform view acquires an Android overlay surface (`createOverlaySurface` / `GetOverlayWindow`), blits and swaps the offscreen FBO into that overlay window (`BlitAndSwapOverlaySurface`), and updates the overlay geometry (`onDisplayOverlaySurface` / `showOverlaySurface`), followed by `OnEndFrame`. -3. **Removing `external_view_embedder/`**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting and HCPP `SurfaceControl` transactions through `FlutterCompositor` (while TLHC and VD operate through the C-API external texture pipeline) and capture per-frame layer and mutator state by value across threads, `shell/platform/android/external_view_embedder/` (`AndroidExternalViewEmbedder` and `AndroidExternalViewEmbedder2`) is deleted in Phase 4 without removing or degrading any of the four platform view modes. No `fml::RasterThreadMerger` handles or internal flow types are exposed in `embedder.h`. + +2. **Layer and Overlay Presentation ([`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2302) / [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321))**: `AndroidCompositor::PresentLayers` begins the platform view frame (`OnBeginFrame`), iterates through the ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) array, and dispatches each layer by type: + - The base [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173) is presented to the primary `ANativeWindow`. + - Each [`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175) layer forwards the view identifier, physical bounds (`offset`, `size`), and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) stack to `AndroidPlatformViewsController::OnDisplayPlatformView` ([`PlatformViewsController.onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289) in Java). + - Any [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173) layer positioned above a platform view acquires an Android overlay surface ([`createOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1402-L1425) / `GetOverlayWindow`), blits and swaps the offscreen FBO into that overlay window (`BlitAndSwapOverlaySurface`), and updates the overlay geometry ([`onDisplayOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1291-L1309) / `showOverlaySurface`), followed by `OnEndFrame`. +3. **Removing [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder)**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting and HCPP `SurfaceControl` transactions through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) (while TLHC and VD operate through the C-API external texture pipeline) and capture per-frame layer and mutator state by value across threads, [`shell/platform/android/external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) ([`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34)) is deleted in Phase 4 without removing or degrading any of the four platform view modes. No [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) handles or internal flow types are exposed in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). #### Frame Synchronization on API 21–33 Without `fml::RasterThreadMerger` On Android 5.0 through Android 13 (API levels 21–33), HCPP is unavailable because joining the Android View system's `SurfaceControl.Transaction` via NDK [`ASurfaceTransaction_fromJava`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_fromjava) requires API 34 (even though [`ASurfaceTransaction_setBuffer`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_setbuffer) itself was added in API 29). Two runtime paths can still prevent a platform view from using Texture Layer Hybrid Composition (TLHC) on these API levels: -1. **Explicit Hybrid Composition (`initExpensiveAndroidView`)**: The plugin requests `createForPlatformViewLayer`, routing directly to [`PlatformViewsController.configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L583). -2. **Embedded Child `SurfaceView` or `TextureView` (`VIEW_TYPES_REQUIRE_NON_TLHC`)**: When a plugin uses `initAndroidView` (`createForTextureLayer`) and the embedded Android `View` hierarchy contains a `SurfaceView` or `TextureView` child whose out-of-band buffer queue bypasses `ViewParent.onDescendantInvalidated()`, [`PlatformViewsController.createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L229-L246) detects `!supportsTextureLayerMode` and branches by `request.displayMode`: - - **`TEXTURE_WITH_VIRTUAL_FALLBACK` (Virtual Display / VD)**: Calls `configureForVirtualDisplay`, hosting the view and its child `SurfaceView` inside an offscreen `VirtualDisplay` backed by a `TextureRegistry` render target. Frame synchronization is handled entirely on the GPU/raster thread as a standard C-API external texture (`FlutterEngineRegisterExternalTexture`). - - **`TEXTURE_WITH_HYBRID_FALLBACK` (Hybrid Composition / HC)**: Calls `configureForHybridComposition` and returns `NON_TEXTURE_FALLBACK` (`-2`), instructing the Dart framework to composite the view via a `PlatformViewLayer` (`kFlutterLayerContentTypePlatformView`). - -When Hybrid Composition is active on API 21–33 without `fml::RasterThreadMerger`, frame synchronization between the Flutter rasterizer and the Android `View` hierarchy is enforced through the following three mechanisms across `AndroidCompositor` and `PlatformViewsController`: - -1. **`FlutterImageView` (`ImageReader`) Target Conversion**: - Rendering the base Flutter layer into a `FlutterSurfaceView` (`SurfaceView`) during HC would present to `SurfaceFlinger` out of band from the Android `ViewRootImpl` UI traversal, causing visual lag against the `FlutterMutatorView`. When the first HC platform view is displayed (`onDisplayPlatformView`), `PlatformViewsController.initializeRootImageViewIfNeeded()` calls `flutterView.convertToImageView()`, switching the engine's primary `ANativeWindow` to an [`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)-backed `FlutterImageView`. Each overlay layer (`PlatformOverlayView`) is likewise backed by its own `ImageReader` `ANativeWindow`. -2. **One Platform-Thread Task per Frame and Raster-Thread Wait for `onEndFrame()`**: - In the legacy architecture, `AndroidExternalViewEmbedder` merged the raster thread onto the platform thread via `fml::RasterThreadMerger` so that `onBeginFrame`, base/overlay rendering and swaps in `SubmitFlutterView`, inline `onDisplayPlatformView` and `onDisplayOverlaySurface` calls, and `onEndFrame` all executed inside one platform-thread task. `AndroidCompositor::PresentLayers` preserves that ordering without `fml::RasterThreadMerger`: - - On the raster thread, `surface_manager_->Present()` and `surface_manager_->BlitAndSwapOverlaySurface(...)` first complete `eglSwapBuffers` / `vkQueuePresentKHR` into the root `FlutterImageView` and `PlatformOverlayView` `ImageReader` `ANativeWindow` queues for frame N. - - `AndroidCompositor` captures frame N's platform view geometry (`offset`, `size`), value-copied `AndroidMutatorsStack`, and overlay rectangles by value and posts a single task (`PostTask`) to the platform thread that executes `onBeginFrame`, every `onDisplayPlatformView`, every `onDisplayOverlaySurface`, and `onEndFrame` in order. Because the Android UI `Looper` runs one message at a time and `ViewRootImpl` schedules view traversals in a separate `Choreographer.CALLBACK_TRAVERSAL` callback, no Android view traversal can run mid-task and observe a partially applied frame (the same single-`PostTask` pattern used by [`AndroidExternalViewEmbedder2::SubmitFlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc#L190-L229)). This is purely a CPU-side `Looper` ordering mechanism; it does not use `synchronization_fence_fd`, which exists only on the HCPP `SurfaceControl.Transaction` path. - - When `platform_views_count > 0` and HCPP is not active (`!IsHcppEnabled()`), if `PresentLayers` was called on the raster thread (`!platform_runner->RunsTasksOnCurrentThread()`), the raster thread waits on a frame-completion latch (`fml::AutoResetWaitableEvent`) until the platform thread finishes `onEndFrame()` (polling a surface-attached flag that the platform thread clears before calling `FlutterEngineNotifyDestroyed` so teardown never deadlocks). Waiting on this latch is required because `onEndFrame()` calls `ImageReader.acquireLatestImage()`, which drops older queued images; without the wait, the raster thread could queue frame N+1 before the platform thread runs frame N's task, pairing frame N+1's pixels with frame N's `FlutterMutatorView` geometry. - - Two mid-frame steps still perform a synchronous hop to the platform thread: allocating a new overlay surface (`createOverlaySurface`), and the first frame that displays an HC view (`initializeRootImageViewIfNeeded` calling `convertToImageView`), where the root layer is blitted from an offscreen backing store into the newly created `FlutterImageView` `ImageReader` surface. -3. **`ImageReader` Gate in `PlatformViewsController.onEndFrame()`**: +1. **Explicit Hybrid Composition ([`initExpensiveAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L194-L210))**: The plugin requests [`createForPlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L168-L177), routing directly to [`PlatformViewsController.configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L581-L585). +2. **Embedded Child `SurfaceView` or `TextureView` ([`VIEW_TYPES_REQUIRE_NON_TLHC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L68))**: When a plugin uses [`initAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L134-L152) ([`createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L194-L247)) and the embedded Android `View` hierarchy contains a `SurfaceView` or `TextureView` child whose out-of-band buffer queue bypasses `ViewParent.onDescendantInvalidated()`, [`PlatformViewsController.createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L194-L247) detects `!supportsTextureLayerMode` and branches by `request.displayMode`: + - **[`TEXTURE_WITH_VIRTUAL_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.java#L24) (Virtual Display / VD)**: Calls [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596-L638), hosting the view and its child `SurfaceView` inside an offscreen `VirtualDisplay` backed by a [`TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L19) render target. Frame synchronization is handled entirely on the GPU/raster thread as a standard C-API external texture ([`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227-L3229)). + - **[`TEXTURE_WITH_HYBRID_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.java#L28) (Hybrid Composition / HC)**: Calls [`configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L581-L585) and returns [`NON_TEXTURE_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java#L316) (`-2`), instructing the Dart framework to composite the view via a [`PlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/platform_view_layer.h#L13) ([`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175)). + +When Hybrid Composition is active on API 21–33 without [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31), frame synchronization between the Flutter rasterizer and the Android `View` hierarchy is enforced through the following three mechanisms across `AndroidCompositor` and [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L54): + +1. **[`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) (`ImageReader`) Target Conversion**: + Rendering the base Flutter layer into a [`FlutterSurfaceView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java#L35) (`SurfaceView`) during HC would present to `SurfaceFlinger` out of band from the Android `ViewRootImpl` UI traversal, causing visual lag against the [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32). When the first HC platform view is displayed ([`onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289)), [`PlatformViewsController.initializeRootImageViewIfNeeded()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1174-L1190) calls [`flutterView.convertToImageView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1397), switching the engine's primary `ANativeWindow` to an [`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)-backed `FlutterImageView`. Each overlay layer (`PlatformOverlayView`) is likewise backed by its own `ImageReader` `ANativeWindow`. +2. **One Platform-Thread Task per Frame and Raster-Thread Wait for [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347)**: + In the legacy architecture, [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) merged the raster thread onto the platform thread via [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) so that [`onBeginFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1311-L1319), base/overlay rendering and swaps in [`SubmitFlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc#L89-L229), inline [`onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289) and [`onDisplayOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1291-L1309) calls, and [`onEndFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) all executed inside one platform-thread task. `AndroidCompositor::PresentLayers` preserves that ordering without [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31): + - On the raster thread, `surface_manager_->Present()` and `surface_manager_->BlitAndSwapOverlaySurface(...)` first complete `eglSwapBuffers` / `vkQueuePresentKHR` into the root [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) and [`PlatformOverlayView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformOverlayView.java#L15) `ImageReader` `ANativeWindow` queues for frame N. + - `AndroidCompositor` captures frame N's platform view geometry (`offset`, `size`), value-copied `AndroidMutatorsStack`, and overlay rectangles by value and posts a single task (`PostTask`) to the platform thread that executes [`onBeginFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1311-L1319), every [`onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289), every [`onDisplayOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1291-L1309), and [`onEndFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) in order. Because the Android UI `Looper` runs one message at a time and `ViewRootImpl` schedules view traversals in a separate `Choreographer.CALLBACK_TRAVERSAL` callback, no Android view traversal can run mid-task and observe a partially applied frame (the same single-`PostTask` pattern used by [`AndroidExternalViewEmbedder2::SubmitFlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc#L89-L229)). This is purely a CPU-side `Looper` ordering mechanism; it does not use `synchronization_fence_fd`, which exists only on the HCPP `SurfaceControl.Transaction` path. + - When `platform_views_count > 0` and HCPP is not active (`!IsHcppEnabled()`), if `PresentLayers` was called on the raster thread (`!platform_runner->RunsTasksOnCurrentThread()`), the raster thread waits on a frame-completion latch ([`fml::AutoResetWaitableEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/synchronization/waitable_event.h#L28)) until the platform thread finishes [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) (polling a surface-attached flag that the platform thread clears before calling `FlutterEngineNotifyDestroyed` so teardown never deadlocks). Waiting on this latch is required because [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) calls `ImageReader.acquireLatestImage()`, which drops older queued images; without the wait, the raster thread could queue frame N+1 before the platform thread runs frame N's task, pairing frame N+1's pixels with frame N's [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) geometry. + - Two mid-frame steps still perform a synchronous hop to the platform thread: allocating a new overlay surface ([`createOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1402-L1425)), and the first frame that displays an HC view ([`initializeRootImageViewIfNeeded`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1174-L1190) calling [`convertToImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1397)), where the root layer is blitted from an offscreen backing store into the newly created [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) `ImageReader` surface. +3. **`ImageReader` Gate in [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347)**: During [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391) on the platform thread, `flutterView.acquireLatestImageViewFrame()` and `overlayView.acquireLatestImage()` pull the newly queued GPU buffers and invalidate the `FlutterImageView` instances. `finishFrame()` checks `isFrameRenderedUsingImageReaders` (verifying that the converted root `FlutterImageView` and every active `PlatformOverlayView` acquired a valid image) before setting `parentView.setVisibility(View.VISIBLE)`. Consequently, the base `FlutterImageView`, the `FlutterMutatorView` (wrapping the native `View`), and all `PlatformOverlayView` layers are drawn together in a single `ViewRootImpl.performTraversals()` pass on the Android UI thread. When the last HC view is disposed (`currentFrameUsedPlatformViewIds.isEmpty()`), `onEndFrame()` invokes `flutterView.revertImageView()` to return to direct `SurfaceView` presentation. - ### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). @@ -705,15 +707,15 @@ Unlike legacy Hybrid Composition (see Dynamic Raster Thread Merging), which merg - Rasterization and Android view updates execute on separate threads without leasing or merging overhead. - The embedder arranges the base Flutter surface, native platform views, and overlay slices into a tree of [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) nodes. - Visual synchronization occurs at the GPU level via hardware sync fences ([`ASyncFence`](https://developer.android.com/ndk/reference/group/sync) or Vulkan binary semaphores) passed through [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits, synchronizing buffer availability without CPU blocking. -- **Per-`FlutterViewId` Value-Captured Frame State**: In a multiview or multi-window environment, the raster thread renders View A (`FlutterPresentViewInfo.view_id = 0`) and immediately begins rasterizing View B (`view_id = 1`) while the platform thread is still applying View A's platform view layout mutations. Storing per-frame composition state (such as `composition_order_`, `picture_bounds_`, or `views_to_recomposite_`) as mutable fields on a shared controller causes cross-thread data races between consecutive views. `AndroidPlatformViewsController` eliminates this hazard by keying active platform view hierarchies by `FlutterViewId` and capturing each view's ordered `FlutterLayer` slice and `FlutterPlatformViewMutation` parameters by value inside the `present_view_callback` closure before dispatching to the platform thread. +- **Per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) Value-Captured Frame State**: In a multiview or multi-window environment, the raster thread renders View A (`FlutterPresentViewInfo.view_id = 0`) and immediately begins rasterizing View B (`view_id = 1`) while the platform thread is still applying View A's platform view layout mutations. Storing per-frame composition state (such as `composition_order_`, `picture_bounds_`, or `views_to_recomposite_`) as mutable fields on a shared controller causes cross-thread data races between consecutive views. `AndroidPlatformViewsController` eliminates this hazard by keying active platform view hierarchies by [`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) and capturing each view's ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) slice and [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) parameters by value inside the [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321) closure before dispatching to the platform thread. -To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the public C-API provides multi-layer compositing primitives through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2322): +To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the public C-API provides multi-layer compositing primitives through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322): -1. **Compositor Backing Stores**: The embedder registers a `FlutterCompositor` configuration where `create_backing_store_callback` allocates [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains managed by an embedder-owned surface pool (`AndroidSurfaceControl`). -2. **Layer Slicing and Dispatch**: During frame presentation, `present_view_callback` (or `present_layers_callback`) delivers an ordered list of `FlutterLayer` items scoped to `FlutterPresentViewInfo.view_id`: - - `kFlutterLayerContentTypeBackingStore`: Rendered Flutter graphics slices (base surface or overlay surfaces). - - `kFlutterLayerContentTypePlatformView`: Embedded native Android [`View`](https://developer.android.com/reference/android/view/View) nodes, accompanied by [`FlutterPlatformViewIdentifier`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1883) and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) arrays (see Platform View Mutator Stack Serialization). -3. **Fence Transfer and Atomic Commit**: In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), extensions are added through [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198) instead of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L663). `FlutterBackingStorePresentInfo` is extended in place with a synchronization fence file descriptor: +1. **Compositor Backing Stores**: The embedder registers a [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) configuration where [`create_backing_store_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2284) allocates [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains managed by an embedder-owned surface pool (`AndroidSurfaceControl`). +2. **Layer Slicing and Dispatch**: During frame presentation, [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321) (or [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2302)) delivers an ordered list of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) items scoped to [`FlutterPresentViewInfo.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2237): + - [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173): Rendered Flutter graphics slices (base surface or overlay surfaces). + - [`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175): Embedded native Android [`View`](https://developer.android.com/reference/android/view/View) nodes, accompanied by [`FlutterPlatformViewIdentifier`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1525) and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays (see Platform View Mutator Stack Serialization). +3. **Fence Transfer and Atomic Commit**: In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), extensions are added through [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198) instead of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231). `FlutterBackingStorePresentInfo` is extended in place with a synchronization fence file descriptor: ```c typedef struct { @@ -737,8 +739,8 @@ typedef struct { #### Synchronization Fence Lifecycle and Ownership 1. **GPU Fence Export**: When Impeller submits render passes for a backing store, it exports the GPU completion semaphore or fence into a POSIX `sync_fence` file descriptor using the [`VK_KHR_external_fence_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_fence_fd.html) extension (`vkGetFenceFdKHR`). -2. **Transfer to Embedder**: The engine populates `FlutterBackingStorePresentInfo.synchronization_fence_fd` and delivers the layers to `present_view_callback`. Ownership of the file descriptor transfers to the embedder. -3. **Buffer Attachment**: In HCPP mode, `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered `FlutterLayer` sequence directly into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`). The embedder attaches the backing store `AHardwareBuffer` and fence file descriptor to the layer via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)). Passing the fence to Android's transaction wraps or closes the descriptor; if unused or on error, the embedder must close it with `::close(fd)`. +2. **Transfer to Embedder**: The engine populates `FlutterBackingStorePresentInfo.synchronization_fence_fd` and delivers the layers to [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321). Ownership of the file descriptor transfers to the embedder. +3. **Buffer Attachment**: In HCPP mode, `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) sequence directly into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`). The embedder attaches the backing store `AHardwareBuffer` and fence file descriptor to the layer via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)). Passing the fence to Android's transaction wraps or closes the descriptor; if unused or on error, the embedder must close it with `::close(fd)`. 4. **SurfaceFlinger Wait & Buffer Release**: Android's window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) waits on the acquire fence on the GPU before compositing. Once compositing completes, SurfaceFlinger signals the transaction's release fence callback, which enables the embedder to recycle the backing store back to its surface pool. ```mermaid @@ -789,9 +791,9 @@ typedef struct { } FlutterPlatformViewMutation; ``` -Rounded-superellipse clips (`MutatorType::kClipRSE`) continue to be approximated as `kFlutterPlatformViewMutationTypeClipRoundedRect` by the engine ([`embedder_layers.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_layers.cc#L162-L167)), matching what Android HC and HCPP do today (`DlRoundSuperellipse::ToApproximateRoundRect()`). +Rounded-superellipse clips ([`MutatorType::kClipRSE`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L38)) continue to be approximated as [`kFlutterPlatformViewMutationTypeClipRoundedRect`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2077) by the engine ([`embedder_layers.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_layers.cc#L162-L167)), matching what Android HC and HCPP do today (`DlRoundSuperellipse::ToApproximateRoundRect()`). -Arbitrary clips need a path representation that does not drag [`SkPath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/third_party/skia/include/core/SkPath.h) across the boundary. In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `FlutterPath` uses a structure-of-arrays layout with an explicit fill rule and conservative control-point bounds: +Arbitrary clips need a path representation that does not drag [`SkPath`](https://github.com/google/skia/blob/main/include/core/SkPath.h) across the boundary. In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `FlutterPath` uses a structure-of-arrays layout with an explicit fill rule and conservative control-point bounds: ```c typedef enum { @@ -852,9 +854,9 @@ typedef enum { } FlutterPlatformViewMutationCapability; ``` -`FlutterCompositor` is extended with `uint64_t platform_view_mutation_capabilities;` (a bitmask of `FlutterPlatformViewMutationCapability` values, defaulting to `0` when omitted by older embedders). On Android API 34+, where `android.graphics.Path.conicTo` is available and HCPP applies path clips to `FlutterMutatorView`, the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` so conic arcs are passed through without quadratic approximation. +[`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) is extended with `uint64_t platform_view_mutation_capabilities;` (a bitmask of `FlutterPlatformViewMutationCapability` values, defaulting to `0` when omitted by older embedders). On Android API 34+, where `android.graphics.Path.conicTo` is available and HCPP applies path clips to [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32), the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` so conic arcs are passed through without quadratic approximation. -Mutations ride on [`FlutterPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1525), which the engine hands to the embedder inside a [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L663) through the compositor's [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1494): +Mutations ride on [`FlutterPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2094-L2117), which the engine hands to the embedder inside a [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) through the compositor's [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2302): ```c typedef struct { @@ -870,7 +872,7 @@ typedef struct { The array is an array of pointers rather than an array of values. Indexing strides by pointer width instead of `sizeof(FlutterPlatformViewMutation)`, so a future mutation type that widens the union does not shift the stride out from under an already-compiled embedder. This is the same technique [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) uses. -Android does not apply backdrop filter mutators over HC or HCPP platform views today: neither `AndroidExternalViewEmbedder` nor `AndroidExternalViewEmbedder2` overrides the no-op [`ExternalViewEmbedder::PushFilterToVisitedPlatformViews`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L538-L558), and both JNI serializers skip `kBackdropFilter` and the backdrop clip mutators ([HC](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1840-L1849), [HCPP](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L2355-L2362)). TLHC and VD render the platform view as an external texture into Flutter's own canvas ([`TextureLayer::Paint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/texture_layer.cc#L48-L67)), so backdrop filters already work in those modes without platform view mutators. The Android embedder skips `FlutterPlatformViewMutationType` values it does not handle, and any future backdrop-filter support over HC or HCPP views will consume the backdrop-blur mutation added by the iOS embedder migration RFC rather than defining an Android-specific mutation type. +Android does not apply backdrop filter mutators over HC or HCPP platform views today: neither [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) nor [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) overrides the no-op [`ExternalViewEmbedder::PushFilterToVisitedPlatformViews`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L538-L558), and both JNI serializers skip `kBackdropFilter` and the backdrop clip mutators ([HC](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1840-L1849), [HCPP](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L2355-L2362)). TLHC and VD render the platform view as an external texture into Flutter's own canvas ([`TextureLayer::Paint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/texture_layer.cc#L48-L67)), so backdrop filters already work in those modes without platform view mutators. The Android embedder skips `FlutterPlatformViewMutationType` values it does not handle, and any future backdrop-filter support over HC or HCPP views will consume the backdrop-blur mutation added by the iOS embedder migration RFC rather than defining an Android-specific mutation type. ### External Textures (Zero-Copy Vulkan and OpenGL ES) @@ -879,7 +881,7 @@ Android provides two distinct mechanisms for external textures: - [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline backed by `ImageReader` and [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) on API 29+ (falling back to `SurfaceTexture` below API 29 and on Huawei devices at API 29). - [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture): An OpenGL ES consumer path where native frames latch into a `GL_TEXTURE_EXTERNAL_OES` texture via [`SurfaceTexture.updateTexImage()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()). -Because the Android embedder owns the `VkDevice` on Vulkan and the `EGLContext` on OpenGL ES, it imports `AHardwareBuffer` frames into backend-native GPU handles itself (`VkImage` via `VK_ANDROID_external_memory_android_hardware_buffer` on Vulkan, and `EGLImage` / `GL_TEXTURE_EXTERNAL_OES` via `eglCreateImageKHR(EGL_NATIVE_BUFFER_ANDROID)` on OpenGL ES, mirroring [`ImageExternalTextureGL`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture_gl.cc#L93-L113)). This keeps `embedder.h` free of Android-specific `AHardwareBuffer` types. +Because the Android embedder owns the `VkDevice` on Vulkan and the `EGLContext` on OpenGL ES, it imports `AHardwareBuffer` frames into backend-native GPU handles itself (`VkImage` via `VK_ANDROID_external_memory_android_hardware_buffer` on Vulkan, and `EGLImage` / `GL_TEXTURE_EXTERNAL_OES` via `eglCreateImageKHR(EGL_NATIVE_BUFFER_ANDROID)` on OpenGL ES, mirroring [`ImageExternalTextureGL`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture_gl.cc#L87-L115)). This keeps `embedder.h` free of Android-specific `AHardwareBuffer` types. On Vulkan, [`flutter/flutter#188855`](https://github.com/flutter/flutter/pull/188855) added [`FlutterVulkanExternalTexture`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/shell/platform/embedder/embedder.h#L971-L1001), `FlutterVulkanTextureFrameCallback`, and `FlutterVulkanRendererConfig.external_texture_frame_callback`. However, camera and video decoder `AHardwareBuffer`s on Android frequently use vendor-specific external YCbCr formats (`VkAndroidHardwareBufferFormatPropertiesANDROID.format == VK_FORMAT_UNDEFINED`) that cannot be sampled without a `VkSamplerYcbcrConversion`. To support multi-planar and external-format `VkImage`s without leaking `AHardwareBuffer` into `embedder.h`, `FlutterVulkanExternalTexture` is extended with an optional `FlutterVulkanYcbcrConversionInfo` pointer: @@ -944,9 +946,9 @@ typedef struct { } FlutterVulkanExternalTexture; ``` -For [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture)-backed textures—used by the public `TextureRegistry.createSurfaceTexture()` / `registerSurfaceTexture()` APIs, by `SurfaceProducer` below API 29 (and on Huawei devices at API 29), and by TLHC/VD render targets below API 29—[`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 UV coordinate transformation matrix accounting for camera sensor rotation, video decoder padding, and vertical coordinate inversion (`ImageReader` buffers carry no matrix because `handlesCropAndRotation()` is `false`). Under Vulkan, `SurfaceTexture` requires an OpenGL ES context to latch frames, so the GL-to-Vulkan blit into an `AHardwareBuffer` ([`SurfaceTextureExternalTextureVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc#L152-L161)) runs inside the Android embedder with the UV matrix baked into the blit. Under OpenGL ES, however, the engine samples the texture directly and needs the UV matrix from the embedder. +For [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture)-backed textures—used by the public `TextureRegistry.createSurfaceTexture()` / `registerSurfaceTexture()` APIs, by `SurfaceProducer` below API 29 (and on Huawei devices at API 29), and by TLHC/VD render targets below API 29—[`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 UV coordinate transformation matrix accounting for camera sensor rotation, video decoder padding, and vertical coordinate inversion (`ImageReader` buffers carry no matrix because `handlesCropAndRotation()` is `false`). Under Vulkan, `SurfaceTexture` requires an OpenGL ES context to latch frames, so the GL-to-Vulkan blit into an `AHardwareBuffer` ([`SurfaceTextureExternalTextureVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc#L24-L161)) runs inside the Android embedder with the UV matrix baked into the blit. Under OpenGL ES, however, the engine samples the texture directly and needs the UV matrix from the embedder. -Because [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L671-L691) has no `struct_size` and is embedded by value inside [`FlutterOpenGLBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1982-L1996) as a render target, it stays unchanged and undeprecated. Instead, `embedder.h` introduces `FlutterOpenGLExternalTexture` (mirroring `FlutterMetalExternalTexture` and `FlutterVulkanExternalTexture`) and appends `FlutterOpenGLTextureFrameCallback external_texture_frame_callback;` to `FlutterOpenGLRendererConfig` (marking `gl_external_texture_frame_callback` `@deprecated` in documentation and rejecting configs that set both): +Because [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) has no `struct_size` and is embedded by value inside [`FlutterOpenGLBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1982-L1996) as a render target, it stays unchanged and undeprecated. Instead, `embedder.h` introduces `FlutterOpenGLExternalTexture` (mirroring `FlutterMetalExternalTexture` and `FlutterVulkanExternalTexture`) and appends `FlutterOpenGLTextureFrameCallback external_texture_frame_callback;` to `FlutterOpenGLRendererConfig` (marking `gl_external_texture_frame_callback` `@deprecated` in documentation and rejecting configs that set both): ```c typedef struct { @@ -988,12 +990,11 @@ FlutterOpenGLTextureFrameCallback external_texture_frame_callback; Under OpenGL ES, platform view compositing also faces constraints: because [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction sync fences depend on Vulkan hardware synchronization, OpenGL platform views must use Texture Layer Hybrid Composition (TLHC) or legacy Hybrid Composition rather than HCPP. +### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783) In-Place Extension) -### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677) In-Place Extension) - -Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677), including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count` (`FlutterSemanticsNode2.child_count` is populated from `childrenInTraversalOrder.size()`, which can differ from `childrenInHitTestOrder.size()` when the framework grafts nodes such as `OverlayPortal`). +Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783), including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count` (`FlutterSemanticsNode2.child_count` is populated from `childrenInTraversalOrder.size()`, which can differ from `childrenInHitTestOrder.size()` when the framework grafts nodes such as `OverlayPortal`). -**Missing Android Accessibility Members**: `FlutterSemanticsRole` is defined to mirror [`flutter::SemanticsRole`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/lib/ui/semantics/semantics_node.h#L107-L110), and the remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677): +**Missing Android Accessibility Members**: `FlutterSemanticsRole` is defined to mirror [`flutter::SemanticsRole`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/lib/ui/semantics/semantics_node.h#L74-L111), and the remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783): ```c typedef enum { @@ -1158,7 +1159,7 @@ Because split APK assets on Android are loaded via `AAssetManager` or memory-map ### Custom Asset and Kernel Resolution (APK / In-Memory Mapping) -Desktop embedders load assets from loose files in a filesystem directory passed to [`FlutterProjectArgs.assets_path`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2519). On Android, assets are packaged inside APK or AAB archives and accessed through the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26), which previously subclassed the internal engine [`AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23): +Desktop embedders load assets from loose files in a filesystem directory passed to [`FlutterProjectArgs.assets_path`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2533). On Android, assets are packaged inside APK or AAB archives and accessed through the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26), which previously subclassed the internal engine [`AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23): ```cpp // Existing internal AssetResolver inheritance in shell/platform/android/apk_asset_provider.h @@ -1210,12 +1211,13 @@ In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072 ``` Resolvers configured via `FlutterProjectArgs.custom_asset_resolvers` remain active across the lifetime of the engine, including hot reload, hot restart, and dynamic feature installation: -- **Hot Restart and Hot Reload**: [`Shell::OnServiceProtocolRunInView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L2034-L2043) and [`Shell::OnServiceProtocolSetAssetBundlePath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L2194-L2203) automatically re-attach every existing resolver whose `IsValidAfterAssetManagerChange()` returns `true` (which the engine's `FlutterCustomAssetResolver` wrapper implements), so the engine continues invoking `get_as_mapping_callback` alongside the updated `devFS` directory. + +- **Hot Restart and Hot Reload**: [`Shell::OnServiceProtocolRunInView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1992-L2043) and [`Shell::OnServiceProtocolSetAssetBundlePath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L2168-L2203) automatically re-attach every existing resolver whose `IsValidAfterAssetManagerChange()` returns `true` (which the engine's `FlutterCustomAssetResolver` wrapper implements), so the engine continues invoking `get_as_mapping_callback` alongside the updated `devFS` directory. - **Play Store Deferred Component Split Installation**: When [`PlayStoreDeferredComponentManager.loadAssets`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java#L383-L399) creates a refreshed package context after a split APK install and calls `FlutterJNI.updateJavaAssetManager`, the Android embedder atomically swaps the `AAssetManager*` and global Java `AssetManager` reference inside the struct pointed to by the resolver's `user_data`. ### Multi-Engine Spawning and Add-to-App (`FlutterEngineSpawn`) -Android Add-to-App architectures rely on [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) to spawn lightweight engines that share the Dart VM, isolate group, and shared cache. In the existing architecture, [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240-L285) reaches directly into [`shell_->Spawn(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L248): +Android Add-to-App architectures rely on [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) to spawn lightweight engines that share the Dart VM, isolate group, and shared cache. In the existing architecture, [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) reaches directly into [`shell_->Spawn(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210): ```cpp // Existing spawning in shell/platform/android/android_shell_holder.cc @@ -1251,22 +1253,22 @@ FlutterEngineResult FlutterEngineSpawn( FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); ``` -The [`FlutterEngineSpawnInfo`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L240) so that spawned isolates mount distinct route hierarchies immediately upon launch. +The [`FlutterEngineSpawnInfo`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) so that spawned isolates mount distinct route hierarchies immediately upon launch. -Spawned shells share the parent engine's [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context.h) / Impeller device context (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. +Spawned shells share the parent engine's [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/context/android_context.h#L26) / Impeller device context (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. #### Relationship to Single-Isolate Multiview (`FlutterEngineAddView`) -`FlutterEngineSpawn` and single-isolate multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)) address distinct concurrency and isolation trade-offs on Android: +`FlutterEngineSpawn` and single-isolate multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)) address distinct concurrency and isolation trade-offs on Android: -1. **Multi-Isolate Engine Groups (`FlutterEngineSpawn` / `FlutterEngineGroup`)**: Each spawned `FlutterEngine` runs an independent Dart root isolate within the shared Dart VM isolate group and `AndroidContext`. A long-running computation or frame drop in one view cannot stall the UI thread of another view, while native memory overhead remains low (~180 KB per additional isolate). -2. **Single-Isolate Multiview (`FlutterEngineAddView` / `FlutterEngineRemoveView`)**: A single `FlutterEngine` and single Dart isolate drive multiple `FlutterView` targets simultaneously, allowing widgets in distinct native windows or surfaces to read and mutate the same Dart object graph directly without cross-isolate message serialization. +1. **Multi-Isolate Engine Groups (`FlutterEngineSpawn` / [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41))**: Each spawned [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) runs an independent Dart root isolate within the shared Dart VM isolate group and [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/context/android_context.h#L26). A long-running computation or frame drop in one view cannot stall the UI thread of another view, while native memory overhead remains low (~180 KB per additional isolate). +2. **Single-Isolate Multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062-L3064))**: A single [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) and single Dart isolate drive multiple [`FlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L112) targets simultaneously, allowing widgets in distinct native windows or surfaces to read and mutate the same Dart object graph directly without cross-isolate message serialization. -Migrating the Android embedder onto `embedder.h` and `FlutterCompositor` unlocks both patterns: `FlutterEngineSpawn` supports `FlutterEngineGroup`, while per-view `FlutterCompositor` callbacks (`FlutterPresentViewInfo.view_id` and `FlutterBackingStoreConfig.view_id`) provide the foundation for single-isolate multiview and multi-window rendering. +Migrating the Android embedder onto [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) unlocks both patterns: `FlutterEngineSpawn` supports [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41), while per-view [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) callbacks ([`FlutterPresentViewInfo.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2237) and [`FlutterBackingStoreConfig.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2167)) provide the foundation for single-isolate multiview and multi-window rendering. ### Task Runner Configuration, Thread Priorities, and Threading Modes -The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2480), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (`platform_task_runner`) alongside dedicated worker task runners (`ui_task_runner`, `render_task_runner`, and `thread_priority_setter`). +The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (`platform_task_runner`) alongside dedicated worker task runners (`ui_task_runner`, `render_task_runner`, and `thread_priority_setter`). Thread priority configuration on Android requires system-level tuning. In the existing architecture, [`AndroidPlatformThreadConfigSetter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35) (located in [`android_shell_holder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35-L75)) uses `setpriority()` system calls to set Linux thread scheduling priorities: @@ -1308,15 +1310,15 @@ static void AndroidPlatformThreadConfigSetter( } ``` -The C-API embedder wires [`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1961) in [`FlutterCustomTaskRunners`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1961) to execute `setpriority()`. This reconciles the priority discrepancy between Android's embedder (which configures the IO task runner to `kNormal` priority, Linux nice value 0) and the engine-managed default (`kBackground`, Linux nice value 10). +The C-API embedder wires [`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975) in [`FlutterCustomTaskRunners`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1960-L1980) to execute `setpriority()`. This reconciles the priority discrepancy between Android's embedder (which configures the IO task runner to `kNormal` priority, Linux nice value 0) and the engine-managed default (`kBackground`, Linux nice value 10). -Internal consumers relying on the `kMergeAfterLaunch` threading model are supported without adding public fields to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). The embedder passes the engine switch `--merged-platform-ui-thread=mergeAfterLaunch` via `FlutterProjectArgs.command_line_argv`. In Java, [`FlutterLoader.ensureInitializationComplete`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L162) synthesizes this command-line argument dynamically from application manifest metadata and runtime configuration. +Internal consumers relying on the `kMergeAfterLaunch` threading model are supported without adding public fields to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). The embedder passes the engine switch `--merged-platform-ui-thread=mergeAfterLaunch` via `FlutterProjectArgs.command_line_argv`. In Java, [`FlutterLoader.ensureInitializationComplete`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L303-L465) synthesizes this command-line argument dynamically from application manifest metadata and runtime configuration. ### Process-Scoped Utilities, Image Decoding, and Diagnostics C-APIs -Four groups of Android entry points in `FlutterMain` and `FlutterJNI` reach engine state outside the frame pipeline, and today they pull `//flutter/lib/ui`, `//flutter/txt`, and `//flutter/runtime` headers into `shell/platform/android/`. `embedder.h` covers them with four focused C-API groups. The Dart callback cache and the default font manager are process-wide singletons that Android uses before any engine exists, so their functions take no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`. +Four groups of Android entry points in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108) reach engine state outside the frame pipeline, and today they pull [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) headers into [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android). [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) covers them with four focused C-API groups. The Dart callback cache and the default font manager are process-wide singletons that Android uses before any engine exists, so their functions take no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`. -1. **Process-Scoped Callback Cache (`//flutter/lib/ui/plugins/callback_cache.h` Severance)**: [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L148-L157) sets the cache directory and loads persisted handles during process initialization, and [`FlutterCallbackInformation.lookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java#L27-L29) resolves handles for background-execution plugins (`workmanager`, `android_alarm_manager_plus`). Because the iOS framework already exports an Objective-C class named [`FlutterCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h#L16-L17), the C struct is named `FlutterDartCallbackInformation`: +1. **Process-Scoped Callback Cache ([`//flutter/lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) Severance)**: [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L148-L157) sets the cache directory and loads persisted handles during process initialization, and [`FlutterCallbackInformation.lookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java#L27-L29) resolves handles for background-execution plugins (`workmanager`, `android_alarm_manager_plus`). Because the iOS framework already exports an Objective-C class named [`FlutterCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h#L16-L17), the C struct is named `FlutterDartCallbackInformation`: ```c typedef struct { @@ -1341,7 +1343,7 @@ Four groups of Android entry points in `FlutterMain` and `FlutterJNI` reach engi FlutterDartCallbackInformation* info_out); ``` -2. **Embedder Image Decoders (`//flutter/lib/ui/painting/image_generator.h` Severance)**: [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L173-L178) registers a fallback decoder backed by Android's [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (API 28+, using [`ALLOCATOR_SOFTWARE`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/image/ImageDecoderDefaultImpl.java#L49-L53)) at priority `-1` for formats the built-in decoders reject, such as HEIF. Because the `ImageGeneratorRegistry` belongs to each `Engine`, decoders are configured via `FlutterProjectArgs.image_generators` (`image_generators_count`), and the engine keeps the encoded byte buffer alive until the generator's `destruction_callback` runs so the embedder can wrap it in a direct `ByteBuffer` without copying: +2. **Embedder Image Decoders ([`//flutter/lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h#L27) Severance)**: [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L173-L178) registers a fallback decoder backed by Android's [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (API 28+, using [`ALLOCATOR_SOFTWARE`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/image/ImageDecoderDefaultImpl.java#L49-L53)) at priority `-1` for formats the built-in decoders reject, such as HEIF. Because the `ImageGeneratorRegistry` belongs to each `Engine`, decoders are configured via `FlutterProjectArgs.image_generators` (`image_generators_count`), and the engine keeps the encoded byte buffer alive until the generator's `destruction_callback` runs so the embedder can wrap it in a direct `ByteBuffer` without copying: ```c typedef enum { @@ -1400,14 +1402,14 @@ Four groups of Android entry points in `FlutterMain` and `FlutterJNI` reach engi const FlutterImageGeneratorRegistration** image_generators; ``` -3. **Font Manager Prefetching (`//flutter/txt` Severance)**: During [`FlutterLoader.initAsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L276-L278), Android warms up the process-wide default system font manager on a background worker before `FlutterEngineInitialize` and passes `--prefetched-default-font-manager` in `command_line_argv`: +3. **Font Manager Prefetching ([`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) Severance)**: During [`FlutterLoader.initAsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L276-L279), Android warms up the process-wide default system font manager on a background worker before `FlutterEngineInitialize` and passes `--prefetched-default-font-manager` in `command_line_argv`: ```c FLUTTER_EXPORT FlutterEngineResult FlutterEnginePrefetchDefaultFontManager(void); ``` -4. **VM Service URI Discovery (`//flutter/runtime/dart_service_isolate.h` Severance)**: Tooling and integration tests (such as the `espresso` package) read the Dart VM Service URL via `FlutterJNI.getVMServiceUri()`. `FlutterProjectArgs` adds `vm_service_uri_callback`, invoked on the platform thread once the VM service is listening (and never invoked after `FlutterEngineShutdown`): +4. **VM Service URI Discovery ([`//flutter/runtime/dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h#L22) Severance)**: Tooling and integration tests (such as the `espresso` package) read the Dart VM Service URL via [`FlutterJNI.getVMServiceUri()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L273-L275). [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) adds `vm_service_uri_callback`, invoked on the platform thread once the VM service is listening (and never invoked after [`FlutterEngineShutdown`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2947-L2948)): ```c typedef struct { @@ -1423,11 +1425,11 @@ Four groups of Android entry points in `FlutterMain` and `FlutterJNI` reach engi FlutterVMServiceUriCallback vm_service_uri_callback; ``` -**Platform-Side Screenshot Capture (`FlutterRenderer.getBitmap()`)**: No screenshot entry point is added to `embedder.h`. Engine-side `Rasterizer::Screenshot` re-rasterizes the last layer tree with a null `ExternalViewEmbedder` ([`rasterizer.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L910-L920)), which omits HC and HCPP platform views. Instead, the Android embedder implements `FlutterRenderer.getBitmap()` in platform code using [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) (available from `minSdk` 24), matching how the macOS embedder serves `flutter/screenshot` from its front `IOSurface`. +**Platform-Side Screenshot Capture ([`FlutterRenderer.getBitmap()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java#L1342-L1345))**: No screenshot entry point is added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). Engine-side [`Rasterizer::Screenshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L1075-L1140) re-rasterizes the last layer tree with a null [`ExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L432) ([`rasterizer.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L910-L920)), which omits HC and HCPP platform views. Instead, the Android embedder implements `FlutterRenderer.getBitmap()` in platform code using [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) (available from `minSdk` 24), matching how the macOS embedder serves `flutter/screenshot` from its front `IOSurface`. ### Platform Channel Messaging, Background Handlers, and Task Queues -In the existing Android architecture, [`PlatformMessageHandlerAndroid::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.cc#L56-L67) invokes `FlutterJNI.handlePlatformMessage` synchronously on the calling thread—either the UI thread (via [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1542-L1579)) or a background Dart isolate's thread (via [`UIDartState::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L202-L220)): +In the existing Android architecture, [`PlatformMessageHandlerAndroid::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.cc#L56-L67) invokes `FlutterJNI.handlePlatformMessage` synchronously on the calling thread—either the UI thread (via [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1532-L1579)) or a background Dart isolate's thread (via [`UIDartState::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L223-L240)): ```cpp // Existing Android message dispatch in shell/platform/android/platform_message_handler_android.cc @@ -1444,7 +1446,7 @@ void PlatformMessageHandlerAndroid::HandlePlatformMessage( } ``` -Inside Java, [`DartMessenger.dispatchMessageToQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L299-L327) inspects the handler's registered `TaskQueue` and either dispatches to a background `SerialTaskQueue` / `ConcurrentTaskQueue` executor (`makeBackgroundTaskQueue()`) or posts to the main `Looper`'s `PlatformTaskQueue`. Meanwhile, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false`, which tells [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1553-L1571) to bounce messages emitted during engine startup (`route_messages_through_platform_thread_`) through the platform thread before dispatching them, ensuring plugins registered immediately after `FlutterEngineRun` in the same platform-thread turn do not miss messages sent during `main()`. +Inside Java, [`DartMessenger.dispatchMessageToQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L299-L327) inspects the handler's registered `TaskQueue` and either dispatches to a background `SerialTaskQueue` / `ConcurrentTaskQueue` executor (`makeBackgroundTaskQueue()`) or posts to the main `Looper`'s `PlatformTaskQueue`. Meanwhile, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false`, which tells [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1532-L1571) to bounce messages emitted during engine startup (`route_messages_through_platform_thread_`) through the platform thread before dispatching them, ensuring plugins registered immediately after `FlutterEngineRun` in the same platform-thread turn do not miss messages sent during `main()`. In contrast, [`EmbedderPlatformMessageHandler::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) in `shell/platform/embedder/` unconditionally posts every message to `platform_task_runner_` and returns `true` from `DoesHandlePlatformMessageOnPlatformThread()`. @@ -1452,8 +1454,8 @@ In contrast, [`EmbedderPlatformMessageHandler::HandlePlatformMessage`](https://g Although the platform and UI threads are merged by default on Android ([`settings.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/settings.h#L381-L382))—meaning root-isolate platform messages already originate on the Android main thread and `makeBackgroundTaskQueue()` handlers still run on a background executor either way—invoking `platform_message_callback` directly in `HandlePlatformMessage` (and returning `false` from `DoesHandlePlatformMessageOnPlatformThread()`) preserves behavioral parity in two ways: -1. **Avoiding an Extra Main-`Looper` Turn for Root-Isolate Messages**: With direct dispatch, `DartMessenger.handleMessageFromDart` runs synchronously inside the send call instead of queuing an extra `PostTask` at the tail of the main `Looper`. Handlers registered with `makeBackgroundTaskQueue()` are submitted to their background executor immediately while the current frame finishes on the main thread, and default-queue handlers take one main-`Looper` post (via `PlatformTaskQueue`) instead of two—matching the iOS embedder migration RFC. -2. **Keeping Background-Isolate Messages Off the Main Thread**: Background isolates registered via `PlatformDispatcher.registerBackgroundIsolate` invoke `UIDartState::HandlePlatformMessage` on their own isolate thread ([`ui_dart_state.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L202-L220)). Direct dispatch allows a background isolate communicating with a `makeBackgroundTaskQueue()` plugin handler to dispatch directly from the background isolate thread to the plugin's background executor without bouncing through the main `Looper`. +1. **Avoiding an Extra Main-`Looper` Turn for Root-Isolate Messages**: With direct dispatch, [`DartMessenger.handleMessageFromDart`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L330-L361) runs synchronously inside the send call instead of queuing an extra `PostTask` at the tail of the main `Looper`. Handlers registered with [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) are submitted to their background executor immediately while the current frame finishes on the main thread, and default-queue handlers take one main-`Looper` post (via [`PlatformTaskQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformTaskQueue.java#L13-L22)) instead of two—matching the iOS embedder migration RFC. +2. **Keeping Background-Isolate Messages Off the Main Thread**: Background isolates registered via [`PlatformDispatcher.registerBackgroundIsolate`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L720-L722) invoke [`UIDartState::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L223-L240) on their own isolate thread ([`ui_dart_state.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L223-L240)). Direct dispatch allows a background isolate communicating with a `makeBackgroundTaskQueue()` plugin handler to dispatch directly from the background isolate thread to the plugin's background executor without bouncing through the main `Looper`. #### C-ABI Specification @@ -1481,11 +1483,11 @@ To reconcile this discrepancy without breaking existing desktop embedders, [`emb This configuration field satisfies both mobile embedder targets: -- **Zero-Initialization Safety**: Existing desktop embedders (Linux, macOS, Windows) zero-initialize `FlutterProjectArgs`, which defaults the field to `false` and preserves their single-threaded platform message assumptions. -- **Mobile Embedder Parity**: Both Android and iOS explicitly set `enable_direct_platform_message_dispatch = true`. On iOS, `PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()` returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`platform_message_callback`](#platform-channel-messaging-background-handlers-and-task-queues) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): - - If registered with a `BinaryMessenger.TaskQueue`, the task is dispatched directly to that queue's background executor pool. +- **Zero-Initialization Safety**: Existing desktop embedders (Linux, macOS, Windows) zero-initialize [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841), which defaults the field to `false` and preserves their single-threaded platform message assumptions. +- **Mobile Embedder Parity**: Both Android and iOS explicitly set `enable_direct_platform_message_dispatch = true`. On iOS, [`PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/platform_message_handler_ios.h#L22) returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`platform_message_callback`](#platform-channel-messaging-background-handlers-and-task-queues) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): + - If registered with a [`BinaryMessenger.TaskQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java#L37), the task is dispatched directly to that queue's background executor pool. - If registered on the default queue, the task posts to the Android platform [`Looper`](https://developer.android.com/reference/android/os/Looper). - - Responses submitted via [`FlutterEngineSendPlatformMessageResponse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3133) execute safely from any thread without main-thread hops. + - Responses submitted via [`FlutterEngineSendPlatformMessageResponse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3185-L3189) execute safely from any thread without main-thread hops. For empty platform messages, the C-API guarantees that a message where `message_size == 0` and `message == nullptr` is delivered to the framework as an empty byte buffer without triggering null-pointer dereferences or serialization assertions. @@ -1493,11 +1495,11 @@ For empty platform messages, the C-API guarantees that a message where `message_ #### Pointer Input Events -Input events processed in [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L434) provide axis and device metadata. The C-API embedder populates [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1377) with: +Input events processed in [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L372-L445) provide axis and device metadata. The C-API embedder populates [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1362-L1409) with: -- Device kind: `kFlutterPointerDeviceKindTouch`, `kFlutterPointerDeviceKindMouse`, `kFlutterPointerDeviceKindStylus`, or `kFlutterPointerDeviceKindTrackpad`. +- Device kind: [`kFlutterPointerDeviceKindTouch`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1321), [`kFlutterPointerDeviceKindMouse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1320), [`kFlutterPointerDeviceKindStylus`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1322), or [`kFlutterPointerDeviceKindTrackpad`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1323). - Axis values: `tilt`, `orientation`, `pressure`, and secondary scroll axes. -- Button states: `kFlutterPointerButtonMousePrimary`, `kFlutterPointerButtonMouseSecondary`, and stylus button bitmasks. +- Button states: [`kFlutterPointerButtonMousePrimary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1330), [`kFlutterPointerButtonMouseSecondary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1331), and stylus button bitmasks. #### Viewport Metrics, Edge-to-Edge Insets, and Foldables @@ -1512,9 +1514,9 @@ Android delivers window configuration metrics through [`FlutterJNI.nativeSetView [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) in `embedder.h` historically provided only `width`, `height`, and `pixel_ratio`. -Rather than creating a separate struct, `FlutterWindowMetricsEvent` is extended directly in place: +Rather than creating a separate struct, [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) is extended directly in place: -1. `FlutterWindowMetricsEvent` contains a `struct_size` field and is passed by pointer (`const FlutterWindowMetricsEvent* event`). +1. [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) contains a `struct_size` field and is passed by pointer (`const FlutterWindowMetricsEvent* event`). 2. This follows the existing precedent in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), which appended window constraint fields directly to `FlutterWindowMetricsEvent`. 3. Engine implementations use `SAFE_ACCESS(event, member, default_value)` to read new fields, which guarantees that existing desktop embedders passing the baseline struct size continue to work without modification. @@ -1587,15 +1589,15 @@ The following fields are appended to [`FlutterWindowMetricsEvent`](https://githu #### System Channels: Locale and Non-Linear Font Scaling -Features that communicate standard framework and platform configuration use platform message channels rather than custom additions to `embedder.h`: +Features that communicate standard framework and platform configuration use platform message channels rather than custom additions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): -1. **Locale Configuration**: The embedder uses the standard `flutter/localization` platform message channel ([`LocalizationChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java#L61-L97)). In the engine ([`embedder.cc:3423-3480`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3423-L3480)), [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3552) serializes locale data into a JSON message on this channel. No new C-API entry point is added for locale settings. +1. **Locale Configuration**: The embedder uses the standard `flutter/localization` platform message channel ([`LocalizationChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java#L61-L97)). In the engine ([`embedder.cc:3410-3467`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3410-L3467)), [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3479-L3481) serializes locale data into a JSON message on this channel. No new C-API entry point is added for locale settings. -2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses piecewise-linear font scaling curves (`FontScaleConverter`) so that large typography scales at lower ratios than body text, preventing UI clipping. In the legacy architecture, this was serviced through a synchronous JNI query ([`PlatformDispatcher._getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1648-L1649) -> [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L118) -> [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1534-L1549)) paired with [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176). Because `PlatformViewEmbedder` does not override `GetScaledFontSize`, invoking `_getScaledFontSize` under `embedder.h` hits the base `PlatformView::GetScaledFontSize` implementation (`FML_UNREACHABLE()`). +2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses piecewise-linear font scaling curves (`FontScaleConverter`) so that large typography scales at lower ratios than body text, preventing UI clipping. In the legacy architecture, this was serviced through a synchronous JNI query ([`PlatformDispatcher._getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1634) -> [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1023-L1024) -> [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544)) paired with [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176). Because `PlatformViewEmbedder` does not override `GetScaledFontSize`, invoking `_getScaledFontSize` under `embedder.h` hits the base `PlatformView::GetScaledFontSize` implementation (`FML_UNREACHABLE()`). - To support non-linear scaling without synchronous UI-thread JNI queries, `ConfigurationQueue` generation tracking, or new C-APIs in `embedder.h`, the existing `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) is extended: + To support non-linear scaling without synchronous UI-thread JNI queries, [`ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176) generation tracking, or new C-APIs in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the existing `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) is extended: - - During startup and configuration changes on API 34+ (when `SettingsChannel.hasNonlinearTextScalingSupport()` is true and `textScaleFactor != 1.0`), `SettingsChannel.MessageBuilder.send()` samples `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, displayMetrics) / displayMetrics.density` across Android's standard piecewise-linear lookup table breakpoints (`8, 10, 12, 14, 18, 20, 24, 30, 100` sp) and includes the resulting `(unscaled, scaled)` curve points in the `flutter/settings` JSON payload: + - During startup and configuration changes on API 34+ (when [`SettingsChannel.hasNonlinearTextScalingSupport()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L45-L47) is true and `textScaleFactor != 1.0`), [`SettingsChannel.MessageBuilder.send()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L105-L128) samples `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, displayMetrics) / displayMetrics.density` across Android's standard piecewise-linear lookup table breakpoints (`8, 10, 12, 14, 18, 20, 24, 30, 100` sp) and includes the resulting `(unscaled, scaled)` curve points in the `flutter/settings` JSON payload: ```json { @@ -1616,14 +1618,14 @@ Features that communicate standard framework and platform configuration use plat } ``` - - In `dart:ui`, [`PlatformDispatcher._updateUserSettingsData`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1294-L1367) parses `fontScaleCurves` into `_PlatformConfiguration` and compares it against `previousConfiguration.fontScaleCurves`. When either `textScaleFactorChanged` or `fontScaleCurvesChanged` is true, `_updateUserSettingsData` invalidates `_cachedFontSizes` and invokes `onTextScaleFactorChanged` (in addition to `onPlatformConfigurationChanged`). - - [`PlatformDispatcher.scaleFontSize(double unscaledFontSize)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1568-L1592) evaluates scaled sizes purely in Dart from `_configuration.fontScaleCurves`: + - In `dart:ui`, [`PlatformDispatcher._updateUserSettingsData`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1286-L1366) parses `fontScaleCurves` into `_PlatformConfiguration` and compares it against `previousConfiguration.fontScaleCurves`. When either `textScaleFactorChanged` or `fontScaleCurvesChanged` is true, `_updateUserSettingsData` invalidates `_cachedFontSizes` and invokes `onTextScaleFactorChanged` (in addition to `onPlatformConfigurationChanged`). + - [`PlatformDispatcher.scaleFontSize(double unscaledFontSize)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1553-L1577) evaluates scaled sizes purely in Dart from `_configuration.fontScaleCurves`: - If `textScaleFactor == 1.0` or `_configuration.fontScaleCurves` is null/empty, it returns `unscaledFontSize * textScaleFactor`. - - Otherwise, it binary-searches the sorted `(unscaled, scaled)` breakpoints in `fontScaleCurves` and linearly interpolates between the two bounding points (`lower.scaled + (upper.scaled - lower.scaled) * ((unscaledFontSize - lower.unscaled) / (upper.unscaled - lower.unscaled))`), interpolating from `(0.0, 0.0)` below the first breakpoint and scaling proportionally (`last.scaled * (unscaledFontSize / last.unscaled)`) above the final breakpoint to match Android's `FontScaleConverterImpl`. This removes synchronous JNI round-trips during text layout and eliminates `SettingsChannel.ConfigurationQueue`. + - Otherwise, it binary-searches the sorted `(unscaled, scaled)` breakpoints in `fontScaleCurves` and linearly interpolates between the two bounding points (`lower.scaled + (upper.scaled - lower.scaled) * ((unscaledFontSize - lower.unscaled) / (upper.unscaled - lower.unscaled))`), interpolating from `(0.0, 0.0)` below the first breakpoint and scaling proportionally (`last.scaled * (unscaledFontSize / last.unscaled)`) above the final breakpoint to match Android's `FontScaleConverterImpl`. This removes synchronous JNI round-trips during text layout and eliminates [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176). ## Target Architecture and Design Principles -The target architecture establishes a strict C-ABI boundary between the Android embedder and the engine core, replacing the legacy `AndroidShellHolder` and `PlatformViewAndroid` hierarchy with `FlutterEmbedderNative` and modular, single-responsibility C-API components. +The target architecture establishes a strict C-ABI boundary between the Android embedder and the engine core, replacing the legacy [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) hierarchy with `FlutterEmbedderNative` and modular, single-responsibility C-API components. ```mermaid flowchart TD @@ -1689,9 +1691,9 @@ sequenceDiagram In the target architecture: -- `FlutterEmbedderNative` holds only an opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) handle and dispatches all engine calls through an initialized `FlutterEngineProcTable` (`FlutterEngineGetProcAddresses`). +- `FlutterEmbedderNative` holds only an opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and dispatches all engine calls through an initialized `FlutterEngineProcTable` (`FlutterEngineGetProcAddresses`). - The `:flutter_embedder_native_src` GN source set compiles with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`. This enforces at compile time that all C-API symbols are invoked through the function pointer table rather than direct static symbol linkage. -- Event dispatch (touch, window metrics, platform messages, accessibility actions) calls public functions: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082), [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077), [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114), and [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398). +- Event dispatch (touch, window metrics, platform messages, accessibility actions) calls public functions: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082), [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077), [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114), and [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3330-L3336). - Engine initialization delegates thread creation to the engine via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006). ### Decomposition and Elimination of [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) @@ -1702,17 +1704,17 @@ In the legacy architecture, the native Android embedding is split across three t 2. [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38): The legacy C++ orchestrator instantiated by [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108). It allocates `fml::ThreadHost`, instantiates [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113), and owns [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43). 3. [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43): An internal engine class subclassing [`flutter::PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h) that centralizes window surfaces, platform view composition, external textures, and semantics. -In the completed target architecture, `AndroidShellHolder`, `PlatformViewAndroid`, and `PlatformViewAndroidJNIImpl` are completely deleted. In their place, `:flutter_embedder_native_src` organizes the Android embedder into a clean coordinator-component architecture: +In the completed target architecture, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), and [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc) are completely deleted. In their place, `:flutter_embedder_native_src` organizes the Android embedder into a clean coordinator-component architecture: -- `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Is the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in `library_loader.cc`). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. +- `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Is the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12)). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. - `JniRouter`, `JniDelegate`, and `JvmInvoker` (`jni_router.h`, `jni_delegate.h`, `jvm_invoker.h`): Decouple outbound C++-to-Java JNI invocations from live Android `JNIEnv*` state through injectable delegate and provider interfaces (`FlutterJniProvider`, `HardwareBufferProvider`, `SurfaceControlProvider`, `SurfaceTransactionProvider`, and `ChoreographerProvider`). This allows the entire C-API embedder (`flutter_embedder_native_unittests`) to be unit-tested on the host machine without an Android JVM or device. -- `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, enforces synchronous surface destruction contracts before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns, and suspends GPU command submission during application backgrounding via `FlutterEngineSetGpuAvailability`. -- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+) and Texture Layer Hybrid Composition (TLHC), deserialize `FlutterPlatformViewMutation` arrays, capture per-`FlutterViewId` frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). +- `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, and enforces synchronous surface destruction contracts before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns via `FlutterEngineNotifyDestroyed`. +- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+) and Texture Layer Hybrid Composition (TLHC), deserialize [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays, capture per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). - `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` (`android_hardware_buffer.cc`, `android_vulkan_external_texture.cc`): Manage [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) and `EGLImageKHR` (OpenGL ES) with UV transformation matrices (`FlutterOpenGLExternalTexture` and `FlutterOpenGLTextureFrameCallback`) and zero-copy [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) ([`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imported into `VkImage` on Vulkan) texture callbacks (`FlutterVulkanExternalTexture`, `FlutterVulkanYcbcrConversionInfo`, and `FlutterVulkanTextureFrameCallback`). -- `AndroidSemanticsMapper` and `AndroidWindowMetricsMapper` (`android_semantics_mapper.cc`, `android_window_metrics_mapper.cc`): Translate `FlutterSemanticsUpdate2` trees into JNI byte buffers for Java `AccessibilityBridge` and pack viewport metrics, insets, corner radii, and foldable display features into `FlutterWindowMetricsEvent`. +- `AndroidSemanticsMapper` and `AndroidWindowMetricsMapper` (`android_semantics_mapper.cc`, `android_window_metrics_mapper.cc`): Translate [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) trees into JNI byte buffers for Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) and pack viewport metrics, insets, corner radii, and foldable display features into [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). - `AndroidVsyncWaiter` (`android_vsync_waiter.cc`): Bridges Android NDK [`AChoreographer`](https://developer.android.com/ndk/reference/group/choreographer) and Java [`Choreographer`](https://developer.android.com/reference/android/view/Choreographer) frame callbacks to `FlutterProjectArgs.vsync_callback` and forwards frame pacing timestamps via `FlutterEngineOnVsync`. -- `AndroidVMInit` and `AndroidEngineGroup` (`android_vm_init.cc`, `android_engine_group.cc`): Coordinate initial Dart VM argument synthesis, Linux thread priority tuning (`thread_priority_setter`), and multi-engine group spawning via `FlutterEngineSpawn`. -- `APKAssetProvider` (`apk_asset_provider.cc`): Implements `FlutterCustomAssetResolver` to stream kernel blobs and application assets directly from Android APK or AAB archives via NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) memory mapping without disk extraction. +- `AndroidVMInit` and `AndroidEngineGroup` (`android_vm_init.cc`, `android_engine_group.cc`): Coordinate initial Dart VM argument synthesis, Linux thread priority tuning ([`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975)), and multi-engine group spawning via `FlutterEngineSpawn`. +- [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) ([`apk_asset_provider.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.cc)): Implements `FlutterCustomAssetResolver` to stream kernel blobs and application assets directly from Android APK or AAB archives via NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) memory mapping without disk extraction. ```mermaid flowchart TD @@ -1759,13 +1761,13 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnInfo`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`)
- `FlutterEngineInitializeCallbackCache` & `FlutterEngineGetCallbackInformation` (`FlutterDartCallbackInformation`)
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriCallback`) | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1523) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | -| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) & [`FlutterEngineNotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3048) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- [`FlutterEngineSetGpuAvailability`](#renderer-availability-multi-window-lifecycle-and-flutterviewid-keying) (`FlutterGpuAvailability`)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates GPU suspension during backgrounding. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnInfo`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`)
- `FlutterEngineInitializeCallbackCache` & `FlutterEngineGetCallbackInformation` (`FlutterDartCallbackInformation`)
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriCallback`) | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](#renderer-availability-and-surface-lifecycle), [`FlutterEngineNotifyDestroyed`](#renderer-availability-and-surface-lifecycle), and `FlutterProjectArgs.defer_surface_creation`
- `FlutterEngineQueryVulkanDriverSupport` (`FlutterVulkanDriverProperties`)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates synchronous surface teardown across `SurfaceHolder.Callback` events. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | -| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3250) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | -| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | -| **`APKAssetProvider`** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetMapping` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping across hot restarts and split APK installs. | -| **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2689) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3439)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | +| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3262-L3264) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | +| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | +| **[`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetMapping` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping across hot restarts and split APK installs. | +| **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2678) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3366-L3369)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | ## Phased Implementation Strategy @@ -1787,7 +1789,7 @@ flowchart LR All C-API extensions specified in Detailed Design are implemented in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc). -Each new API is validated with automated engine unit tests ([`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L89)) using mock platform delegates. No Android-specific files are modified in Phase 1. +Each new API is validated with automated engine unit tests ([`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390)) using mock platform delegates. No Android-specific files are modified in Phase 1. Phase 1 must be fully merged and green across CI trees before Phase 2 begins. @@ -1798,11 +1800,11 @@ Phase 2 reorganizes the Android embedder C++ codebase in-place to establish clea Phase 2 accomplishes two mandatory structural goals: 1. It establishes a hard compile-time firewall in GN (`:flutter_embedder_native_src`) that isolates new C-API code from legacy internal engine headers and enforces `FLUTTER_ENGINE_NO_PROTOTYPES`. -2. It decomposes the monolithic `PlatformViewAndroid` into focused modular components (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and `APKAssetProvider`) and decouples them from internal engine classes. +2. It decomposes the monolithic [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) into focused modular components (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)) and decouples them from internal engine classes. #### Compile-Time Firewall via GN Target Partitioning -To guarantee that new modular components cannot inadvertently include private engine headers (such as `//flutter/flow`, `//flutter/runtime`, `//flutter/skia`, or `//flutter/impeller`), [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates the new C-API implementation inside `source_set("flutter_embedder_native_src")`: +To guarantee that new modular components cannot inadvertently include private engine headers (such as [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), or [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller)), [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates the new C-API implementation inside `source_set("flutter_embedder_native_src")`: ```gn # GN Target Partitioning in shell/platform/android/BUILD.gn: @@ -1917,7 +1919,7 @@ In contrast, external embedders communicating via [`embedder.h`](https://github. #### Lifecycle and Deletion of Legacy Classes (`AndroidShellHolder` and `PlatformViewAndroid`) -`AndroidShellHolder` and `PlatformViewAndroid` progress across four migration stages culminating in their complete removal: +[`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) progress across four migration stages culminating in their complete removal: ```mermaid flowchart TD @@ -1934,43 +1936,43 @@ flowchart TD P3 --> P4 ``` -1. **Pre-Migration**: `AndroidShellHolder` and `PlatformViewAndroid` handle 100% of production traffic and link to 98 internal engine headers. -2. **Phase 2 (Partitioning & Strangler-Fig Seam)**: Modular components are constructed inside `flutter_embedder_native_src` with `check_includes = true` and `FLUTTER_ENGINE_NO_PROTOTYPES`. During incremental slice development, a transitional polymorphic delegate interface (`AndroidEngine`, implemented by `ShellAndroidEngine` and `EmbedderAndroidEngine`) allows individual subsystems to be migrated and tested against `AndroidShellHolder` without breaking existing `flutter_shell_native_unittests`. -3. **Phase 3 (C-API Cutover)**: `FlutterEmbedderNative` assumes direct ownership of the `FlutterJNI` native bindings (`FlutterEmbedderNative::RegisterJni(env)`) and drives the engine exclusively through `FlutterEngineProcTable`. Inside the core engine, the embedder runtime uses [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and `EmbedderExternalViewEmbedder` to fulfill the shell's internal requirements. -4. **Phase 4 (Legacy Class Purge & Build Decoupling)**: Once `FlutterEmbedderNative` and `flutter_embedder_native_unittests` reach full parity, `AndroidShellHolder`, `PlatformViewAndroid`, `PlatformViewAndroidJNIImpl`, `AndroidEngine`, `ShellAndroidEngine`, `EmbedderAndroidEngine`, and `external_view_embedder/` are permanently deleted from the repository (`legacy-class-purge`). `:flutter_shell_native_src` is reduced to just three files (`flutter_main.cc`, `flutter_main.h`, and `library_loader.cc`), and the Android embedder retains zero subclassing of internal engine classes. +1. **Pre-Migration**: [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) handle 100% of production traffic and link to 98 internal engine headers. +2. **Phase 2 (Partitioning & Strangler-Fig Seam)**: Modular components are constructed inside `flutter_embedder_native_src` with `check_includes = true` and `FLUTTER_ENGINE_NO_PROTOTYPES`. During incremental slice development, a transitional polymorphic delegate interface (`AndroidEngine`, implemented by `ShellAndroidEngine` and `EmbedderAndroidEngine`) allows individual subsystems to be migrated and tested against [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) without breaking existing [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L42-L67). +3. **Phase 3 (C-API Cutover)**: `FlutterEmbedderNative` assumes direct ownership of the [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108) native bindings (`FlutterEmbedderNative::RegisterJni(env)`) and drives the engine exclusively through [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827). Inside the core engine, the embedder runtime uses [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and `EmbedderExternalViewEmbedder` to fulfill the shell's internal requirements. +4. **Phase 4 (Legacy Class Purge & Build Decoupling)**: Once `FlutterEmbedderNative` and `flutter_embedder_native_unittests` reach full parity, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), `AndroidEngine`, `ShellAndroidEngine`, `EmbedderAndroidEngine`, and [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) are permanently deleted from the repository (`legacy-class-purge`). [`:flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) is reduced to just three files ([`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc), [`flutter_main.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17), and [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12)), and the Android embedder retains zero subclassing of internal engine classes. ### Phase 3: Genuine C-API Cutover (`FlutterEmbedderNative`) -In Phase 3, `FlutterEmbedderNative` initializes [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L398) via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006) through `FlutterEngineProcTable`. +In Phase 3, `FlutterEmbedderNative` initializes [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006) through `FlutterEngineProcTable`. Event dispatch routes directly to public C-API entry points: - Touch events: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082) - Viewport metrics: [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077) - Platform messages: [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114) -- Semantics actions: [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3398) +- Semantics actions: [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3330-L3336) - Texture registrations: [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) -- Compositor and multi-surface presentation: [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2322) layer presentation (`present_view_callback`) coordinating [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits via `AndroidSurfaceControl` and `AndroidPlatformViewsController`. +- Compositor and multi-surface presentation: [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) layer presentation (`present_view_callback`) coordinating [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits via `AndroidSurfaceControl` and `AndroidPlatformViewsController`. #### Transitional Polymorphic Delegation via `AndroidEngine` (Phases 2–3) -During Phases 2 and 3, before `AndroidShellHolder` is deleted in Phase 4, incremental subsystem migration is bridged using the transitional `AndroidEngine` seam: +During Phases 2 and 3, before [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) is deleted in Phase 4, incremental subsystem migration is bridged using the transitional `AndroidEngine` seam: 1. **`ShellAndroidEngine` (Legacy Path)**: - - Directs lifecycle and events to internal engine components: [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L137), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), and [`fml::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L27). - - Retains private engine dependencies (`//flutter/flow`, `//flutter/runtime`, `//flutter/impeller`) until Phase 4 deletion. + - Directs lifecycle and events to internal engine components: [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), and [`fml::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21). + - Retains private engine dependencies ([`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller)) until Phase 4 deletion. 2. **`EmbedderAndroidEngine` / `FlutterEmbedderNative` (C-API Target Path)**: - - Encapsulates the opaque `FlutterEngine` handle and `FlutterEngineProcTable`. + - Encapsulates the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827). - Forwards all operations strictly through the public C-API in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). - - Once `FlutterEmbedderNative` registers JNI directly in `library_loader.cc`, both `AndroidShellHolder` and the transitional `AndroidEngine` classes become dead code and are removed in Phase 4. + - Once `FlutterEmbedderNative` registers JNI directly in [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12), both [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and the transitional `AndroidEngine` classes become dead code and are removed in Phase 4. ### Phase 4: Build Decoupling and Legacy Class Purge Once `FlutterEmbedderNative` passes all functional and performance tests, [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) and `shell/platform/android/` are permanently decoupled: -- Delete `AndroidShellHolder`, `PlatformViewAndroid`, `PlatformViewAndroidJNIImpl`, `AndroidEngine`, `EmbedderAndroidEngine`, `ShellAndroidEngine`, `VsyncWaiterAndroid`, `PlatformMessageHandlerAndroid`, `SurfaceTextureExternalTexture`, `ImageExternalTexture`, and the `external_view_embedder/` directory. -- Reduce `:flutter_shell_native_src` to `flutter_main.cc`, `flutter_main.h`, and `library_loader.cc` (which calls `FlutterEmbedderNative::RegisterJni(env)`). +- Delete [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), `AndroidEngine`, `EmbedderAndroidEngine`, `ShellAndroidEngine`, [`VsyncWaiterAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/vsync_waiter_android.h#L19), [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18), [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33), [`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36), and the [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) directory. +- Reduce [`:flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) to [`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc), [`flutter_main.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17), and [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12) (which calls `FlutterEmbedderNative::RegisterJni(env)`). - Remove dependencies on [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common). - Restrict dependencies strictly to: - [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`) @@ -2071,7 +2073,7 @@ The presubmit linter enforces four deterministic rules on incoming pull requests Following Phase 3 cutover validation: -1. All legacy classes (`AndroidShellHolder`, `PlatformViewAndroid`, `PlatformViewAndroidJNIImpl`, `external_view_embedder/`) are deleted from the repository. +1. All legacy classes ([`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder)) are deleted from the repository. 2. The allowlist configuration is locked permanently: ```yaml @@ -2088,12 +2090,12 @@ Because Android is Flutter's largest mobile deployment target, verification must ### Engine Unit Tests (`flutter_embedder_native_unittests`) -- Host-executable C++ unit tests (`flutter_embedder_native_unittests`) for `FlutterEmbedderNative` and each modularized component (`AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, and `AndroidEngineGroup`) using mock `FlutterEngineProcTable` entries and injectable `JniDelegate` / `JvmInvoker` provider interfaces without requiring a live Android JVM or device. -- Unit tests validating the serialization and deserialization of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2071) structs and per-`FlutterViewId` frame state capture. +- Host-executable C++ unit tests (`flutter_embedder_native_unittests`) for `FlutterEmbedderNative` and each modularized component (`AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, and `AndroidEngineGroup`) using mock [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827) entries and injectable `JniDelegate` / `JvmInvoker` provider interfaces without requiring a live Android JVM or device. +- Unit tests validating the serialization and deserialization of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) structs and per-`FlutterViewId` frame state capture. ### Android Integration Tests -- [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L48) and `flutter_embedder_native_unittests` executed across the CI test matrix. +- [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L42-L67) and `flutter_embedder_native_unittests` executed across the CI test matrix. - Platform view lifecycle and composition tests verifying Hybrid Composition++ (HCPP with [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) on API 34+), legacy Hybrid Composition (HC), Texture Layer Hybrid Composition (TLHC), and Virtual Displays (VD) under both flag configurations. - Android embedding integration tests verifying JNI message channels, background isolate callbacks, and deferred library loading. From e30dd555519386fee7b1ba989cdddf9f4f4c05f4 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Mon, 19 Oct 2026 15:14:58 -0700 Subject: [PATCH 27/43] details --- rfc/410.0000-flutter-android-embedder-c-api-migration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index d6d74c1..c40a55a 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -207,6 +207,8 @@ sequenceDiagram Because [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) reach directly into internal engine classes, [`flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) in [`engine/src/flutter/shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) depends on internal engine targets. +
+ An audit of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) reveals 98 internal engine headers included across Android shell files, categorized across 9 distinct subsystems: | Subsystem & Headers | Direct GN Dependencies | Key Internal Headers | Functional Purpose in Embedder | @@ -220,6 +222,7 @@ An audit of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/ | **Asset Management & Packaging**
(1 header) | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) | [`assets/asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h) | Resolving assets and kernel blobs packaged within the Android APK via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17). | | **GPU Backends & Surface Delegates**
(5 headers) | [`//flutter/shell/gpu`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | [`shell/gpu/gpu_surface_gl_impeller.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu/gpu_surface_gl_impeller.h)
[`gpu_surface_vulkan_impeller.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu/gpu_surface_vulkan_impeller.h)
[`vulkan/vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h) | Implementing platform window surface delegates for OpenGL and Vulkan render pipelines. | | **Foundational Utilities & Concurrency**
(24 headers) | [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | [`fml/raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)
[`fml/task_runner.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h)
[`fml/message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.h)
[`fml/concurrent_message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/concurrent_message_loop.h)
[`fml/platform/android/jni_util.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/jni_util.h)
[`fml/platform/android/scoped_java_ref.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/scoped_java_ref.h) | Message loops, JNI handle wrappers, task scheduling, and raster thread leasing. | +
### Technical Consequences of Coupling From 0e7411d934c99d48d45e718c373bb1b6ca17153d Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Tue, 20 Oct 2026 17:23:15 +0000 Subject: [PATCH 28/43] Updating to a callback for deffered renderer with the backing_store_caches turned off --- ...lutter-android-embedder-c-api-migration.md | 615 +++++++++++------- 1 file changed, 362 insertions(+), 253 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index c40a55a..de2e658 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -239,39 +239,35 @@ To allow the Android embedder to communicate with the engine exclusively through ### Graphics Surface Setup, Context Ownership, and Surface Lifecycle ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)) +To let the Android embedder choose between Vulkan (`kVulkan`) and OpenGL ES (`kOpenGL`) on the raster thread without blocking the Android UI thread during startup, this RFC adds a `kDeferred` renderer type (`FlutterDeferredRendererConfig`) and a `FlutterRendererConfigResolveCallback` callback to [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) (see [Deferred Backend Selection (`kDeferred`)](#deferred-backend-selection-kdeferred)): + +- At startup, the Android embedder passes `type = kDeferred` with its `resolve_callback` to [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) so the UI thread does not load `libvulkan.so` or initialize a GPU context synchronously. +- While the engine launches, the raster thread runs `CreateImpellerContext` ([`PlatformViewEmbedder::SetupImpellerContext()`](#deferring-impellercontext-and-surface-creation-in-shellplatformembedder-setupimpellercontext)) and invokes `FlutterRendererConfigResolveCallback`, which probes the device and fills in a concrete `kVulkan` or `kOpenGL` `FlutterRendererConfig`. +- If the engine rejects the returned `kVulkan` configuration (because context creation fails or the driver matches Impeller's known-bad driver list), it re-invokes `FlutterRendererConfigResolveCallback` with a `FlutterRendererResolveInfo` rejection reason so the embedder can destroy its Vulkan handles and return `kOpenGL`. + #### How `SetupImpellerContext` Works on Android Today -During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) creates the `PlatformView` on the Android UI thread (`on_create_platform_view`) and then posts a `CreateImpellerContext` task to the raster task runner without blocking the UI thread: +
-```cpp -// Existing raster-thread task in shell/common/shell.cc (L314-L333) -fml::TaskRunner::RunNowOrPostTask( - task_runners.GetRasterTaskRunner(), - fml::MakeCopyable([..., platform_view_ptr]() mutable { - TRACE_EVENT0("flutter", "CreateImpellerContext"); - platform_view_ptr->SetupImpellerContext(); - std::shared_ptr impeller_context = - platform_view_ptr->GetImpellerContext(); - ... - })); -``` +[`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added in [#167765](https://github.com/flutter/flutter/pull/167765) because Vulkan driver and context initialization can take 100+ ms on some Android devices. During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) creates [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) (which constructs [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) as an empty shell) on the Android UI thread and posts a `CreateImpellerContext` task ([`shell.cc#L314-L333`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L314-L333)) to the raster task runner to run [`PlatformViewAndroid::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570): -[`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added (in [#167765](https://github.com/flutter/flutter/pull/167765)) because Vulkan driver and context initialization can take 100+ ms on some Android devices. In the current Android embedder, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) constructs [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) as an empty shell on the UI thread and defers all graphics work until `CreateImpellerContext` runs [`PlatformViewAndroid::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570) on the raster thread: +```mermaid +sequenceDiagram + autonumber + participant UI as "Android UI Thread" + participant PV as "PlatformViewAndroid / AndroidContextDynamicImpeller" + participant Raster as "Engine Raster Thread" -```cpp -// Existing deferred graphics resolution in shell/platform/android/android_context_dynamic_impeller.cc -void AndroidContextDynamicImpeller::SetupImpellerContext() { - if (vk_context_ || gl_context_) { - return; - } - vk_context_ = GetActualRenderingAPIForImpeller(android_get_device_api_level(), - settings_); - if (!vk_context_) { - gl_context_ = std::make_shared( - std::make_unique(), - settings_.enable_gpu_tracing, io_task_runner_); - } -} + UI->>PV: Shell::CreateWithSnapshot() creates empty AndroidContextDynamicImpeller shell + UI-)Raster: Post CreateImpellerContext task (non-blocking) + UI-->>UI: Continue startup without waiting for GPU initialization + Raster->>PV: PlatformViewAndroid::SetupImpellerContext() + PV->>PV: GetActualRenderingAPIForImpeller() (SoC/property checks, VkInstance/VkDevice, ContextVK::Create, IsKnownBadDriver) + opt vk_context_ is null (Vulkan unavailable or driver rejected) + PV->>PV: Construct AndroidContextGLImpeller (egl::Display, ContextGLES::Create) + end + Raster->>PV: GetImpellerContext() + PV-->>Raster: std::shared_ptr ``` That single raster-thread call currently performs two distinct jobs inside [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android): @@ -285,14 +281,14 @@ When Android migrates to [`embedder.h`](https://github.com/flutter/flutter/blob/ Preserving Android's non-blocking startup behavior while staying on the existing two-phase initialization API ([`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007)) requires three coordinated changes: -1. **Backend selection and `VkDevice` creation must run asynchronously before [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975)**, because [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1042-L1055) requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) is called—before [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) or the raster thread runs. +1. **The backend choice must move to the raster thread.** [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1042-L1055) requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) is called, before [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) or the raster thread runs. Android makes this choice inside `CreateImpellerContext` today. A new `kDeferred` renderer type lets the embedder make it there too (see [Deferred Backend Selection](#deferred-backend-selection-kdeferred)). 2. **[`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) must be wired up to [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) in [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder)**, because [#167765](https://github.com/flutter/flutter/pull/167765) only implemented [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) on [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43). In [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder) today: - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) does not override `SetupImpellerContext()`. It only overrides `GetImpellerContext()`, which calls `embedder_surface_->CreateImpellerContext()`. - Despite the name [`CreateImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L25), both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L619-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L307-L500)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. -3. **[`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) must not call [`NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) before a window surface exists.** In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2515-L2526) today, `FlutterEngineRunInitialized` calls `embedder_engine->LaunchShell()` and immediately calls `embedder_engine->NotifyCreated()`: +3. **The internal `NotifyCreated()` call in [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) must not block on the raster thread.** In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2515-L2526), `FlutterEngineRunInitialized` calls `embedder_engine->LaunchShell()`, `embedder_engine->NotifyCreated()`, and `embedder_engine->RunRootIsolate()`: ```cpp - // Existing implementation in shell/platform/embedder/embedder.cc (L2580-L2599) + // Existing implementation in shell/platform/embedder/embedder.cc (L2515-L2534) // Step 1: Launch the shell. if (!embedder_engine->LaunchShell()) { return LOG_EMBEDDER_ERROR(kInvalidArguments, ...); @@ -309,57 +305,152 @@ Preserving Android's non-blocking startup behavior while staying on the existing } ``` - Because [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) posts `CreateRenderingSurface()` to the FIFO raster task runner—queued **behind** `CreateImpellerContext`—and blocks the UI thread on a latch (`latch.Wait()`), calling `NotifyCreated()` inside `FlutterEngineRunInitialized` forces the UI thread to wait for `SetupImpellerContext()` to finish anyway, and creates a premature surface before Android's `SurfaceHolder.Callback.surfaceCreated` has provided an `ANativeWindow`. + [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) posts `CreateRenderingSurface()` to the FIFO raster task runner, queued **behind** `CreateImpellerContext`, and blocks the UI thread on a latch (`latch.Wait()`) before passing the surface to [`Shell::OnPlatformViewCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L973-L1062). Once context creation moves to the raster thread, that latch makes the UI thread wait for `SetupImpellerContext()` anyway, and with `kDeferred` it would also wait for the embedder's resolve callback. Launching the shell doesn't have this problem: the rasterizer is created in a raster task posted before `CreateImpellerContext` ([`shell.cc#L295-L335`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L295-L335)), and `Shell::Setup` waits only for the engine, the rasterizer and the IO manager ([`shell.cc#L438-L441`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L438-L441)), which the IO thread hands over before it waits for the context ([`shell.cc#L382-L391`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L382-L391)). `FlutterEngineRunInitialized` continues to call `embedder_engine->NotifyCreated()`, and `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the raster task runner instead of waiting on a latch (see [Deferring `impeller::Context` and Surface Creation](#deferring-impellercontext-and-surface-creation-in-shellplatformembedder-setupimpellercontext)). Creating that surface before Android provides an `ANativeWindow` is fine, because with a compositor it holds only the rendering context (see [Renderer Availability and Surface Lifecycle](#renderer-availability-and-surface-lifecycle)). +
-#### Backend Selection and Background Vulkan Probe Before `FlutterEngineInitialize` +#### Deferred Backend Selection (`kDeferred`) -In [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L62-L63) and [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L388-L389), `FlutterLoader.startInitialization()` and `FlutterLoader.ensureInitializationComplete()` are called on consecutive lines on the UI thread unless the host application pre-warms `FlutterLoader` in `Application.onCreate`. Joining the Vulkan probe inside `ensureInitializationComplete()` would therefore block the UI thread immediately with zero overlap. +Android chooses between Vulkan and OpenGL ES on the raster thread today. To keep that, `embedder.h` adds a renderer type whose configuration is a callback. The engine calls it on the raster thread inside `CreateImpellerContext`, and the callback returns a concrete configuration: -Instead, the embedder follows the same pattern [`FlutterLoader.initTask`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L197-L291) uses for `prefetchDefaultFontManager`: as soon as `flutterJNI.loadLibrary(appContext)` completes on `initTask`, `FlutterLoader` submits the process-wide two-stage Vulkan probe as a separate task on `executorService` that `ensureInitializationComplete()` does **not** wait on. The embedder only joins the probe future right before constructing `FlutterRendererConfig` for `FlutterEngineInitialize`, overlapping the probe with `FlutterMain.init`, `FlutterEngine` Java subsystem construction, plugin registration, and initial channel setup: +```c +// FlutterRendererConfig gains a struct tag so that the callback below can +// refer to it. Adding a tag to a typedef'd struct is source and ABI compatible. +typedef struct FlutterRendererConfig FlutterRendererConfig; -- **Stage 1 (Android system-property and SoC deny-list)**: Before loading `libvulkan.so`, the Android embedder runs the checks from [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120): known emulators, Huawei devices (`ro.com.google.clientidbase`), MediaTek devices with `ro.vendor.build.version.sdk < 32`, and the `ro.product.board` deny-list ([`IsKnownBadSOC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L68-L77)). Matching devices select `kOpenGL` immediately without loading Vulkan. -- **Stage 2 (Impeller known-bad driver query via [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h))**: If Stage 1 passes, the background task loads Vulkan, selects a physical device, and reads `VkPhysicalDeviceProperties`. To check those properties against Impeller's [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377) while keeping the driver blocklist in one place inside Impeller, `embedder.h` adds a stateless query function: +typedef enum { + // ... existing values ... + /// The embedder chooses the renderer on the raster thread while the engine + /// launches. See |FlutterDeferredRendererConfig|. + kDeferred, +} FlutterRendererType; + +typedef enum { + kFlutterRendererRejectReasonNone = 0, + /// The configuration failed validation (missing callbacks, a type this build + /// or these settings do not support, or `kDeferred` itself). + kFlutterRendererRejectReasonInvalidConfig, + /// The engine could not create a rendering context from the configuration, + /// for example because the Vulkan device lacks a required extension. + kFlutterRendererRejectReasonContextCreationFailed, + /// The Vulkan driver is on the engine's list of known-bad drivers. + kFlutterRendererRejectReasonKnownBadDriver, +} FlutterRendererRejectReason; -```c typedef struct { - /// The size of this struct. Must be sizeof(FlutterVulkanDriverProperties). + /// The size of this struct. Must be sizeof(FlutterRendererResolveInfo). size_t struct_size; - /// Vulkan API version reported by VkPhysicalDeviceProperties::apiVersion. - uint32_t api_version; - /// Driver version reported by VkPhysicalDeviceProperties::driverVersion. - uint32_t driver_version; - /// PCI/Khronos vendor ID reported by VkPhysicalDeviceProperties::vendorID. - uint32_t vendor_id; - /// Device ID reported by VkPhysicalDeviceProperties::deviceID. - uint32_t device_id; - /// NUL-terminated device name string from - /// VkPhysicalDeviceProperties::deviceName; only read during the call. - const char* device_name; -} FlutterVulkanDriverProperties; + /// 0 on the first call; incremented after each rejected configuration. + size_t attempt; + /// The type of the previously returned configuration. Unset on attempt 0. + FlutterRendererType rejected_type; + /// Why the previous configuration was rejected. None on attempt 0. + FlutterRendererRejectReason reject_reason; +} FlutterRendererResolveInfo; + +/// @param[in] user_data The `user_data` passed to `FlutterEngineInitialize` +/// or `FlutterEngineRun`. +/// @param[in] info Which attempt this is and, after a rejection, why. +/// @param[out] config_out Zero-initialized, engine-owned. Set `type` to +/// `kOpenGL`, `kSoftware`, `kMetal` or `kVulkan` and +/// fill in that union member. Memory it references must +/// stay valid until `FlutterEngineDeinitialize` returns +/// for this engine and every engine spawned from it. +/// @return `false` if no renderer can be created. +typedef bool (*FlutterRendererConfigResolveCallback)( + void* /* user_data */, + const FlutterRendererResolveInfo* /* info */, + FlutterRendererConfig* /* config_out */); -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineQueryVulkanDriverSupport( - const FlutterVulkanDriverProperties* properties, - bool* out_is_known_bad); +typedef struct { + /// The size of this struct. Must be sizeof(FlutterDeferredRendererConfig). + size_t struct_size; + /// Required. Called on the raster task runner before the engine creates its + /// rendering context, while the engine launches. If the engine rejects the + /// returned configuration, it releases every object it created from it + /// (embedder-provided handles are never destroyed by the engine) and calls + /// this again with the reason; return `false` to stop. The engine creates + /// its rendering surface after this returns, so only + /// `FlutterEngineDeinitialize` and `FlutterEngineShutdown` wait for it. The + /// callback may block, but it must not wait on the platform thread. + /// Engines created with `FlutterEngineSpawn` reuse the resolved + /// configuration and rendering context; the callback is not called for + /// them. + FlutterRendererConfigResolveCallback resolve_callback; +} FlutterDeferredRendererConfig; + +struct FlutterRendererConfig { + FlutterRendererType type; + union { + FlutterOpenGLRendererConfig open_gl; + FlutterSoftwareRendererConfig software; + FlutterMetalRendererConfig metal; + FlutterVulkanRendererConfig vulkan; + FlutterDeferredRendererConfig deferred; + }; +}; ``` -The embedder initializes `is_known_bad = true` before calling `FlutterEngineQueryVulkanDriverSupport`. If the call fails or leaves `is_known_bad` set to `true` (unless Vulkan was explicitly forced in a non-release build), or if `VkDevice` creation fails, the embedder tears down its Vulkan handles and falls back to `kOpenGL`. Because the probed `VkInstance` and `VkDevice` are held by a process-wide singleton (`VulkanDeviceOwner`), subsequent [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) instances reuse the initialized handles with zero probe cost. +**What the Android callback does.** It runs the checks Android runs today in [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120): known emulators, Huawei devices (`ro.com.google.clientidbase`), MediaTek devices with `ro.vendor.build.version.sdk < 32`, and the `ro.product.board` deny-list ([`IsKnownBadSOC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L68-L77)). If one matches, it returns `kOpenGL` without loading Vulkan. Otherwise it loads `libvulkan.so`, creates the `VkInstance`, `VkDevice` and `VkQueue`, and returns `kVulkan`. `AndroidContextDynamicImpeller` does the same work on the raster thread today, so the UI thread never waits for it, and nothing has to start earlier on a `FlutterLoader` executor. A process-wide singleton (`VulkanDeviceOwner`) holds the device, so the callbacks of later [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) instances return the same handles without probing again. + +**When the engine rejects a configuration.** Android falls back to OpenGL ES today when Vulkan context creation fails or the driver is on Impeller's known-bad list ([`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.cc#L65-L69)). In the embedder path, [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc#L176-L188) still checks the embedder's device, but a failure leaves the engine without a renderer. For a `kDeferred` configuration the engine keeps Android's fallback: + +1. It validates the returned configuration and creates the rendering context from it. +2. It applies Impeller's known-bad driver check ([`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)). Explicit `kVulkan` configurations from other embedders keep their current behavior. +3. If either step fails, it releases what it created from that configuration and calls the callback again with the reason. The engine never takes ownership of embedder-provided Vulkan handles, so the Android callback destroys its `VkDevice` and returns `kOpenGL`. + +The driver blocklist stays in one place inside Impeller, and `embedder.h` needs no driver query function. + +The callback comes with these rules: + +- **Threading.** The callback runs on the raster task runner. It may block, for example while it creates the Vulkan device, but it must not wait on the platform thread, because `FlutterEngineDeinitialize` and `FlutterEngineShutdown` wait for it. No other call waits for it. The engine reads the rendering context only on the raster and IO threads, and it creates its rendering surface after resolution (see the next section). +- **Failure.** If the callback returns `false`, or the engine rejects every configuration, the engine runs without a renderer and the isolate keeps running. Frames are dropped before any backing store is requested. +- **Validation.** `FlutterEngineInitialize` checks only that `resolve_callback` is set. The checks it runs today on a concrete configuration, such as required callbacks being present and the backend being compiled in, run on the raster thread after resolution. Context-creation failures are only logged today, and `FlutterEngineRunInitialized` reports success either way. +- **External textures.** `FlutterEngineRegisterExternalTexture` may be called after `FlutterEngineRunInitialized` returns while `resolve_callback` is still running on the raster thread (textures registered before `FlutterEngineRunInitialized` are queued by `FlutterEmbedderNative` and flushed after `FlutterEngineRunInitialized`; see [Alternative Considered](#alternative-considered-splitting-startup-into-separate-shell-and-isolate-launch-apis)). Registration only records the texture ID. The engine binds it to the resolved configuration's frame callback on the raster thread, where every texture operation already runs after `CreateImpellerContext`. +- **Spawn.** Spawned engines share the parent's task runners ([`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L718-L722)), so the parent's resolution runs first. A spawned engine reuses the resolved configuration and rendering context, and the callback is not called for it. `FlutterEngineSpawn` may be called before the parent has resolved. +- **Settings.** The resolved type must be valid for the engine's settings. Under Impeller, Android chooses only between `kVulkan` and `kOpenGL`. #### Deferring `impeller::Context` and Surface Creation in `shell/platform/embedder/` (`SetupImpellerContext`) -To keep [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L106) and [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L27-L30) on the raster thread instead of running them in the [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) constructors on the UI thread, and to prevent [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) from blocking on [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) before a window surface exists, [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder) is updated as follows: +To keep [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L106) and [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L27-L30) on the raster thread instead of running them in the [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) constructors on the UI thread, and to keep [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) from waiting for the raster thread, [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder) is updated as follows: - [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) adds `virtual void SetupImpellerContext() {}`. -- [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) overrides `PlatformView::SetupImpellerContext()` and forwards the call to `embedder_surface_->SetupImpellerContext()`. +- [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) overrides `PlatformView::SetupImpellerContext()`. For a `kDeferred` configuration it calls `resolve_callback`, validates the result, builds the `EmbedderSurface` and external texture resolver for the resolved type, and creates the context, retrying as described above. For a concrete configuration it forwards the call to `embedder_surface_->SetupImpellerContext()`. - [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L20-L95) validates the `VulkanDispatchTable` callbacks and stores `impeller::ContextVK::Settings` (including `EmbedderData` handles and extension lists) in its constructor without creating the Impeller context. It overrides `SetupImpellerContext()` to run `context_ = impeller::ContextVK::Create(std::move(settings_))` on the raster thread when `Shell::CreateWithSnapshot` executes the `CreateImpellerContext` task. - [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L77-L132) likewise validates `gl_dispatch_table_` in its constructor and moves `gl_make_current_callback()`, `ProcTableGLES` creation, `impeller::ContextGLES::Create(...)`, and `gl_clear_current_callback()` into `EmbedderSurfaceGLImpeller::SetupImpellerContext()` on the raster thread. -- [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) records `SAFE_ACCESS(args, defer_surface_creation, false)` during [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2023-L2499). When `defer_surface_creation` is `true`, [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2501-L2537) executes `LaunchShell()` and `RunRootIsolate()` and skips the implicit `NotifyCreated()` call, leaving surface creation to `FlutterEngineNotifyCreated`. +- `FlutterEngineRunInitialized` continues to call `embedder_engine->NotifyCreated()` (`platform_view->NotifyCreated()`) internally unchanged. [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L594) becomes `virtual`, and `PlatformViewEmbedder::NotifyCreated()` queues rendering surface creation on the FIFO raster task runner without waiting on a latch. That task runs right after `CreateImpellerContext`: it calls `CreateRenderingSurface()`, `Rasterizer::Setup`, and asks the UI thread to schedule a frame. Frames requested before then are dropped quietly, as they are today before a surface exists ([`rasterizer.cc#L523-L524`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L523-L524)). The latches in `PlatformView::NotifyCreated()` ([`platform_view.cc#L66-L79`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L66-L79)) and [`Shell::OnPlatformViewCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1023-L1054) only existed to keep `platform_view` alive while the raster and IO tasks ran and to create the IO resource context; [`Shell::~Shell()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L634-L673) already drains the raster task runner before destroying `platform_view_`, and the IO thread already creates its resource context during shell setup ([`shell.cc#L386-L391`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L386-L391)). ```cpp // In shell/platform/embedder/platform_view_embedder.cc void PlatformViewEmbedder::SetupImpellerContext() { - if (embedder_surface_ != nullptr) { - embedder_surface_->SetupImpellerContext(); + if (!deferred_config_) { + if (embedder_surface_ != nullptr) { + embedder_surface_->SetupImpellerContext(); + } + return; + } + FlutterRendererResolveInfo info = {}; + info.struct_size = sizeof(FlutterRendererResolveInfo); + while (true) { + FlutterRendererConfig config = {}; + if (!deferred_config_->resolve_callback(user_data_, &info, &config)) { + FML_LOG(ERROR) << "The embedder could not provide a renderer."; + return; // The engine runs without a renderer. + } + // Validates the configuration, then builds the EmbedderSurface and the + // external texture resolver for its type. + FlutterRendererRejectReason reason = BuildEmbedderSurface(config); + if (reason == kFlutterRendererRejectReasonNone) { + embedder_surface_->SetupImpellerContext(); + // Context creation, then Impeller's known-bad driver check for Vulkan. + reason = CheckImpellerContext(); + } + if (reason == kFlutterRendererRejectReasonNone) { + return; + } + embedder_surface_.reset(); // Never destroys embedder-provided handles. + info.attempt++; + info.rejected_type = config.type; + info.reject_reason = reason; } } @@ -371,6 +462,13 @@ std::shared_ptr PlatformViewEmbedder::GetImpellerContext() return embedder_surface_->CreateImpellerContext(); } +void PlatformViewEmbedder::NotifyCreated() { + // Called by FlutterEngineRunInitialized on the platform thread. + // Queues surface creation behind CreateImpellerContext on the FIFO raster + // task runner and returns without blocking the platform thread on a latch. + delegate_.OnPlatformViewCreatedAsync(); +} + // In shell/platform/embedder/embedder_surface_vulkan_impeller.cc void EmbedderSurfaceVulkanImpeller::SetupImpellerContext() { if (context_ || !valid_) { @@ -383,58 +481,33 @@ void EmbedderSurfaceVulkanImpeller::SetupImpellerContext() { } } -// In shell/platform/embedder/embedder.cc -// Inside FlutterEngineInitialize: -bool defer_surface_creation = - SAFE_ACCESS(args, defer_surface_creation, false); - -auto embedder_engine = std::make_unique( - std::move(thread_host), // - std::move(task_runners), // - std::move(settings), // - std::move(run_configuration), // - on_create_platform_view, // - on_create_rasterizer, // - std::move(external_texture_resolver), // - defer_surface_creation // -); - -// Inside FlutterEngineRunInitialized: -FlutterEngineResult FlutterEngineRunInitialized( - FLUTTER_API_SYMBOL(FlutterEngine) engine) { - if (!engine) { - return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); - } - - auto embedder_engine = reinterpret_cast(engine); - if (embedder_engine->IsValid()) { - return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); - } - - // Step 1: Launch the shell (posts CreateImpellerContext to the raster thread). - if (!embedder_engine->LaunchShell()) { - return LOG_EMBEDDER_ERROR(kInvalidArguments, - "Could not launch the engine using supplied " - "initialization arguments."); - } - - // Step 2: Tell the platform view to initialize itself unless the embedder - // defers surface creation until FlutterEngineNotifyCreated. - if (!embedder_engine->DeferSurfaceCreation() && - !embedder_engine->NotifyCreated()) { - return LOG_EMBEDDER_ERROR(kInternalInconsistency, - "Could not create platform view components."); - } - - // Step 3: Launch the root isolate. - if (!embedder_engine->RunRootIsolate()) { - return LOG_EMBEDDER_ERROR( - kInvalidArguments, - "Could not run the root isolate of the Flutter application using the " - "project arguments specified."); - } - - return kSuccess; +// In shell/common/shell.cc +// Called on the platform thread by PlatformViewEmbedder::NotifyCreated(). +// Returns without waiting; the raster task runs after CreateImpellerContext +// because the raster task runner is FIFO. +void Shell::OnPlatformViewCreatedAsync() { + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetRasterTaskRunner(), + [platform_view = platform_view_.get(), + rasterizer = rasterizer_->GetWeakPtr(), + engine = engine_->GetWeakPtr(), + ui_task_runner = task_runners_.GetUITaskRunner(), + &waiting_for_first_frame = waiting_for_first_frame_]() { + // ~Shell drains the raster task runner before destroying platform_view_. + std::unique_ptr surface = + platform_view->CreateRenderingSurface(); + if (!rasterizer || !surface || !surface->IsValid()) { + FML_LOG(ERROR) << "Could not create the rendering surface."; + return; + } + rasterizer->Setup(std::move(surface)); + waiting_for_first_frame.store(true); + fml::TaskRunner::RunNowOrPostTask(ui_task_runner, [engine]() { + if (engine) { + engine->ScheduleFrame(); + } + }); + }); } ``` @@ -442,42 +515,36 @@ FlutterEngineResult FlutterEngineRunInitialized( sequenceDiagram autonumber participant UI as "Android UI Thread" - participant Bg as "FlutterLoader Executor" - participant Emb as "PlatformViewEmbedder & EmbedderSurface" + participant Emb as "embedder.cc & PlatformViewEmbedder" participant Raster as "Engine Raster Thread" - - UI->>Bg: FlutterLoader.startInitialization() (loadLibrary) - Note over Bg: After loadLibrary, initTask spawns
non-blocking executorService probe - par Background Vulkan Probe - Bg->>Bg: Stage 1: check system properties & SoC deny-list - Bg->>Bg: Create VkInstance & read VkPhysicalDeviceProperties - Bg->>Emb: FlutterEngineQueryVulkanDriverSupport(props, &is_known_bad) - Emb-->>Bg: kSuccess (is_known_bad == false) - Bg->>Bg: Create VkDevice & VkQueue (or fall back to EGL if known-bad) - and UI Thread Continues - UI->>UI: ensureInitializationComplete() + FlutterEngine. + initial channels - end - UI->>Bg: Join Vulkan probe future before FlutterEngineInitialize - Bg-->>UI: Resolved backend & device handles - UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig, defer_surface_creation=true) - UI->>Emb: FlutterEngineRunInitialized() -> LaunchShell() + RunRootIsolate() - Emb->>Emb: Construct EmbedderSurfaceVulkanImpeller (store settings_ only) - Emb-)Raster: Post CreateImpellerContext task (no NotifyCreated latch wait) - Emb-->>UI: Return without waiting for ContextVK - par Raster Thread Initializes Impeller Context - Raster->>Emb: PlatformViewEmbedder::SetupImpellerContext() - Emb->>Emb: EmbedderSurfaceVulkanImpeller::SetupImpellerContext() - Emb->>Emb: context_ = impeller::ContextVK::Create(std::move(settings_)) - Raster->>Emb: PlatformViewEmbedder::GetImpellerContext() - Emb-->>Raster: context_ + participant Cb as "Android resolve_callback" + + UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig{kDeferred}) + UI->>Emb: FlutterEngineRunInitialized() + Emb-)Raster: LaunchShell() posts CreateImpellerContext + Emb-)Raster: NotifyCreated() queues surface creation + Emb->>Emb: RunRootIsolate() + Emb-->>UI: Return without waiting for the raster thread + par Raster thread resolves the renderer + Raster->>Cb: resolve_callback(attempt 0) + Cb->>Cb: System property and SoC checks, then VkInstance, VkDevice and VkQueue + Cb-->>Raster: kVulkan configuration + Raster->>Raster: Validate, build EmbedderSurfaceVulkanImpeller, ContextVK::Create + Raster->>Raster: Known-bad driver check + opt Configuration rejected + Raster->>Cb: resolve_callback(attempt 1, reject_reason) + Cb-->>Raster: kOpenGL configuration + Raster->>Raster: Build EmbedderSurfaceGLImpeller, ContextGLES::Create + end + Raster->>Raster: Create the rendering surface (context only), Rasterizer::Setup and UI Thread Inflates Activity View Hierarchy UI->>UI: Activity.onStart() / onResume() / WindowManager layout end - UI->>Emb: surfaceCreated(ANativeWindow*) -> FlutterEngineNotifyCreated(engine) - Emb->>Raster: CreateRenderingSurface() (ContextVK already initialized) + UI->>UI: surfaceCreated(ANativeWindow*): create swapchain, allow backing stores + UI->>Emb: FlutterEngineScheduleFrame(engine) ``` -Once the context is initialized, window surface attachments ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and swapchains are created and destroyed across Android [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events (`FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` - see next section) without recreating the `VkDevice`, `EGLContext`, or `impeller::Context`. +After that, the Android embedder creates and destroys `ANativeWindow` swapchains across Android [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events without recreating the `VkDevice`, `EGLContext`, or `impeller::Context`, and without a surface lifecycle call into the engine (see the next section). ### Renderer Availability and Surface Lifecycle @@ -487,75 +554,55 @@ Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/ref Static graphics configuration (the Vulkan device and queue handles, or the EGL context and callbacks) persists in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) for the lifetime of the engine, while presentation surfaces attach and detach across `Activity` lifecycle events. Recreating `FlutterRendererConfig` on every window change would re-initialize the GPU context and discard Impeller pipeline caches. -In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89) directly as a `PlatformView` subclass only when an `ANativeWindow` was attached or detached. Because `embedder.h` previously only invoked `NotifyCreated` and `NotifyDestroyed` internally inside `FlutterEngineRunInitialized` and `FlutterEngineDeinitialize`, [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) adds `bool defer_surface_creation` (instructing `FlutterEngineRunInitialized` not to call `NotifyCreated` prematurely when no window surface exists yet) and two surface lifecycle functions are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): +In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) calls [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89) as an `ANativeWindow` is attached or detached. Its `AndroidSurface` renders into that window, so the raster thread has to stop using the window before `surfaceDestroyed()` returns. -```c -// In FlutterProjectArgs: -/// When true, `FlutterEngineRunInitialized` launches the shell and root isolate -/// without calling `NotifyCreated`. The embedder must subsequently call -/// `FlutterEngineNotifyCreated` once a render surface or native window exists. -bool defer_surface_creation; +Under this RFC the engine never holds the window, because Android renders every frame through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) with `avoid_backing_store_cache` set to `true`: -/// Notify the engine that the render surface or native window was created. -/// -/// Must be called on the platform thread after the embedder's window surface -/// exists and is ready for the raster thread to acquire images or framebuffers. -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineNotifyCreated( - FLUTTER_API_SYMBOL(FlutterEngine) engine); +- When a compositor is installed, the engine's own GPU surface holds only the rendering context and presents nothing. [`EmbedderSurfaceGLImpeller::CreateGPUSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L203-L214) passes `render_to_surface = false` in that case, and [#193000](https://github.com/flutter/flutter/pull/193000) made `EmbedderSurfaceVulkanImpeller` do the same. +- The engine requests every backing store inside the frame that uses it ([`embedder_external_view_embedder.cc#L561-L572`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L561-L572)). With the cache off, it collects each one as soon as `present_view_callback` returns ([`#L627-L643`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L627-L643)). +- If `create_backing_store_callback` returns `false`, the engine skips that layer ([`#L261-L265`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L261-L265)) and still presents the frame's other layers ([`#L465-L468`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L465-L468)). -/// Notify the engine that the render surface or native window is being -/// destroyed. -/// -/// Must be called on the platform thread before the embedder releases the -/// underlying window surface. This call blocks until the raster thread has -/// torn down the on-screen render surface and the IO thread has drained -/// pending GPU resource deletions. -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineNotifyDestroyed( - FLUTTER_API_SYMBOL(FlutterEngine) engine); -``` +So `embedder.h` gains no surface lifecycle functions. The embedder owns the window's lifetime through the compositor. The engine keeps its context-only rendering surface from resolution until shutdown, and [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) releases the transient GPU caches inside it (see [Releasing Transient Rendering Caches (`FlutterEngineNotifyLowMemoryWarning`)](#releasing-transient-rendering-caches-flutterenginenotifylowmemorywarning)). -In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc), `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` delegate directly to `EmbedderEngine::NotifyCreated` and `EmbedderEngine::NotifyDestroyed`: +#### Window Ownership Contract -```cpp -FlutterEngineResult FlutterEngineNotifyCreated( - FLUTTER_API_SYMBOL(FlutterEngine) engine) { - if (!engine) { - return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); - } - auto embedder_engine = reinterpret_cast(engine); - if (!embedder_engine->NotifyCreated()) { - return LOG_EMBEDDER_ERROR(kInternalInconsistency, - "Could not create platform view components."); - } - return kSuccess; -} +When `avoid_backing_store_cache` is `true`, the engine logs a refused backing store at a low level instead of as an error ([`embedder.cc#L1406-L1408`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L1406-L1408)). -FlutterEngineResult FlutterEngineNotifyDestroyed( - FLUTTER_API_SYMBOL(FlutterEngine) engine) { - if (!engine) { - return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); - } - auto embedder_engine = reinterpret_cast(engine); - if (!embedder_engine->NotifyDestroyed()) { - return LOG_EMBEDDER_ERROR(kInternalInconsistency, - "Could not destroy platform view components."); - } - return kSuccess; -} -``` +The Android embedder (`AndroidSurfaceControl`) follows these rules: + +- Because the Android OS owns the underlying `Surface` (`BufferQueue` / `SurfaceFlinger`) and can invalidate it or dispatch `surfaceDestroyed` while a raster frame is in flight, the embedder cannot guarantee that the window remains valid from `create_backing_store_callback` through `present_view_callback`. When no window is attached or acquiring or presenting a buffer fails (such as `VK_ERROR_SURFACE_LOST_KHR`, `VK_ERROR_OUT_OF_DATE_KHR`, or `EGL_BAD_SURFACE`), `create_backing_store_callback` returns `false` (or `present_view_callback` drops the presentation) and `collect_backing_store_callback` releases any per-frame resources. +- On OpenGL ES, the engine calls `make_current` at the start of every frame and outside frames, and doesn't call `clear_current` after each frame. `make_current` therefore binds a pbuffer or a surfaceless context, and the embedder binds the window surface only while it hands out the root backing store (`is_root_layer`). +- On Vulkan, the GPU has to finish with presented images before the swapchain is destroyed, so the embedder waits for its last present to complete first. -> [!NOTE] **New Surface Lifecycle Exports** -> `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` are new additions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) exposing [`EmbedderEngine::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.cc#L124-L131) and [`EmbedderEngine::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.cc#L133-L140) ([`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89)). Setting `FlutterProjectArgs.defer_surface_creation = true` prevents [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) from calling [`NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) before the first `SurfaceHolder.Callback.surfaceCreated` event. +Embedders that render without a compositor also get only per-frame handles from the engine, through `make_current`/`fbo_callback`/`present` or `get_next_image_callback`/`present_image_callback`, and can release their window the same way. Refusing in those callbacks fails the whole frame instead of one layer. -#### Lifecycle Mapping and Synchronous Destruction Contract +#### Lifecycle Mapping -These functions map to Android [`SurfaceHolder.Callback`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback) events on the platform thread: +The Android embedder maps [`SurfaceHolder.Callback`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback) events on the platform thread as follows: -- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder binds the [`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window) and swapchain surface first, then calls `FlutterEngineNotifyCreated` so the rasterizer can allocate its GPU surface and begin rendering. +- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder creates its swapchain (or `SurfaceControl` tree) for the [`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window) and starts handing out backing stores. It re-arms its first-frame callback with [`FlutterEngineSetNextFrameCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3638-L3641), which `PlatformViewAndroid::NotifyCreated` does internally today, and calls [`FlutterEngineScheduleFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3620-L3621). - [`surfaceChanged`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder,%20int,%20int,%20int)): The embedder sends the updated physical dimensions and device pixel ratio via `FlutterEngineSendWindowMetricsEvent`. -- [`surfaceDestroyed`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)): The embedder calls `FlutterEngineNotifyDestroyed` before releasing the `ANativeWindow`. Inside the engine, [`Shell::OnPlatformViewDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1066-L1129) posts teardown to the raster thread, chains an unref-queue drain on the IO thread, and waits on a latch before returning to the platform thread. Only after `FlutterEngineNotifyDestroyed` returns does the embedder destroy its swapchain and release the `ANativeWindow` back to the OS, preventing `SIGSEGV` or `EGL_BAD_NATIVE_WINDOW` errors from in-flight raster work. +- [`surfaceDestroyed`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)): The embedder stops handing out new backing stores, synchronizes with any active callback on the raster thread, destroys its swapchain, and releases the `ANativeWindow`. There is no engine teardown to wait for. If no other view of the engine is visible, it calls the existing [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554), which releases the same engine GPU caches that `NotifyDestroyed` frees today. + +#### Releasing Transient Rendering Caches (`FlutterEngineNotifyLowMemoryWarning`) + +`avoid_backing_store_cache` covers only the backing stores. Impeller also keeps GPU caches of its own inside the engine's rendering surface, in the `ContentContext` that its `AiksContext` owns ([`content_context.h#L256-L261`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/entity/contents/content_context.h#L256-L261), [`#L309-L323`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/entity/contents/content_context.h#L309-L323)): an offscreen render-target cache for save layers and filters (separate from the backing-store cache), cached `DlImage` textures, glyph atlases, the text shadow cache, and per-frame host buffers. The engine allocates these itself; they never go through `create_backing_store_callback`, so the embedder cannot free them directly. + +[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) already provides [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) ([`embedder.cc#L3597-L3617`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3597-L3617)): + +```c +// Existing API in shell/platform/embedder/embedder.h (L3535-L3554) +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineNotifyLowMemoryWarning( + FLUTTER_API_SYMBOL(FlutterEngine) engine); +``` + +Today, `FlutterEngineNotifyLowMemoryWarning` calls [`Shell::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L750-L770) (which notifies the Dart VM and posts [`Rasterizer::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L173-L192) to the raster thread) and sends the `{"type": "memoryPressure"}` message on the `flutter/system` channel. Since [#193497](https://github.com/flutter/flutter/pull/193497), `Rasterizer::NotifyLowMemoryWarning()` calls `aiks_context->ClearRenderTargetCache()` and `aiks_context->ClearCachedTextures()`, leaving the glyph atlases, text shadow cache, and per-frame host buffers in place. + +Under this RFC, `Rasterizer::NotifyLowMemoryWarning()` is extended so that it also clears `ContentContext`'s glyph atlases, text shadow cache, and per-frame host buffers (and collects any cached compositor backing stores) while keeping `impeller::Context` and its pipeline caches intact. No new C-API function is needed in `embedder.h`: + +- When Android dispatches [`ComponentCallbacks2.onTrimMemory`](https://developer.android.com/reference/android/content/ComponentCallbacks2#onTrimMemory(int)) (including `TRIM_MEMORY_UI_HIDDEN = 20`, which satisfies [`level >= TRIM_MEMORY_RUNNING_LOW`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java#L1043-L1047) in `FlutterActivityAndFragmentDelegate.onTrimMemory`), [`FlutterJNI.notifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1401-L1407) invokes `FlutterEngineNotifyLowMemoryWarning(engine)`. +- When the last view of an engine detaches in `surfaceDestroyed`, `AndroidSurfaceControl` also calls `FlutterEngineNotifyLowMemoryWarning(engine)` so that cached add-to-app engines whose host app stays in the foreground release the same GPU caches that `NotifyDestroyed` frees today. #### Alternative Considered: Splitting Startup into Separate Shell and Isolate Launch APIs @@ -588,31 +635,44 @@ FlutterEngineResult FlutterEngineLaunchEngine( const FlutterEngineRunConfiguration* config); ``` +
+ Under this alternative, [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) called [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) (with `defer_root_isolate_launch = true` and `initial_lifecycle_state = "AppLifecycleState.detached"`) followed by `FlutterEngineLaunchShell`, and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) later called `FlutterEngineLaunchEngine` with `FlutterEngineRunConfiguration`. The motivation was to mirror legacy [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) startup ordering so that platform messages (`flutter/localization`, `flutter/navigation` `setInitialRoute`, `flutter/settings`), viewport metrics, and external textures sent between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`executeDartEntrypoint()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java#L133-L143) could be buffered directly inside the engine's [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52), and so [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) could post [`CreateImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L25) to the raster thread during [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442). -This alternative was rejected in favor of keeping the existing two-phase [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) sequence (paired with `defer_surface_creation` and `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`) for four reasons: +Why does pre-run state exist in the two-phase [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) + [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) model? In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2301-L2537), `FlutterEngineInitialize` takes [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) (which includes the Dart entrypoint and asset resolvers) and constructs `EmbedderEngine` with `shell_ == nullptr`, while `FlutterEngineRunInitialized` calls both `LaunchShell()` (`Shell::Create`) and `RunRootIsolate()`. Every [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.cc#L143-L195) method (`SendPlatformMessage`, `SetViewportMetrics`, `RegisterTexture`) checks `IsValid()` (`shell_ != nullptr`) and fails before `FlutterEngineRunInitialized` runs. On Android, the [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L408-L433) constructor calls `attachToJni()` (`attachToNative()`), sends initial locales (`flutter/localization`), and registers plugins (`GeneratedPluginRegister.registerGeneratedPlugins`) **before** [`DartExecutor.executeDartEntrypoint()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java#L133-L143) calls [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) with the entrypoint and asset arguments. Because `FlutterEngineInitialize` and `FlutterEngineRunInitialized` run in `runBundleAndSnapshotFromLibrary()`, `FlutterEmbedderNative` records messages, viewport metrics, and texture IDs sent after `attachToNative()` and flushes them as soon as `FlutterEngineRunInitialized` returns. + +This alternative was rejected in favor of keeping the existing two-phase [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) sequence (paired with `kDeferred` and non-blocking `NotifyCreated()`) for three reasons: -1. **Avoids a fragile four-stage initialization state machine in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)**: [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) already separates startup into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) so embedders can obtain the [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and bind custom task runners before any tasks are posted. Splitting startup further into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) → `FlutterEngineLaunchShell` → `FlutterEngineLaunchEngine` → `FlutterEngineNotifyCreated` introduces intermediate partially-initialized [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) states, duplicates [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`assets_path`, [`custom_dart_entrypoint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2687), [`dart_entrypoint_argv`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2765), `custom_asset_resolvers`) into `FlutterEngineRunConfiguration`, and requires seeding `initial_lifecycle_state` to keep the shell from scheduling frames before an isolate exists. -2. **Preserves a larger overlap window for the background Vulkan probe**: Because [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1042-L1055) requires a resolved backend (`kVulkan` or `kOpenGL`) and live `VkInstance`/`VkDevice` handles when [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) is called, invoking [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) earlier in [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) forces the UI thread to join the background [`FlutterLoader`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L41) Vulkan probe earlier. Calling [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) together in [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) allows the background Vulkan probe to overlap with [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) Java subsystem construction, plugin registration, and initial system channel configuration. -3. **Pre-run buffering is self-contained in `FlutterEmbedderNative`**: Queueing platform messages, initial route, viewport metrics, and external texture registrations in `FlutterEmbedderNative` between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)—and flushing them immediately after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007)—requires no new [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) entry points or [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) lifecycle flags. -4. **`defer_surface_creation` already moves [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) off the UI thread**: Because [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) executes during `Activity.onCreate` / `onStart` well before WindowManager dispatches `SurfaceHolder.Callback.surfaceCreated`, skipping the implicit [`NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) call inside [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) already gives the raster thread the entire window between [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) and `surfaceCreated()` to complete [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) asynchronously. +1. **Avoids a multi-stage initialization state machine in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)**: [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) already separates startup into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) so embedders can obtain the [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and bind custom task runners before any tasks are posted. Splitting startup further into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) → `FlutterEngineLaunchShell` → `FlutterEngineLaunchEngine` introduces intermediate partially-initialized [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) states, duplicates [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`assets_path`, [`custom_dart_entrypoint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2687), [`dart_entrypoint_argv`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2765), `custom_asset_resolvers`) into `FlutterEngineRunConfiguration`, and requires seeding `initial_lifecycle_state` to keep the shell from scheduling frames before an isolate exists. +2. **Pre-run buffering is self-contained in `FlutterEmbedderNative`**: Queueing platform messages, initial route, viewport metrics, and external texture registrations in `FlutterEmbedderNative` between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)—and flushing them immediately after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007)—requires no new [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) entry points or [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) lifecycle flags. +3. **Non-blocking `NotifyCreated()` already keeps [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) off the UI thread**: [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) executes during `Activity.onCreate` / `onStart`, well before WindowManager dispatches `SurfaceHolder.Callback.surfaceCreated`, and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) queues surface creation on the raster thread without waiting. The raster thread therefore has the whole interval until the first frame to resolve the renderer and create the context. -| Dimension | Proposed: Two-Phase Init ([`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) + [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) + `defer_surface_creation`) | Alternative: Split Shell & Isolate Launch (`FlutterEngineLaunchShell` + `FlutterEngineLaunchEngine`) | +With `kDeferred`, the UI thread doesn't wait for backend selection in either design. The remaining startup difference is when context creation starts: at `runBundleAndSnapshotFromLibrary()` here, and at `attachToNative()` in the alternative. The startup benchmarks in [DeviceLab Benchmarks](#devicelab-benchmarks) measure whether that changes time to first frame on low-end devices. + +| Dimension | Proposed: Two-Phase Init ([`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) + [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) + `kDeferred`) | Alternative: Split Shell & Isolate Launch (`FlutterEngineLaunchShell` + `FlutterEngineLaunchEngine`) | | :--- | :--- | :--- | -| **Public C-API Surface ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h))** | **Pro:** Reuses existing [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007); adds only `bool defer_surface_creation` on [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) plus `FlutterEngineNotifyCreated` / `FlutterEngineNotifyDestroyed`. | **Con:** Adds two new functions (`FlutterEngineLaunchShell`, `FlutterEngineLaunchEngine`), a new `FlutterEngineRunConfiguration` struct duplicating [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields, and two [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`defer_root_isolate_launch`, `initial_lifecycle_state`). | -| **[`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) State Machine** | **Pro:** Preserves the existing two-state model (initialized → running) used by all desktop embedders and `FlutterEngineSpawn`. | **Con:** Introduces a four-stage startup state machine (initialized → shell launched without isolate → isolate launched → surface created) that must also be mirrored in `FlutterEngineSpawnInfo`. | -| **Background Vulkan Probe Overlap (`VkInstance` + `VkDevice`)** | **Pro:** Longer overlap window—the UI thread does not join the [`FlutterLoader`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L41) Vulkan probe until [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089), overlapping the probe with [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) Java subsystem construction, plugin registration, and initial channel setup. | **Con:** Shorter overlap window—calling [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) inside [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) forces the UI thread to join the Vulkan probe before [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) Java construction and plugin registration run. | -| **Raster-Thread [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) Overlap ([`ContextVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L64) / [`ContextGLES`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L23))** | **Pro:** Runs asynchronously on the raster thread across the entire [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) → `surfaceCreated()` window without blocking the UI thread. | **Pro:** Starts slightly earlier (at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) rather than [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)), though both complete well before `surfaceCreated()`. | -| **Pre-Run State (`flutter/localization`, `setInitialRoute`, `flutter/settings`, Textures)** | **Con:** `FlutterEmbedderNative` must queue messages, viewport metrics, and external textures sent between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) and flush them right after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007).
**Pro:** Keeps pre-run queueing internal to the Android embedder without exposing [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) state in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). | **Pro:** [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) and [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) exist at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442), so pre-run platform messages and viewport metrics can be dispatched directly into the engine before the isolate launches.
**Con:** Requires `initial_lifecycle_state = "AppLifecycleState.detached"` in [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) to prevent premature frame scheduling while the shell has no isolate. | +| **Public C-API Surface ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h))** | **Pro:** Reuses existing [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007); adds the `kDeferred` renderer type and no lifecycle functions. | **Con:** Adds two new functions (`FlutterEngineLaunchShell`, `FlutterEngineLaunchEngine`), a new `FlutterEngineRunConfiguration` struct duplicating [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields, and two [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`defer_root_isolate_launch`, `initial_lifecycle_state`). | +| **[`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) State Machine** | **Pro:** Preserves the existing two-state model (initialized → running) used by all desktop embedders and `FlutterEngineSpawn`. | **Con:** Introduces a three-stage startup state machine (initialized → shell launched without isolate → isolate launched) that must also be mirrored in `FlutterEngineSpawnInfo`. | +| **Raster-Thread [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) Overlap ([`ContextVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L64) / [`ContextGLES`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L23))** | **Pro:** Runs asynchronously on the raster thread across the entire [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) → `surfaceCreated()` window without blocking the UI thread. | **Pro:** Starts earlier (at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) rather than [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)). Whether that shortens time to first frame on low-end devices has not been measured. | +| **Pre-Run State (`flutter/localization`, `setInitialRoute`, `flutter/settings`, Textures)** | **Con:** Because `FlutterProjectArgs` requires the Dart entrypoint and asset arguments passed in [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) (and `EmbedderEngine` rejects calls until `FlutterEngineRunInitialized` creates the `Shell`), `FlutterEmbedderNative` must queue messages, viewport metrics, and external textures sent between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) and flush them right after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007).
**Pro:** Keeps pre-run queueing internal to the Android embedder without exposing [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) state in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). | **Pro:** [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) and [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) exist at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442), so pre-run platform messages and viewport metrics can be dispatched directly into the engine before the isolate launches.
**Con:** Requires `initial_lifecycle_state = "AppLifecycleState.detached"` in [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) to prevent premature frame scheduling while the shell has no isolate. | +
### Replacing Internal [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) with `FlutterCompositor` (Preserving Hybrid Composition) -Android supports four platform view composition modes: +To eliminate the internal C++ [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) subclasses while preserving all four Android platform view composition modes, this RFC routes platform views through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) and the external texture C API with three targeted `FlutterCompositor` additions: + +- `FlutterBackingStoreConfig.is_root_layer` identifies whether a requested backing store is the bottom-most Flutter layer beneath all platform views (so the embedder can return the primary on-screen surface) or an overlay layer above a platform view (so the embedder allocates a translucent overlay buffer). +- `FlutterCompositor.merge_raster_thread_on_platform_views` is an opt-in flag that dynamically merges the raster task runner onto the platform thread during Hybrid Composition (HC) frames so Java [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L54) requires no modifications. +- `FlutterBackingStorePresentInfo.synchronization_fence_fd` transfers the GPU completion fence file descriptor to the embedder for Hybrid Composition++ (HCPP) [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits. -1. **Hybrid Composition (HC)**: Places the native Android [`View`](https://developer.android.com/reference/android/view/View) directly in the view hierarchy (`createForPlatformViewLayer`) and composites Flutter UI layers above the platform view into Android overlay surfaces (`FlutterImageView` / `PlatformViewWrapper` via `createOverlaySurface` and `onDisplayOverlaySurface`). -2. **Hybrid Composition++ (HCPP)**: Uses NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU acquire/release sync fences on Android 14+ (API level 34+) (`createPlatformViewHcpp`). -3. **Texture Layer Hybrid Composition (TLHC)**: Attaches the Android `View` to the view hierarchy ([`PlatformViewWrapper`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L40)) while redirecting its draw output into an offscreen [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) or [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) render target registered as a Flutter external texture (`createForTextureLayer` / `configureForTextureLayerComposition`). -4. **Virtual Display (VD)**: Hosts the Android `View` inside an offscreen [`VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay) (`VirtualDisplayController`) backed by a `TextureRegistry` render target (`SurfaceProducer` or `SurfaceTexture`) and composites its output as a Flutter external texture (`createForTextureLayer` / `configureForVirtualDisplay`). +Android's four platform view composition modes and their mechanisms map to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) as follows: + +| Mode | Android Mechanism | Flutter UI Above View | `embedder.h` Boundary | +| :--- | :--- | :--- | :--- | +| **Hybrid Composition (HC)** | Places the native [`View`](https://developer.android.com/reference/android/view/View) directly in the view hierarchy (`createForPlatformViewLayer`) and converts the root surface to [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) ([`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)). | Rendered into [`PlatformViewWrapper`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L40) overlay surfaces (`createOverlaySurface` / `onDisplayOverlaySurface`) on the merged platform/raster thread. | `FlutterCompositor` (`kFlutterLayerContentTypePlatformView`, `is_root_layer`, `merge_raster_thread_on_platform_views = true`) | +| **Hybrid Composition++ (HCPP)** | Places the native `View` in the view hierarchy (`createPlatformViewHcpp`) and composites layers in [`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager) via NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU sync fences (API 34+, Vulkan). | Rendered into [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) swapchain slices attached to overlay `SurfaceControl` nodes on unmerged threads. | `FlutterCompositor` (`kFlutterLayerContentTypePlatformView`, `is_root_layer`, `synchronization_fence_fd`) | +| **Texture Layer Hybrid Composition (TLHC)** | Attaches the native `View` to `PlatformViewWrapper` while redirecting its draw output into an offscreen [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) or [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`configureForTextureLayerComposition`). | Rendered directly in the single Flutter scene around the `Texture` widget. | External texture C API (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`) | +| **Virtual Display (VD)** | Hosts the native `View` inside an offscreen [`VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay) (`VirtualDisplayController`) backed by a `SurfaceProducer` or `SurfaceTexture` (`configureForVirtualDisplay`). | Rendered directly in the single Flutter scene around the `Texture` widget. | External texture C API (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`) | **All four platform view composition modes are preserved; retiring Hybrid Composition (HC) or Virtual Display (VD) is not an option.** @@ -647,7 +707,10 @@ At the C-API boundary ([`embedder.h`](https://github.com/flutter/flutter/blob/47 - **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L643-L685) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanTextureFrameCallback`, and `FlutterOpenGLTextureFrameCallback`) without creating `FlutterPlatformView` layers. - **Hybrid Composition (HC) and Hybrid Composition++ (HCPP) via [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322)**: The engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). `AndroidCompositor` and `AndroidPlatformViewsController` drive both HC and HCPP over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: -1. **Backing Store Allocation ([`create_backing_store_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2284))**: [`FlutterBackingStoreConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2160-L2168) is extended with `bool is_root_layer;` (and [`FlutterCompositor.avoid_backing_store_cache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2312) is set to `true` because the engine's render-target cache keys only on size). When `config->is_root_layer` is `true`—meaning the layer is the bottom-most layer of the frame for `view_id` with no platform view composited beneath it—`AndroidCompositor::CreateBackingStore` returns the main surface render target (`GetFBO()` on OpenGL ES, or the primary swapchain image on Vulkan). When a Hybrid Composition platform view splits the scene and Flutter renders UI layers above a platform view (`is_root_layer == false`), `CreateBackingStore` allocates an offscreen backing store (`AcquireOffscreenFBO` / overlay `AHardwareBuffer`): +1. **Backing Store Allocation ([`create_backing_store_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2284))**: `create_backing_store_callback` is invoked during layer rasterization *before* `present_view_callback` receives the ordered `FlutterLayer` list, and [`FlutterBackingStoreConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2160-L2168) currently passes only `size` and `view_id`: + - An overlay layer rendered above a full-screen platform view has the exact same `size` and `view_id` as the primary window. + - When a platform view sits at the bottom of the scene with no Flutter background behind it, the first backing store requested in the frame is an overlay (`is_root_layer == false`), not the root surface. + - Adding `bool is_root_layer` to `FlutterBackingStoreConfig` (with [`FlutterCompositor.avoid_backing_store_cache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2312) set to `true` because the engine's render-target cache keys only on size) distinguishes the bottom-most background slice from overlay slices at allocation time so the embedder can back the root slice with the primary `ANativeWindow` surface (`GetFBO()` on OpenGL ES, or the primary swapchain image on Vulkan) and allocate a translucent overlay buffer ([`PlatformViewWrapper`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L40) or child [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl)) for layers above a platform view: ```c typedef struct { @@ -677,35 +740,60 @@ At the C-API boundary ([`embedder.h`](https://github.com/flutter/flutter/blob/47 - The base [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173) is presented to the primary `ANativeWindow`. - Each [`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175) layer forwards the view identifier, physical bounds (`offset`, `size`), and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) stack to `AndroidPlatformViewsController::OnDisplayPlatformView` ([`PlatformViewsController.onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289) in Java). - Any [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173) layer positioned above a platform view acquires an Android overlay surface ([`createOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1402-L1425) / `GetOverlayWindow`), blits and swaps the offscreen FBO into that overlay window (`BlitAndSwapOverlaySurface`), and updates the overlay geometry ([`onDisplayOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1291-L1309) / `showOverlaySurface`), followed by `OnEndFrame`. -3. **Removing [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder)**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting and HCPP `SurfaceControl` transactions through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) (while TLHC and VD operate through the C-API external texture pipeline) and capture per-frame layer and mutator state by value across threads, [`shell/platform/android/external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) ([`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34)) is deleted in Phase 4 without removing or degrading any of the four platform view modes. No [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) handles or internal flow types are exposed in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). +3. **Removing [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder)**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting and HCPP `SurfaceControl` transactions through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) (while TLHC and VD operate through the C-API external texture pipeline), [`shell/platform/android/external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) ([`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34)) is deleted in Phase 4 without removing or degrading any of the four platform view modes. No [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) handles or internal flow types are exposed in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). -#### Frame Synchronization on API 21–33 Without `fml::RasterThreadMerger` +#### Opt-In Dynamic Raster Thread Merging for Hybrid Composition (`merge_raster_thread_on_platform_views`) -On Android 5.0 through Android 13 (API levels 21–33), HCPP is unavailable because joining the Android View system's `SurfaceControl.Transaction` via NDK [`ASurfaceTransaction_fromJava`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_fromjava) requires API 34 (even though [`ASurfaceTransaction_setBuffer`](https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction_setbuffer) itself was added in API 29). Two runtime paths can still prevent a platform view from using Texture Layer Hybrid Composition (TLHC) on these API levels: +Hybrid Composition (HC) is used whenever HCPP is not active. HCPP (`enable_surface_control`) is opt-in ([`settings.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/settings.h#L237-L238)) and also requires Android 14+ (API level 34+) and Impeller Vulkan with external fence and semaphore file-descriptor extensions ([`platform_view_android.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L171-L174), [`external_view_embedder_wrapper.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_wrapper.cc#L33-L42)). When HCPP is not active, two runtime paths route a platform view to HC instead of Texture Layer Hybrid Composition (TLHC): -1. **Explicit Hybrid Composition ([`initExpensiveAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L194-L210))**: The plugin requests [`createForPlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L168-L177), routing directly to [`PlatformViewsController.configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L581-L585). -2. **Embedded Child `SurfaceView` or `TextureView` ([`VIEW_TYPES_REQUIRE_NON_TLHC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L68))**: When a plugin uses [`initAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L134-L152) ([`createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L194-L247)) and the embedded Android `View` hierarchy contains a `SurfaceView` or `TextureView` child whose out-of-band buffer queue bypasses `ViewParent.onDescendantInvalidated()`, [`PlatformViewsController.createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L194-L247) detects `!supportsTextureLayerMode` and branches by `request.displayMode`: - - **[`TEXTURE_WITH_VIRTUAL_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.java#L24) (Virtual Display / VD)**: Calls [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596-L638), hosting the view and its child `SurfaceView` inside an offscreen `VirtualDisplay` backed by a [`TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L19) render target. Frame synchronization is handled entirely on the GPU/raster thread as a standard C-API external texture ([`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227-L3229)). +1. **Explicit Hybrid Composition ([`initExpensiveAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L194-L210))**: The plugin calls [`createForPlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L168-L177), routing directly to [`PlatformViewsController.configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L581-L585). +2. **Embedded Child `SurfaceView` ([`VIEW_TYPES_REQUIRE_NON_TLHC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L68))**: When a plugin uses [`initAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L134-L152) ([`createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L194-L247)) and the embedded Android `View` hierarchy contains a `SurfaceView` child whose out-of-band buffer queue bypasses `ViewParent.onDescendantInvalidated()`, [`PlatformViewsController.createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L223-L246) branches by `request.displayMode`: + - **[`TEXTURE_WITH_VIRTUAL_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.java#L24) (Virtual Display / VD)**: Calls [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596-L638), hosting the view inside an offscreen `VirtualDisplay` backed by a [`TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L19) render target ([`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227-L3229)). - **[`TEXTURE_WITH_HYBRID_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.java#L28) (Hybrid Composition / HC)**: Calls [`configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L581-L585) and returns [`NON_TEXTURE_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java#L316) (`-2`), instructing the Dart framework to composite the view via a [`PlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/platform_view_layer.h#L13) ([`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175)). -When Hybrid Composition is active on API 21–33 without [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31), frame synchronization between the Flutter rasterizer and the Android `View` hierarchy is enforced through the following three mechanisms across `AndroidCompositor` and [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L54): +Rather than changing HC to run with the raster and platform threads unmerged (which would require cross-thread latches during `present_view_callback` ([`onEndFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391)), overlay allocation ([`createOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1402-L1425)), and the first-frame [`convertToImageView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1390) surface switch), [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) is extended with an opt-in `merge_raster_thread_on_platform_views` flag: -1. **[`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) (`ImageReader`) Target Conversion**: - Rendering the base Flutter layer into a [`FlutterSurfaceView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java#L35) (`SurfaceView`) during HC would present to `SurfaceFlinger` out of band from the Android `ViewRootImpl` UI traversal, causing visual lag against the [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32). When the first HC platform view is displayed ([`onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289)), [`PlatformViewsController.initializeRootImageViewIfNeeded()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1174-L1190) calls [`flutterView.convertToImageView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1397), switching the engine's primary `ANativeWindow` to an [`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)-backed `FlutterImageView`. Each overlay layer (`PlatformOverlayView`) is likewise backed by its own `ImageReader` `ANativeWindow`. -2. **One Platform-Thread Task per Frame and Raster-Thread Wait for [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347)**: - In the legacy architecture, [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) merged the raster thread onto the platform thread via [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) so that [`onBeginFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1311-L1319), base/overlay rendering and swaps in [`SubmitFlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc#L89-L229), inline [`onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289) and [`onDisplayOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1291-L1309) calls, and [`onEndFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) all executed inside one platform-thread task. `AndroidCompositor::PresentLayers` preserves that ordering without [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31): - - On the raster thread, `surface_manager_->Present()` and `surface_manager_->BlitAndSwapOverlaySurface(...)` first complete `eglSwapBuffers` / `vkQueuePresentKHR` into the root [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) and [`PlatformOverlayView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformOverlayView.java#L15) `ImageReader` `ANativeWindow` queues for frame N. - - `AndroidCompositor` captures frame N's platform view geometry (`offset`, `size`), value-copied `AndroidMutatorsStack`, and overlay rectangles by value and posts a single task (`PostTask`) to the platform thread that executes [`onBeginFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1311-L1319), every [`onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289), every [`onDisplayOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1291-L1309), and [`onEndFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) in order. Because the Android UI `Looper` runs one message at a time and `ViewRootImpl` schedules view traversals in a separate `Choreographer.CALLBACK_TRAVERSAL` callback, no Android view traversal can run mid-task and observe a partially applied frame (the same single-`PostTask` pattern used by [`AndroidExternalViewEmbedder2::SubmitFlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc#L89-L229)). This is purely a CPU-side `Looper` ordering mechanism; it does not use `synchronization_fence_fd`, which exists only on the HCPP `SurfaceControl.Transaction` path. - - When `platform_views_count > 0` and HCPP is not active (`!IsHcppEnabled()`), if `PresentLayers` was called on the raster thread (`!platform_runner->RunsTasksOnCurrentThread()`), the raster thread waits on a frame-completion latch ([`fml::AutoResetWaitableEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/synchronization/waitable_event.h#L28)) until the platform thread finishes [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) (polling a surface-attached flag that the platform thread clears before calling `FlutterEngineNotifyDestroyed` so teardown never deadlocks). Waiting on this latch is required because [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347) calls `ImageReader.acquireLatestImage()`, which drops older queued images; without the wait, the raster thread could queue frame N+1 before the platform thread runs frame N's task, pairing frame N+1's pixels with frame N's [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) geometry. - - Two mid-frame steps still perform a synchronous hop to the platform thread: allocating a new overlay surface ([`createOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1402-L1425)), and the first frame that displays an HC view ([`initializeRootImageViewIfNeeded`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1174-L1190) calling [`convertToImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1397)), where the root layer is blitted from an offscreen backing store into the newly created [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) `ImageReader` surface. -3. **`ImageReader` Gate in [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1347)**: - During [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391) on the platform thread, `flutterView.acquireLatestImageViewFrame()` and `overlayView.acquireLatestImage()` pull the newly queued GPU buffers and invalidate the `FlutterImageView` instances. `finishFrame()` checks `isFrameRenderedUsingImageReaders` (verifying that the converted root `FlutterImageView` and every active `PlatformOverlayView` acquired a valid image) before setting `parentView.setVisibility(View.VISIBLE)`. Consequently, the base `FlutterImageView`, the `FlutterMutatorView` (wrapping the native `View`), and all `PlatformOverlayView` layers are drawn together in a single `ViewRootImpl.performTraversals()` pass on the Android UI thread. When the last HC view is disposed (`currentFrameUsedPlatformViewIds.isEmpty()`), `onEndFrame()` invokes `flutterView.revertImageView()` to return to direct `SurfaceView` presentation. +```c +typedef struct { + // ... existing fields ... + /// When true, the engine dynamically merges the raster task runner onto the + /// platform task runner for frames that composite one or more + /// `kFlutterLayerContentTypePlatformView` layers, holding a short frame + /// lease before unmerging once platform views leave the scene. + /// + /// While the threads are merged, `create_backing_store_callback`, + /// `collect_backing_store_callback`, and `present_view_callback` (or + /// `present_layers_callback`) execute on the platform thread. + /// + /// Requires engine-managed message loop task queues for the platform and + /// render threads (`FlutterCustomTaskRunners.platform_task_runner` and + /// `render_task_runner` must be NULL so the engine binds `fml::MessageLoop` + /// to the calling platform thread and creates the raster thread). + bool merge_raster_thread_on_platform_views; +} FlutterCompositor; +``` + +The Android embedder sets `merge_raster_thread_on_platform_views` to `true` when HCPP is not active, and `false` when HCPP (`enable_surface_control`) is active. Inside [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder), [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) implements this mode in three steps: + +1. **Dynamic Lease & Thread Merge (`SupportsDynamicThreadMerging` & `PostPrerollAction`)**: + Today, [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) overrides neither `SupportsDynamicThreadMerging()` nor `PostPrerollAction()`, so [`Rasterizer::Setup()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L94-L103) never creates an [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31). When `merge_raster_thread_on_platform_views` is `true`, `EmbedderExternalViewEmbedder::SupportsDynamicThreadMerging()` returns `true` and `PostPrerollAction()` mirrors [`AndroidExternalViewEmbedder::PostPrerollAction()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213): + - If the prerolled frame has no platform views (`composition_order_.size() == 1`), `PostPrerollAction()` returns `PostPrerollResult::kSuccess`, and [`Rasterizer::DoDraw()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L618-L626) decrements the lease at the end of each frame until the queues unmerge. + - If the frame contains platform views and `!raster_thread_merger->IsMerged()`, it calls `CancelFrame()`, merges the queues with a 10-frame lease (`MergeWithLease(kDefaultMergedLeaseDuration)`), and returns `PostPrerollResult::kSkipAndRetryFrame` so the frame is retried on the platform thread. + - Once merged, it extends the lease (`ExtendLeaseTo(kDefaultMergedLeaseDuration)`). +2. **First-HC-Frame Surface Switch (`PostPrerollResult::kResubmitFrame` and `convertToImageView`)**: + On the first frame that composites a platform view (`previous_frame_view_count_ == 0`), `EmbedderExternalViewEmbedder::PostPrerollAction()` returns `PostPrerollResult::kResubmitFrame` (matching [`external_view_embedder.cc#L209-L211`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L209-L211)). In [`CompositorContext::ScopedFrame::Raster()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/compositor_context.cc#L141-L143) and [`Rasterizer::DrawToSurfaceUnsafe()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L844-L864): + - Returning `kResubmitFrame` skips `PaintLayerTree`, so no Flutter drawing operations are recorded into the layer slices ([`EmbedderExternalView::HasEngineRenderedContents()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view.cc#L68-L78) is `false`). + - [`EmbedderExternalViewEmbedder::SubmitFlutterView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L571-L641) therefore skips `create_backing_store_callback` and `Render()` and invokes `present_view_callback` on the merged platform thread with the `kFlutterLayerContentTypePlatformView` layers. + - Inside `present_view_callback`, [`PlatformViewsController.onDisplayPlatformView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289) calls [`initializeRootImageViewIfNeeded()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1174-L1179) → [`flutterView.convertToImageView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1390), switching the primary `ANativeWindow` from [`FlutterSurfaceView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java#L35) to the [`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)-backed [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42), while [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391) keeps the platform view `View.GONE` because no `ImageReader` image has been queued yet. + - `DrawToSurfaceUnsafe()` returns `DrawSurfaceStatus::kRetry` for `RasterStatus::kResubmit`, and `Rasterizer` immediately resubmits the frame on the merged platform thread (`previous_frame_view_count_ > 0`). On the resubmitted pass, `PostPrerollAction()` returns `PostPrerollResult::kSuccess`, `create_backing_store_callback` acquires the root backing store from the newly attached `FlutterImageView` (and any overlay backing stores), and `present_view_callback` presents all layers and runs `onEndFrame()`, which acquires the queued `ImageReader` buffers and sets the [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) to `View.VISIBLE` in the same `ViewRootImpl.performTraversals()` pass. +3. **Unmerge and Revert to `FlutterSurfaceView`**: + When the last HC view is removed (`currentFrameUsedPlatformViewIds.isEmpty()`), [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1326-L1334) calls `flutterView.revertImageView()` to return to direct `FlutterSurfaceView` presentation, and the raster thread unmerges once the 10-frame lease expires. Zero changes to the Java `PlatformViewsController` HC implementation are required. ### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). -Unlike legacy Hybrid Composition (see Dynamic Raster Thread Merging), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: +Unlike legacy Hybrid Composition (see [Opt-In Dynamic Raster Thread Merging for Hybrid Composition](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views)), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: - Rasterization and Android view updates execute on separate threads without leasing or merging overhead. - The embedder arranges the base Flutter surface, native platform views, and overlay slices into a tree of [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) nodes. @@ -760,13 +848,21 @@ sequenceDiagram Impeller->>Engine: Deliver backing store + fence fd Engine->>Embedder: present_view_callback with view_id and synchronization_fence_fd Embedder->>SC: setBuffer with surfaceControl, hardwareBuffer, fenceFd - Embedder->>SC: applyTransactions or swapTransactions + Embedder->>SC: sealFrameTransactions and swapTransactions SC->>SF: Commit transaction with acquire fence SF->>SF: GPU waits on acquire fence before compositing SF-->>Embedder: Release fence signaled when buffer reading completes Embedder->>Embedder: Return buffer to AndroidSurfaceControl for recycling ``` +#### Frame Synchronization When Multiple Raster Frames Run Before a Choreographer Traversal ([#194591](https://github.com/flutter/flutter/pull/194591)) + +Because HCPP keeps the raster and platform threads unmerged, the raster thread can finish frame `N + 1` before the platform thread runs frame `N`'s task or before `Choreographer.doFrame()` draws frame `N`. [`flutter/flutter#194591`](https://github.com/flutter/flutter/pull/194591) fixes three multi-frame synchronization hazards across those boundaries, and each maps directly onto this `FlutterCompositor` design without additional `embedder.h` surface: + +1. **Per-Frame Raster Transaction Batching (`sealFrameTransactions`)**: In `AndroidExternalViewEmbedder2::SubmitFlutterView()`, the raster thread publishes each layer's `SurfaceControl.Transaction` and calls `sealFrameTransactions()` before posting the platform-thread task so `PlatformViewsController2` queues one sealed transaction batch per frame (`pendingRasterFrames`) instead of draining a flat transaction list across overlapping frames. Under `FlutterCompositor`, [`EmbedderExternalViewEmbedder::SubmitFlutterView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L571-L641) invokes `present_view_callback` once per frame on the raster thread with the complete `FlutterLayer` array and `synchronization_fence_fd` descriptors. That single callback is the exact per-frame boundary where the embedder seals the frame's raster transactions (or captures the transaction batch by value inside the posted closure) before dispatching to the platform thread. +2. **One Staged Frame per `Choreographer` Traversal (`QueuedFrame`)**: On the platform thread, `PlatformViewsController2` stages at most one frame's view hierarchy mutations (`onDisplayPlatformView`, `hidePlatformView`, `showOverlaySurface`, `hideOverlaySurface`) and `applyTransactionOnDraw()` call per `Choreographer` traversal, deferring subsequent frames into `pendingFrames` until the active traversal callback (`traversalFrameCallback` or `flushCompletedTraversalIfNeeded()`) fires. This logic lives in `PlatformViewsController2.java` and relies on the engine-managed `fml::MessageLoopAndroid` ALooper on the platform thread (`FlutterCustomTaskRunners.platform_task_runner = nullptr`), both of which this RFC preserves unchanged. +3. **Out-of-Order `ASurfaceTransaction_setOnComplete` Callbacks (`present_id`)**: When three or more frames arrive within a single VSync and coalesce in `pendingFrames`, two transactions targeting the same `SurfaceControl` merge into one draw pass, and Android's `SurfaceComposerClient` can invoke the newer transaction's `OnComplete` callback before the older, superseded callback. Following [#194591](https://github.com/flutter/flutter/pull/194591), the embedder-owned `AndroidSurfaceControl` swapchain tracks a monotonically increasing `present_id` per transaction so a superseded callback (`present_id <= last_completed_present_id_`) recycles its own dropped `AHardwareBuffer` texture into the pool rather than displacing `currently_displayed_texture_`. + ### Platform View Mutator Stack Serialization Platform views embedded within Flutter require spatial transformations, opacity adjustments, and geometric clips across Android composition modes: HCPP, legacy HC, and TLHC. @@ -1256,22 +1352,22 @@ FlutterEngineResult FlutterEngineSpawn( FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); ``` -The [`FlutterEngineSpawnInfo`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) so that spawned isolates mount distinct route hierarchies immediately upon launch. +The [`FlutterEngineSpawnInfo`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) so that spawned isolates mount distinct route hierarchies immediately upon launch. When `renderer_config` is `nullptr` (or when the parent engine used `kDeferred`), the spawned engine reuses the parent's resolved renderer configuration and [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70). -Spawned shells share the parent engine's [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/context/android_context.h#L26) / Impeller device context (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. +Spawned shells share the parent engine's resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. #### Relationship to Single-Isolate Multiview (`FlutterEngineAddView`) `FlutterEngineSpawn` and single-isolate multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)) address distinct concurrency and isolation trade-offs on Android: -1. **Multi-Isolate Engine Groups (`FlutterEngineSpawn` / [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41))**: Each spawned [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) runs an independent Dart root isolate within the shared Dart VM isolate group and [`AndroidContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/context/android_context.h#L26). A long-running computation or frame drop in one view cannot stall the UI thread of another view, while native memory overhead remains low (~180 KB per additional isolate). +1. **Multi-Isolate Engine Groups (`FlutterEngineSpawn` / [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41))**: Each spawned [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) runs an independent Dart root isolate within the shared Dart VM isolate group and [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70). A long-running computation or frame drop in one view cannot stall the UI thread of another view, while native memory overhead remains low (~180 KB per additional isolate). 2. **Single-Isolate Multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062-L3064))**: A single [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) and single Dart isolate drive multiple [`FlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L112) targets simultaneously, allowing widgets in distinct native windows or surfaces to read and mutate the same Dart object graph directly without cross-isolate message serialization. Migrating the Android embedder onto [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) unlocks both patterns: `FlutterEngineSpawn` supports [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41), while per-view [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) callbacks ([`FlutterPresentViewInfo.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2237) and [`FlutterBackingStoreConfig.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2167)) provide the foundation for single-isolate multiview and multi-window rendering. ### Task Runner Configuration, Thread Priorities, and Threading Modes -The Android embedder requires custom task runners to drive engine tasks on Android OS message loops. In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841), `custom_task_runners` cannot be `nullptr`. The embedder supplies task runner delegates driving Android's main thread [`ALooper`](https://developer.android.com/ndk/reference/group/looper) / [`Looper`](https://developer.android.com/reference/android/os/Looper) (`platform_task_runner`) alongside dedicated worker task runners (`ui_task_runner`, `render_task_runner`, and `thread_priority_setter`). +The Android embedder configures [`FlutterProjectArgs.custom_task_runners`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1960-L1980) with [`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975) while leaving `platform_task_runner` and `render_task_runner` set to `nullptr`. When `platform_task_runner` and `render_task_runner` are `nullptr`, [`EmbedderThreadHost::CreateEmbedderManagedPlatformThreadHost()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc#L175-L216) calls [`fml::MessageLoop::EnsureInitializedForCurrentThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.cc#L46-L56) on the calling Android UI thread (which binds [`fml::MessageLoopAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/message_loop_android.cc#L66-L86) directly to the thread's NDK [`ALooper`](https://developer.android.com/ndk/reference/group/looper) via `ALooper_forThread()`) and spawns the engine's dedicated worker threads (`ui`, `raster`, `io`). Because both the platform and raster runners are backed by [`fml::TaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h#L26) instances with valid [`fml::TaskQueueId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_queue_id.h#L16)s (unlike [`EmbedderTaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc#L14-L20), which uses `TaskQueueId::kInvalid`), [`fml::MessageLoopTaskQueues::Merge()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop_task_queues.cc#L212-L261) can dynamically merge and unmerge the raster and platform queues when `FlutterCompositor.merge_raster_thread_on_platform_views` is enabled. Thread priority configuration on Android requires system-level tuning. In the existing architecture, [`AndroidPlatformThreadConfigSetter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35) (located in [`android_shell_holder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35-L75)) uses `setpriority()` system calls to set Linux thread scheduling priorities: @@ -1449,7 +1545,7 @@ void PlatformMessageHandlerAndroid::HandlePlatformMessage( } ``` -Inside Java, [`DartMessenger.dispatchMessageToQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L299-L327) inspects the handler's registered `TaskQueue` and either dispatches to a background `SerialTaskQueue` / `ConcurrentTaskQueue` executor (`makeBackgroundTaskQueue()`) or posts to the main `Looper`'s `PlatformTaskQueue`. Meanwhile, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false`, which tells [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1532-L1571) to bounce messages emitted during engine startup (`route_messages_through_platform_thread_`) through the platform thread before dispatching them, ensuring plugins registered immediately after `FlutterEngineRun` in the same platform-thread turn do not miss messages sent during `main()`. +Inside Java, [`DartMessenger.dispatchMessageToQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L299-L327) inspects the handler's registered `TaskQueue` and either dispatches to a background `SerialTaskQueue` / `ConcurrentTaskQueue` executor (`makeBackgroundTaskQueue()`) or posts to the main `Looper`'s `PlatformTaskQueue`. Meanwhile, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false`, which tells [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1532-L1571) to bounce messages emitted during engine startup (`route_messages_through_platform_thread_`) through the platform thread before dispatching them so that plugins registered immediately after `FlutterEngineRun` in the same platform-thread turn do not miss messages sent during `main()`. In contrast, [`EmbedderPlatformMessageHandler::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) in `shell/platform/embedder/` unconditionally posts every message to `platform_task_runner_` and returns `true` from `DoesHandlePlatformMessageOnPlatformThread()`. @@ -1680,14 +1776,14 @@ sequenceDiagram participant EmbedderAPI as embedder.h ProcTable participant Core as FlutterEngine Core - FlutterJNI->>FlutterEmbedderNative: Initialize with Surface, Assets, Looper - FlutterEmbedderNative->>EmbedderAPI: FlutterEngineInitialize with project_args - EmbedderAPI->>Core: Shell::Create with configuration - Core-->>EmbedderAPI: Shell Instance + FlutterJNI->>FlutterEmbedderNative: attachToNative (create FlutterEmbedderNative) + FlutterJNI->>FlutterEmbedderNative: Pre-run calls (queued) + FlutterJNI->>FlutterEmbedderNative: runBundleAndSnapshotFromLibrary (entrypoint and assets) + FlutterEmbedderNative->>EmbedderAPI: FlutterEngineInitialize (kDeferred, project_args) EmbedderAPI-->>FlutterEmbedderNative: FlutterEngine Opaque Handle - FlutterJNI->>FlutterEmbedderNative: Launch with entrypoint and args - FlutterEmbedderNative->>EmbedderAPI: FlutterEngineRunInitialized with engine_handle - EmbedderAPI->>Core: RunEngine with configuration + FlutterEmbedderNative->>EmbedderAPI: FlutterEngineRunInitialized (engine_handle) + EmbedderAPI->>Core: LaunchShell, NotifyCreated (async), and RunRootIsolate + FlutterEmbedderNative->>EmbedderAPI: Flush queued pre-run calls ``` ### Pure Embedder API Client (`FLUTTER_ENGINE_NO_PROTOTYPES`) @@ -1711,8 +1807,8 @@ In the completed target architecture, [`AndroidShellHolder`](https://github.com/ - `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Is the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12)). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. - `JniRouter`, `JniDelegate`, and `JvmInvoker` (`jni_router.h`, `jni_delegate.h`, `jvm_invoker.h`): Decouple outbound C++-to-Java JNI invocations from live Android `JNIEnv*` state through injectable delegate and provider interfaces (`FlutterJniProvider`, `HardwareBufferProvider`, `SurfaceControlProvider`, `SurfaceTransactionProvider`, and `ChoreographerProvider`). This allows the entire C-API embedder (`flutter_embedder_native_unittests`) to be unit-tested on the host machine without an Android JVM or device. -- `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, and enforces synchronous surface destruction contracts before [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) returns via `FlutterEngineNotifyDestroyed`. -- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+) and Texture Layer Hybrid Composition (TLHC), deserialize [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays, capture per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). +- `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, and implements the window ownership contract: in [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) it stops handing out backing stores and waits only for the frame in flight before it releases the window. +- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition (HC), Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+), Texture Layer Hybrid Composition (TLHC), and Virtual Display (VD), deserialize [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays, capture per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). - `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` (`android_hardware_buffer.cc`, `android_vulkan_external_texture.cc`): Manage [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) and `EGLImageKHR` (OpenGL ES) with UV transformation matrices (`FlutterOpenGLExternalTexture` and `FlutterOpenGLTextureFrameCallback`) and zero-copy [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) ([`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imported into `VkImage` on Vulkan) texture callbacks (`FlutterVulkanExternalTexture`, `FlutterVulkanYcbcrConversionInfo`, and `FlutterVulkanTextureFrameCallback`). - `AndroidSemanticsMapper` and `AndroidWindowMetricsMapper` (`android_semantics_mapper.cc`, `android_window_metrics_mapper.cc`): Translate [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) trees into JNI byte buffers for Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) and pack viewport metrics, insets, corner radii, and foldable display features into [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). - `AndroidVsyncWaiter` (`android_vsync_waiter.cc`): Bridges Android NDK [`AChoreographer`](https://developer.android.com/ndk/reference/group/choreographer) and Java [`Choreographer`](https://developer.android.com/reference/android/view/Choreographer) frame callbacks to `FlutterProjectArgs.vsync_callback` and forwards frame pacing timestamps via `FlutterEngineOnVsync`. @@ -1765,8 +1861,8 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | | :--- | :--- | :--- | :--- | | **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnInfo`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`)
- `FlutterEngineInitializeCallbackCache` & `FlutterEngineGetCallbackInformation` (`FlutterDartCallbackInformation`)
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriCallback`) | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | -| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - [`FlutterEngineNotifyCreated`](#renderer-availability-and-surface-lifecycle), [`FlutterEngineNotifyDestroyed`](#renderer-availability-and-surface-lifecycle), and `FlutterProjectArgs.defer_surface_creation`
- `FlutterEngineQueryVulkanDriverSupport` (`FlutterVulkanDriverProperties`)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support and coordinates synchronous surface teardown across `SurfaceHolder.Callback` events. | -| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`)
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Captures per-`FlutterViewId` frame state by value and eliminates `external_view_embedder/` and `fml::RasterThreadMerger`. | +| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - `kDeferred` ([`FlutterDeferredRendererConfig`](#deferred-backend-selection-kdeferred), `FlutterRendererResolveInfo`)
- [`FlutterCompositor.avoid_backing_store_cache`](#window-ownership-contract) window ownership contract
- [`FlutterEngineNotifyLowMemoryWarning`](#releasing-transient-rendering-caches-flutterenginenotifylowmemorywarning) (extended `Rasterizer::NotifyLowMemoryWarning()`)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support, and releases each view's window on `surfaceDestroyed` without an engine round trip. | +| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`, [`merge_raster_thread_on_platform_views`](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views))
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Delegates dynamic raster thread merging for HC to `EmbedderExternalViewEmbedder`, captures per-`FlutterViewId` frame state by value, and eliminates `external_view_embedder/` from `shell/platform/android/`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3262-L3264) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | | **[`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetMapping` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping across hot restarts and split APK installs. | @@ -1794,6 +1890,15 @@ All C-API extensions specified in Detailed Design are implemented in [`embedder. Each new API is validated with automated engine unit tests ([`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390)) using mock platform delegates. No Android-specific files are modified in Phase 1. +Phase 1 also includes the engine changes those APIs depend on: + +- Renderer resolution in `PlatformViewEmbedder::SetupImpellerContext()`, including the retry and Impeller's known-bad driver check for `kDeferred` configurations. +- Non-blocking rendering surface creation in `PlatformViewEmbedder::NotifyCreated()` (`Shell::OnPlatformViewCreatedAsync()`). +- The window ownership contract on `FlutterCompositor`, with refused backing stores logged at a low level. +- Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) in `EmbedderExternalViewEmbedder` (`SupportsDynamicThreadMerging()` and `PostPrerollAction()`). +- Extending `Rasterizer::NotifyLowMemoryWarning()` (called by `FlutterEngineNotifyLowMemoryWarning`) to release Impeller's glyph atlases, text shadow cache, and per-frame host buffers in addition to its render-target and texture caches, and to collect any cached backing stores on the raster thread. +- Impeller rendering into Vulkan backing stores, which every Vulkan `FlutterCompositor` path on Android needs and `embedder.cc` doesn't implement yet ([#187525](https://github.com/flutter/flutter/issues/187525)). + Phase 1 must be fully merged and green across CI trees before Phase 2 begins. ### Phase 2: Android Shell Modularization and GN Target Partitioning @@ -2094,6 +2199,7 @@ Because Android is Flutter's largest mobile deployment target, verification must ### Engine Unit Tests (`flutter_embedder_native_unittests`) - Host-executable C++ unit tests (`flutter_embedder_native_unittests`) for `FlutterEmbedderNative` and each modularized component (`AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, and `AndroidEngineGroup`) using mock [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827) entries and injectable `JniDelegate` / `JvmInvoker` provider interfaces without requiring a live Android JVM or device. +- [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) cases for the engine behavior this RFC adds: `kDeferred` resolution and retry (a rejected Vulkan configuration leads to a second call that returns OpenGL ES), `FlutterEngineRunInitialized` returning while the resolve callback is still running, refused backing stores (the frame presents its other layers, and rendering resumes after `FlutterEngineScheduleFrame`), a window released while a frame is in flight, and `FlutterEngineNotifyLowMemoryWarning` releasing and recreating the transient rendering caches. - Unit tests validating the serialization and deserialization of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) structs and per-`FlutterViewId` frame state capture. ### Android Integration Tests @@ -2101,12 +2207,15 @@ Because Android is Flutter's largest mobile deployment target, verification must - [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L42-L67) and `flutter_embedder_native_unittests` executed across the CI test matrix. - Platform view lifecycle and composition tests verifying Hybrid Composition++ (HCPP with [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) on API 34+), legacy Hybrid Composition (HC), Texture Layer Hybrid Composition (TLHC), and Virtual Displays (VD) under both flag configurations. - Android embedding integration tests verifying JNI message channels, background isolate callbacks, and deferred library loading. +- Activity stop/start and configuration-change loops with HC and HCPP on Vulkan and OpenGL ES, checking that nothing renders into a window after `surfaceDestroyed()` returns and that rendering resumes after `surfaceCreated()`. ### DeviceLab Benchmarks - Frame pacing and rasterization latency benchmarks (comparing legacy vs C-API) to verify that the C-ABI abstraction does not introduce frame drops or scheduling jitter. - Platform view scrolling benchmarks across HCPP and HC modes to measure GPU sync fence latency, [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) submission timing, and touch response. - Startup latency benchmarks on low-end and high-end devices to verify that [`FlutterAssetResolver`](#custom-asset-and-kernel-resolution-apk--in-memory-mapping) memory mapping maintains zero-copy asset loading performance. +- Cold-start traces on low-end and high-end devices that compare when `CreateImpellerContext` runs and the time to first frame, legacy versus C-API, to check that resolving the renderer at `runBundleAndSnapshotFromLibrary()` doesn't delay the first frame. +- Memory after `surfaceDestroyed()`, legacy (`NotifyDestroyed`) versus C-API (`FlutterEngineNotifyLowMemoryWarning`), to check that a hidden engine releases the same GPU memory. ### Golden and Conformance Tests From 0275a4514830346af4bcc42ab0a69c5f288e4555 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Tue, 20 Oct 2026 19:38:06 +0000 Subject: [PATCH 29/43] Add clear summaries to the beginning of the each detailed section --- ...lutter-android-embedder-c-api-migration.md | 86 ++++++++++++++++--- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index de2e658..5623a09 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -548,7 +548,12 @@ After that, the Android embedder creates and destroys `ANativeWindow` swapchains ### Renderer Availability and Surface Lifecycle -Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) are created and destroyed by the OS during [`Activity`](https://developer.android.com/reference/android/app/Activity) transitions, configuration changes, and backgrounding while the `FlutterEngine` remains alive. +Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) are created and destroyed by the OS during [`Activity`](https://developer.android.com/reference/android/app/Activity) transitions, configuration changes, and backgrounding while the `FlutterEngine` remains alive. Under this RFC: + +- No surface lifecycle functions (`NotifyCreated` / `NotifyDestroyed`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). +- Because Android renders every frame through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) with `avoid_backing_store_cache = true`, the engine's internal GPU surface is context-only (`render_to_surface = false`) and never holds the `ANativeWindow`. +- The Android embedder owns window and swapchain lifetime across `surfaceCreated`, `surfaceChanged`, and `surfaceDestroyed`—returning `false` from `create_backing_store_callback` when no window is attached or when the OS invalidates the surface mid-frame, and re-arming the first-frame callback with [`FlutterEngineSetNextFrameCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3638-L3641). +- [`Rasterizer::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L173-L192)—invoked via the existing [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) C API on `onTrimMemory` and when the last view detaches in `surfaceDestroyed`—is extended to clear Impeller's glyph atlases, text shadow cache, and per-frame host buffers in addition to its render-target and texture caches. #### Decoupling Configuration from Surface Availability @@ -791,7 +796,11 @@ The Android embedder sets `merge_raster_thread_on_platform_views` to `true` when ### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State -Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). +Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). To support HCPP over [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322): + +- `FlutterBackingStorePresentInfo.synchronization_fence_fd` is added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) so the engine transfers the exported Vulkan GPU completion fence file descriptor (`vkGetFenceFdKHR`) to the embedder for each rendered backing store slice. +- `AndroidSurfaceControl` and `AndroidPlatformViewsController` replace the internal [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) subclass, allocating [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains in `create_backing_store_callback` and assembling [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits (`setBuffer`, `setLayer`, `setCrop`, `setMatrix`) in `present_view_callback` on unmerged threads. +- Per-frame composition state is captured by value per [`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) inside `present_view_callback` and sealed once per frame (`sealFrameTransactions`) to prevent cross-view and cross-frame data races ([#194591](https://github.com/flutter/flutter/pull/194591)). Unlike legacy Hybrid Composition (see [Opt-In Dynamic Raster Thread Merging for Hybrid Composition](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views)), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: @@ -865,7 +874,11 @@ Because HCPP keeps the raster and platform threads unmerged, the raster thread c ### Platform View Mutator Stack Serialization -Platform views embedded within Flutter require spatial transformations, opacity adjustments, and geometric clips across Android composition modes: HCPP, legacy HC, and TLHC. +Platform views embedded within Flutter require spatial transformations, opacity adjustments, and geometric clips across Android composition modes (HCPP, legacy HC, and TLHC). To pass these mutators across [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) without exposing internal `//flutter/flow` or `SkPath` headers: + +- `FlutterPath` (`FlutterPathFillType`, `FlutterPathVerb`), `kFlutterPlatformViewMutationTypeClipPath`, and `FlutterCompositor.platform_view_mutation_capabilities` (`FlutterPlatformViewMutationCapability`) are added to `embedder.h` so arbitrary path clips serialize into an ABI-stable structure-of-arrays layout. +- Rounded-superellipse clips (`MutatorType::kClipRSE`) continue to be approximated as `kFlutterPlatformViewMutationTypeClipRoundedRect` by the engine, and conic path verbs (`kFlutterPathVerbConic`) are converted to quadratic Beziers by the engine unless the compositor opts in via `kFlutterPlatformViewMutationCapabilityPathConics` (which Android sets on API 34+). +- `AndroidMutatorsMapper` replaces direct `flutter::MutatorsStack` iteration in `PlatformViewAndroidJNIImpl`, translating the `FlutterPlatformView.mutations` array into Java [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) and `SurfaceControl.Transaction` transforms. Internally, these operations are managed by [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211) in [`flow/embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h). Because `flow` and `SkPath` headers cannot cross the C-API boundary, mutators must be serialized into an ABI-stable array of C structs: @@ -975,6 +988,12 @@ Android does not apply backdrop filter mutators over HC or HCPP platform views t ### External Textures (Zero-Copy Vulkan and OpenGL ES) +To replace the internal `flutter::Texture` subclasses ([`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36) and [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33)) across both [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) and [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`GL_TEXTURE_EXTERNAL_OES`) pipelines: + +- `FlutterVulkanYcbcrConversionInfo` and `FlutterVulkanExternalTexture.ycbcr_conversion_info` are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) so the Vulkan renderer can sample multi-planar and vendor-specific external YCbCr camera and video frames (`VkSamplerYcbcrConversion`) without exposing Android `AHardwareBuffer` types in `embedder.h`. +- `FlutterOpenGLExternalTexture` (with `FlutterTransformation uv_transform`) and `FlutterOpenGLRendererConfig.external_texture_frame_callback` are added to `embedder.h` (deprecating `gl_external_texture_frame_callback`) so the OpenGL ES renderer applies `SurfaceTexture.getTransformMatrix()` UV transforms while keeping `FlutterOpenGLTexture` unchanged for render targets. +- `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` import `AHardwareBuffer` frames into `VkImage` (Vulkan) or `EGLImageKHR` (OpenGL ES) inside the Android embedder and register textures through `FlutterEngineRegisterExternalTexture` and `FlutterEngineMarkExternalTextureFrameAvailable`. + Android provides two distinct mechanisms for external textures: - [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline backed by `ImageReader` and [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) on API 29+ (falling back to `SurfaceTexture` below API 29 and on Huawei devices at API 29). @@ -1091,9 +1110,13 @@ Under OpenGL ES, platform view compositing also faces constraints: because [`Sur ### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783) In-Place Extension) -Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) requires semantics metadata that was absent in [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783), including `max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count` (`FlutterSemanticsNode2.child_count` is populated from `childrenInTraversalOrder.size()`, which can differ from `childrenInHitTestOrder.size()` when the framework grafts nodes such as `OverlayPortal`). +To deliver the complete semantics tree to Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) without including internal [`lib/ui/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h) headers: -**Missing Android Accessibility Members**: `FlutterSemanticsRole` is defined to mirror [`flutter::SemanticsRole`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/lib/ui/semantics/semantics_node.h#L74-L111), and the remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783): +- `FlutterSemanticsRole` and nine node fields (`max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count`) are appended in place to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783) in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). +- `hit_test_child_count` separates the length of `children_in_hit_test_order` from `child_count` (`children_in_traversal_order`), which can differ when the framework grafts nodes such as `OverlayPortal`. +- `AndroidSemanticsMapper` consumes `FlutterSemanticsUpdate2` via `FlutterProjectArgs.update_semantics_callback2` and packs the direct `ByteBuffer` and string pool arrays expected by `AccessibilityBridge`, while accessibility actions route back through `FlutterEngineSendSemanticsAction`. + +`FlutterSemanticsRole` mirrors [`flutter::SemanticsRole`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/lib/ui/semantics/semantics_node.h#L74-L111), and the remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783): ```c typedef enum { @@ -1152,7 +1175,10 @@ size_t hit_test_child_count; ### Dart Deferred Library Loading -Android supports dynamic delivery through Google Play Feature Delivery / split APKs. When Dart code requests a deferred library via `deferred as`, the embedder must download or extract the split APK and load the compiled snapshot into the running isolate. +Android supports dynamic delivery through Google Play Feature Delivery / split APKs: when Dart code requests a deferred library via `deferred as`, the embedder downloads or extracts the split APK and loads the compiled snapshot into the running isolate. To support deferred loading through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): + +- `FlutterRequestDartDeferredLibraryCallback` (`FlutterProjectArgs.request_dart_deferred_library_callback`), `FlutterDartDeferredLibrary` (with `destruction_callback`), `FlutterEngineLoadDartDeferredLibrary`, and `FlutterEngineNotifyDartDeferredLibraryLoadError` (`FlutterDartDeferredLibraryLoadError`) are added to `embedder.h`. +- Instead of calling internal [`PlatformViewAndroid::LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L508-L514) methods directly, the Android embedder receives loading unit requests via `request_dart_deferred_library_callback`, resolves the split APK snapshot buffers, and supplies them to `FlutterEngineLoadDartDeferredLibrary` (or reports failures via `FlutterEngineNotifyDartDeferredLibraryLoadError`), releasing mapped resources when `destruction_callback` fires. Currently, this is handled through private calls across JNI in [`LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L701-L735) into [`PlatformViewAndroid::LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L508-L514): @@ -1258,6 +1284,11 @@ Because split APK assets on Android are loaded via `AAssetManager` or memory-map ### Custom Asset and Kernel Resolution (APK / In-Memory Mapping) +To resolve assets packaged inside APK or AAB archives without subclassing internal C++ [`flutter::AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23) or extracting files to disk: + +- `FlutterAssetMapping`, `FlutterCustomAssetResolver` (`FlutterAssetResolverGetAsMappingCallback`), and `FlutterProjectArgs.custom_asset_resolvers` (`custom_asset_resolvers_count`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). +- [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) supplies a `FlutterCustomAssetResolver` that maps assets directly from the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) in `get_as_mapping_callback` and atomically swaps its `AAssetManager*` pointer inside `user_data` when `FlutterJNI.updateJavaAssetManager` runs after a split APK install. + Desktop embedders load assets from loose files in a filesystem directory passed to [`FlutterProjectArgs.assets_path`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2533). On Android, assets are packaged inside APK or AAB archives and accessed through the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26), which previously subclassed the internal engine [`AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23): ```cpp @@ -1316,7 +1347,12 @@ Resolvers configured via `FlutterProjectArgs.custom_asset_resolvers` remain acti ### Multi-Engine Spawning and Add-to-App (`FlutterEngineSpawn`) -Android Add-to-App architectures rely on [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) to spawn lightweight engines that share the Dart VM, isolate group, and shared cache. In the existing architecture, [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) reaches directly into [`shell_->Spawn(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210): +To support [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) and Add-to-App multi-engine sharing without calling internal [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) methods: + +- `FlutterEngineSpawn` and `FlutterEngineSpawnInfo` are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). +- `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285), spawning child `FlutterEngine` instances that share the parent engine's Dart VM isolate group, `io_manager`, and resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) (including when the parent engine initialized with `type = kDeferred`). + +In the existing architecture, [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) reaches directly into [`shell_->Spawn(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210): ```cpp // Existing spawning in shell/platform/android/android_shell_holder.cc @@ -1367,7 +1403,14 @@ Migrating the Android embedder onto [`embedder.h`](https://github.com/flutter/fl ### Task Runner Configuration, Thread Priorities, and Threading Modes -The Android embedder configures [`FlutterProjectArgs.custom_task_runners`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1960-L1980) with [`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975) while leaving `platform_task_runner` and `render_task_runner` set to `nullptr`. When `platform_task_runner` and `render_task_runner` are `nullptr`, [`EmbedderThreadHost::CreateEmbedderManagedPlatformThreadHost()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc#L175-L216) calls [`fml::MessageLoop::EnsureInitializedForCurrentThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.cc#L46-L56) on the calling Android UI thread (which binds [`fml::MessageLoopAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/message_loop_android.cc#L66-L86) directly to the thread's NDK [`ALooper`](https://developer.android.com/ndk/reference/group/looper) via `ALooper_forThread()`) and spawns the engine's dedicated worker threads (`ui`, `raster`, `io`). Because both the platform and raster runners are backed by [`fml::TaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h#L26) instances with valid [`fml::TaskQueueId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_queue_id.h#L16)s (unlike [`EmbedderTaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc#L14-L20), which uses `TaskQueueId::kInvalid`), [`fml::MessageLoopTaskQueues::Merge()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop_task_queues.cc#L212-L261) can dynamically merge and unmerge the raster and platform queues when `FlutterCompositor.merge_raster_thread_on_platform_views` is enabled. +To configure Android thread scheduling, [`ALooper`](https://developer.android.com/ndk/reference/group/looper) binding, and thread merging through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) without instantiating internal [`flutter::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21) directly: + +- No new functions or structs are added to `embedder.h`. +- The Android embedder sets [`FlutterCustomTaskRunners.thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975) to apply Android's Linux scheduling priorities (`setpriority()`) and CPU core affinity while leaving `platform_task_runner` and `render_task_runner` set to `nullptr`. +- Leaving `platform_task_runner` and `render_task_runner` `nullptr` causes [`EmbedderThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc#L175-L216) to bind [`fml::MessageLoopAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/message_loop_android.cc#L66-L86) to the calling Android UI thread's NDK `ALooper` and create engine-managed worker threads with valid [`fml::TaskQueueId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_queue_id.h#L16)s required for dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`). +- Merged platform/UI threading (`kMergeAfterLaunch`) is configured by passing `--merged-platform-ui-thread=mergeAfterLaunch` via `FlutterProjectArgs.command_line_argv`. + +When `platform_task_runner` and `render_task_runner` are `nullptr`, [`EmbedderThreadHost::CreateEmbedderManagedPlatformThreadHost()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc#L175-L216) calls [`fml::MessageLoop::EnsureInitializedForCurrentThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.cc#L46-L56) on the calling Android UI thread (which binds [`fml::MessageLoopAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/message_loop_android.cc#L66-L86) directly to the thread's NDK [`ALooper`](https://developer.android.com/ndk/reference/group/looper) via `ALooper_forThread()`) and spawns the engine's dedicated worker threads (`ui`, `raster`, `io`). Because both the platform and raster runners are backed by [`fml::TaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h#L26) instances with valid [`fml::TaskQueueId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_queue_id.h#L16)s (unlike [`EmbedderTaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc#L14-L20), which uses `TaskQueueId::kInvalid`), [`fml::MessageLoopTaskQueues::Merge()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop_task_queues.cc#L212-L261) can dynamically merge and unmerge the raster and platform queues when `FlutterCompositor.merge_raster_thread_on_platform_views` is enabled. Thread priority configuration on Android requires system-level tuning. In the existing architecture, [`AndroidPlatformThreadConfigSetter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35) (located in [`android_shell_holder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35-L75)) uses `setpriority()` system calls to set Linux thread scheduling priorities: @@ -1415,7 +1458,15 @@ Internal consumers relying on the `kMergeAfterLaunch` threading model are suppor ### Process-Scoped Utilities, Image Decoding, and Diagnostics C-APIs -Four groups of Android entry points in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108) reach engine state outside the frame pipeline, and today they pull [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) headers into [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android). [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) covers them with four focused C-API groups. The Dart callback cache and the default font manager are process-wide singletons that Android uses before any engine exists, so their functions take no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`. +To eliminate the remaining [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) header inclusions in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108): + +- `FlutterEngineInitializeCallbackCache` and `FlutterEngineGetCallbackInformation` (`FlutterDartCallbackInformation`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and `FlutterEngineProcTable` for the process-wide Dart callback cache. +- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`, `FlutterImageGenerator`, `FlutterImageGeneratorInfo`) is added to `embedder.h` so the Android embedder can register its fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats) without subclassing `flutter::ImageGenerator`. +- `FlutterEnginePrefetchDefaultFontManager` is added to `embedder.h` and `FlutterEngineProcTable` to warm up the process-wide default font manager on a background thread during `FlutterLoader.initAsync`. +- `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`) is added to `embedder.h` to deliver the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. +- No screenshot C API is added to `embedder.h`; `FlutterRenderer.getBitmap()` is implemented on the platform side via Android [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) so that Hybrid Composition and HCPP platform views are captured alongside Flutter content. + +The Dart callback cache and the default font manager are process-wide singletons that Android uses before any engine exists, so their functions take no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`: 1. **Process-Scoped Callback Cache ([`//flutter/lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) Severance)**: [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L148-L157) sets the cache directory and loads persisted handles during process initialization, and [`FlutterCallbackInformation.lookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java#L27-L29) resolves handles for background-execution plugins (`workmanager`, `android_alarm_manager_plus`). Because the iOS framework already exports an Objective-C class named [`FlutterCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h#L16-L17), the C struct is named `FlutterDartCallbackInformation`: @@ -1528,6 +1579,11 @@ Four groups of Android entry points in [`FlutterMain`](https://github.com/flutte ### Platform Channel Messaging, Background Handlers, and Task Queues +To replace the internal [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18) subclass while preserving background `TaskQueue` dispatch and startup message ordering: + +- `FlutterProjectArgs.enable_direct_platform_message_dispatch` (`bool`, defaulting to `false`) is added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). +- When `enable_direct_platform_message_dispatch` is `true`, [`EmbedderPlatformMessageHandler`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) invokes `platform_message_callback` directly on the calling isolate's thread (and returns `false` from `DoesHandlePlatformMessageOnPlatformThread()`), so root-isolate messages avoid an extra main-`Looper` post and background-isolate messages route directly to [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) executors without bouncing through the Android main thread. + In the existing Android architecture, [`PlatformMessageHandlerAndroid::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.cc#L56-L67) invokes `FlutterJNI.handlePlatformMessage` synchronously on the calling thread—either the UI thread (via [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1532-L1579)) or a background Dart isolate's thread (via [`UIDartState::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L223-L240)): ```cpp @@ -1592,6 +1648,12 @@ For empty platform messages, the C-API guarantees that a message where `message_ ### Pointer Input Events, Viewport Metrics, and System Configuration +To route pointer input, edge-to-edge/foldable window metrics, and system configuration without internal [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) headers or synchronous JNI calls: + +- `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, and `FlutterDisplayFeature` (`FlutterDisplayFeatureType`, `FlutterDisplayFeatureState`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), and `physical_padding`, `physical_system_gesture_insets`, `physical_touch_slop`, `display_corner_radii`, and `display_features` (`display_features_count`) are appended in place to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). +- Pointer input and locale updates use the existing [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082) and [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3479-L3481) C APIs without `embedder.h` additions. +- Android 14+ non-linear font scaling replaces synchronous UI-thread JNI calls (`PlatformView::GetScaledFontSize` and `SettingsChannel.ConfigurationQueue`) by sending the 9-point `FontScaleConverter` lookup table (`fontScaleCurves`) over the existing `flutter/settings` JSON channel and interpolating in `PlatformDispatcher.scaleFontSize`. + #### Pointer Input Events Input events processed in [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L372-L445) provide axis and device metadata. The C-API embedder populates [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1362-L1409) with: @@ -1870,7 +1932,7 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec ## Phased Implementation Strategy -To maintain continuous test coverage and avoid architectural traps, the migration executes in four sequential phases: +To maintain continuous test coverage and avoid architectural traps, the migration is structured into four phases—with Phase 1 (`shell/platform/embedder/`) and Phase 2 (`shell/platform/android/` modularization and GN partitioning) executing in parallel before converging at Phase 3: ```mermaid flowchart LR @@ -1879,7 +1941,7 @@ flowchart LR Phase3["Phase 3:
C-API Cutover
Behind Feature Flag"] Phase4["Phase 4:
Build Decoupling
& CI Ratchet"] - Phase1 --> Phase2 + Phase1 --> Phase3 Phase2 --> Phase3 Phase3 --> Phase4 ``` @@ -1899,7 +1961,7 @@ Phase 1 also includes the engine changes those APIs depend on: - Extending `Rasterizer::NotifyLowMemoryWarning()` (called by `FlutterEngineNotifyLowMemoryWarning`) to release Impeller's glyph atlases, text shadow cache, and per-frame host buffers in addition to its render-target and texture caches, and to collect any cached backing stores on the raster thread. - Impeller rendering into Vulkan backing stores, which every Vulkan `FlutterCompositor` path on Android needs and `embedder.cc` doesn't implement yet ([#187525](https://github.com/flutter/flutter/issues/187525)). -Phase 1 must be fully merged and green across CI trees before Phase 2 begins. +Because Phase 1 modifies only `shell/platform/embedder/` and `embedder_unittests`, it proceeds in parallel with Phase 2's in-place modularization of `shell/platform/android/`, and both converge before Phase 3 cutover. ### Phase 2: Android Shell Modularization and GN Target Partitioning From 2227b821826232cfc62294e8851d9babf51e7ae4 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Tue, 20 Oct 2026 21:42:54 +0000 Subject: [PATCH 30/43] Remove synchronization_fence since disabling the backing store cache leaves the Android side with ownership --- ...lutter-android-embedder-c-api-migration.md | 109 +++++------------- 1 file changed, 26 insertions(+), 83 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 5623a09..213d632 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -553,7 +553,7 @@ Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/ref - No surface lifecycle functions (`NotifyCreated` / `NotifyDestroyed`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). - Because Android renders every frame through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) with `avoid_backing_store_cache = true`, the engine's internal GPU surface is context-only (`render_to_surface = false`) and never holds the `ANativeWindow`. - The Android embedder owns window and swapchain lifetime across `surfaceCreated`, `surfaceChanged`, and `surfaceDestroyed`—returning `false` from `create_backing_store_callback` when no window is attached or when the OS invalidates the surface mid-frame, and re-arming the first-frame callback with [`FlutterEngineSetNextFrameCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3638-L3641). -- [`Rasterizer::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L173-L192)—invoked via the existing [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) C API on `onTrimMemory` and when the last view detaches in `surfaceDestroyed`—is extended to clear Impeller's glyph atlases, text shadow cache, and per-frame host buffers in addition to its render-target and texture caches. +- The Android embedder calls the existing [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) C API on `onTrimMemory` and when the last view detaches in `surfaceDestroyed` to release Impeller's offscreen render-target and cached texture pools (wired in [#193497](https://github.com/flutter/flutter/pull/193497)), requiring no changes to `embedder.h` or `Rasterizer::NotifyLowMemoryWarning()`. #### Decoupling Configuration from Surface Availability @@ -571,8 +571,6 @@ So `embedder.h` gains no surface lifecycle functions. The embedder owns the wind #### Window Ownership Contract -When `avoid_backing_store_cache` is `true`, the engine logs a refused backing store at a low level instead of as an error ([`embedder.cc#L1406-L1408`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L1406-L1408)). - The Android embedder (`AndroidSurfaceControl`) follows these rules: - Because the Android OS owns the underlying `Surface` (`BufferQueue` / `SurfaceFlinger`) and can invalidate it or dispatch `surfaceDestroyed` while a raster frame is in flight, the embedder cannot guarantee that the window remains valid from `create_backing_store_callback` through `present_view_callback`. When no window is attached or acquiring or presenting a buffer fails (such as `VK_ERROR_SURFACE_LOST_KHR`, `VK_ERROR_OUT_OF_DATE_KHR`, or `EGL_BAD_SURFACE`), `create_backing_store_callback` returns `false` (or `present_view_callback` drops the presentation) and `collect_backing_store_callback` releases any per-frame resources. @@ -591,7 +589,7 @@ The Android embedder maps [`SurfaceHolder.Callback`](https://developer.android.c #### Releasing Transient Rendering Caches (`FlutterEngineNotifyLowMemoryWarning`) -`avoid_backing_store_cache` covers only the backing stores. Impeller also keeps GPU caches of its own inside the engine's rendering surface, in the `ContentContext` that its `AiksContext` owns ([`content_context.h#L256-L261`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/entity/contents/content_context.h#L256-L261), [`#L309-L323`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/entity/contents/content_context.h#L309-L323)): an offscreen render-target cache for save layers and filters (separate from the backing-store cache), cached `DlImage` textures, glyph atlases, the text shadow cache, and per-frame host buffers. The engine allocates these itself; they never go through `create_backing_store_callback`, so the embedder cannot free them directly. +`avoid_backing_store_cache` covers only the backing stores. Impeller also keeps transient offscreen render-target and cached `DlImage` texture pools inside the engine's rendering surface (`AiksContext`). The engine allocates these itself; they never go through `create_backing_store_callback`, so the embedder cannot free them directly. [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) already provides [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) ([`embedder.cc#L3597-L3617`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3597-L3617)): @@ -602,9 +600,7 @@ FlutterEngineResult FlutterEngineNotifyLowMemoryWarning( FLUTTER_API_SYMBOL(FlutterEngine) engine); ``` -Today, `FlutterEngineNotifyLowMemoryWarning` calls [`Shell::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L750-L770) (which notifies the Dart VM and posts [`Rasterizer::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L173-L192) to the raster thread) and sends the `{"type": "memoryPressure"}` message on the `flutter/system` channel. Since [#193497](https://github.com/flutter/flutter/pull/193497), `Rasterizer::NotifyLowMemoryWarning()` calls `aiks_context->ClearRenderTargetCache()` and `aiks_context->ClearCachedTextures()`, leaving the glyph atlases, text shadow cache, and per-frame host buffers in place. - -Under this RFC, `Rasterizer::NotifyLowMemoryWarning()` is extended so that it also clears `ContentContext`'s glyph atlases, text shadow cache, and per-frame host buffers (and collects any cached compositor backing stores) while keeping `impeller::Context` and its pipeline caches intact. No new C-API function is needed in `embedder.h`: +`FlutterEngineNotifyLowMemoryWarning` calls [`Shell::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L750-L770) (which notifies the Dart VM and posts [`Rasterizer::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L173-L192) to the raster thread) and sends the `{"type": "memoryPressure"}` message on the `flutter/system` channel. Since [#193497](https://github.com/flutter/flutter/pull/193497), `Rasterizer::NotifyLowMemoryWarning()` calls `aiks_context->ClearRenderTargetCache()` and `aiks_context->ClearCachedTextures()` while keeping `impeller::Context` and its pipeline caches intact, so no changes to `embedder.h` or `Rasterizer::NotifyLowMemoryWarning()` are needed: - When Android dispatches [`ComponentCallbacks2.onTrimMemory`](https://developer.android.com/reference/android/content/ComponentCallbacks2#onTrimMemory(int)) (including `TRIM_MEMORY_UI_HIDDEN = 20`, which satisfies [`level >= TRIM_MEMORY_RUNNING_LOW`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java#L1043-L1047) in `FlutterActivityAndFragmentDelegate.onTrimMemory`), [`FlutterJNI.notifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1401-L1407) invokes `FlutterEngineNotifyLowMemoryWarning(engine)`. - When the last view of an engine detaches in `surfaceDestroyed`, `AndroidSurfaceControl` also calls `FlutterEngineNotifyLowMemoryWarning(engine)` so that cached add-to-app engines whose host app stays in the foreground release the same GPU caches that `NotifyDestroyed` frees today. @@ -664,18 +660,17 @@ With `kDeferred`, the UI thread doesn't wait for backend selection in either des ### Replacing Internal [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) with `FlutterCompositor` (Preserving Hybrid Composition) -To eliminate the internal C++ [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) subclasses while preserving all four Android platform view composition modes, this RFC routes platform views through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) and the external texture C API with three targeted `FlutterCompositor` additions: +To eliminate the internal C++ [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) subclasses while preserving all four Android platform view composition modes, this RFC routes platform views through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) and the external texture C API with two targeted `FlutterCompositor` additions: - `FlutterBackingStoreConfig.is_root_layer` identifies whether a requested backing store is the bottom-most Flutter layer beneath all platform views (so the embedder can return the primary on-screen surface) or an overlay layer above a platform view (so the embedder allocates a translucent overlay buffer). - `FlutterCompositor.merge_raster_thread_on_platform_views` is an opt-in flag that dynamically merges the raster task runner onto the platform thread during Hybrid Composition (HC) frames so Java [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L54) requires no modifications. -- `FlutterBackingStorePresentInfo.synchronization_fence_fd` transfers the GPU completion fence file descriptor to the embedder for Hybrid Composition++ (HCPP) [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits. Android's four platform view composition modes and their mechanisms map to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) as follows: | Mode | Android Mechanism | Flutter UI Above View | `embedder.h` Boundary | | :--- | :--- | :--- | :--- | | **Hybrid Composition (HC)** | Places the native [`View`](https://developer.android.com/reference/android/view/View) directly in the view hierarchy (`createForPlatformViewLayer`) and converts the root surface to [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) ([`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)). | Rendered into [`PlatformViewWrapper`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L40) overlay surfaces (`createOverlaySurface` / `onDisplayOverlaySurface`) on the merged platform/raster thread. | `FlutterCompositor` (`kFlutterLayerContentTypePlatformView`, `is_root_layer`, `merge_raster_thread_on_platform_views = true`) | -| **Hybrid Composition++ (HCPP)** | Places the native `View` in the view hierarchy (`createPlatformViewHcpp`) and composites layers in [`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager) via NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU sync fences (API 34+, Vulkan). | Rendered into [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) swapchain slices attached to overlay `SurfaceControl` nodes on unmerged threads. | `FlutterCompositor` (`kFlutterLayerContentTypePlatformView`, `is_root_layer`, `synchronization_fence_fd`) | +| **Hybrid Composition++ (HCPP)** | Places the native `View` in the view hierarchy (`createPlatformViewHcpp`) and composites layers in [`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager) via NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU sync fences (API 34+, Vulkan). | Rendered into [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) swapchain slices attached to overlay `SurfaceControl` nodes on unmerged threads. | `FlutterCompositor` (`kFlutterLayerContentTypePlatformView`, `is_root_layer`, `merge_raster_thread_on_platform_views = false`) | | **Texture Layer Hybrid Composition (TLHC)** | Attaches the native `View` to `PlatformViewWrapper` while redirecting its draw output into an offscreen [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) or [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`configureForTextureLayerComposition`). | Rendered directly in the single Flutter scene around the `Texture` widget. | External texture C API (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`) | | **Virtual Display (VD)** | Hosts the native `View` inside an offscreen [`VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay) (`VirtualDisplayController`) backed by a `SurfaceProducer` or `SurfaceTexture` (`configureForVirtualDisplay`). | Rendered directly in the single Flutter scene around the `Texture` widget. | External texture C API (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`) | @@ -798,15 +793,15 @@ The Android embedder sets `merge_raster_thread_on_platform_views` to `true` when Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). To support HCPP over [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322): -- `FlutterBackingStorePresentInfo.synchronization_fence_fd` is added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) so the engine transfers the exported Vulkan GPU completion fence file descriptor (`vkGetFenceFdKHR`) to the embedder for each rendered backing store slice. +- No new fields are added to [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198): because `AndroidSurfaceControl` owns the [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html) and [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html) passed in [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L995-L1035) and Impeller flushes its render pass command buffers to that `VkQueue` ([`ContextVK::FlushCommandBuffers()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc#L703-L715)) before invoking `present_view_callback`, `AndroidSurfaceControl` signals its own exportable `VkSemaphore` ([`VK_KHR_external_semaphore_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_semaphore_fd.html) / `vkGetSemaphoreFdKHR`) on `VkQueue` inside `present_view_callback`. - `AndroidSurfaceControl` and `AndroidPlatformViewsController` replace the internal [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) subclass, allocating [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains in `create_backing_store_callback` and assembling [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits (`setBuffer`, `setLayer`, `setCrop`, `setMatrix`) in `present_view_callback` on unmerged threads. -- Per-frame composition state is captured by value per [`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) inside `present_view_callback` and sealed once per frame (`sealFrameTransactions`) to prevent cross-view and cross-frame data races ([#194591](https://github.com/flutter/flutter/pull/194591)). +- Per-frame composition state is captured by value per [`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) inside `present_view_callback` before dispatching to the platform thread to prevent cross-view and cross-frame data races. Unlike legacy Hybrid Composition (see [Opt-In Dynamic Raster Thread Merging for Hybrid Composition](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views)), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: - Rasterization and Android view updates execute on separate threads without leasing or merging overhead. - The embedder arranges the base Flutter surface, native platform views, and overlay slices into a tree of [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) nodes. -- Visual synchronization occurs at the GPU level via hardware sync fences ([`ASyncFence`](https://developer.android.com/ndk/reference/group/sync) or Vulkan binary semaphores) passed through [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits, synchronizing buffer availability without CPU blocking. +- Visual synchronization occurs at the GPU level via hardware sync fences ([`ASyncFence`](https://developer.android.com/ndk/reference/group/sync) exported from Vulkan binary semaphores) passed through [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits, synchronizing buffer availability without CPU blocking. - **Per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) Value-Captured Frame State**: In a multiview or multi-window environment, the raster thread renders View A (`FlutterPresentViewInfo.view_id = 0`) and immediately begins rasterizing View B (`view_id = 1`) while the platform thread is still applying View A's platform view layout mutations. Storing per-frame composition state (such as `composition_order_`, `picture_bounds_`, or `views_to_recomposite_`) as mutable fields on a shared controller causes cross-thread data races between consecutive views. `AndroidPlatformViewsController` eliminates this hazard by keying active platform view hierarchies by [`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) and capturing each view's ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) slice and [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) parameters by value inside the [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321) closure before dispatching to the platform thread. To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the public C-API provides multi-layer compositing primitives through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322): @@ -815,47 +810,28 @@ To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4 2. **Layer Slicing and Dispatch**: During frame presentation, [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321) (or [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2302)) delivers an ordered list of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) items scoped to [`FlutterPresentViewInfo.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2237): - [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173): Rendered Flutter graphics slices (base surface or overlay surfaces). - [`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175): Embedded native Android [`View`](https://developer.android.com/reference/android/view/View) nodes, accompanied by [`FlutterPlatformViewIdentifier`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1525) and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays (see Platform View Mutator Stack Serialization). -3. **Fence Transfer and Atomic Commit**: In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), extensions are added through [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198) instead of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231). `FlutterBackingStorePresentInfo` is extended in place with a synchronization fence file descriptor: +3. **Embedder-Owned Acquire Semaphore Export and Atomic Commit**: Because `AndroidSurfaceControl` owns the [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html), and [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed `VkImage` passed to the engine, it exports the acquire `sync_fence` file descriptor directly on its `VkQueue` inside `present_view_callback` without adding a fence field to [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198). -```c -typedef struct { - /// The size of this struct. Must be sizeof(FlutterBackingStorePresentInfo). - size_t struct_size; +#### Acquire Fence Lifecycle and Ownership - /// The area of the backing store that contains Flutter contents. - FlutterRegion* paint_region; - - /// File descriptor representing a sync_fence / ASyncFence. - /// When presenting on Android API 34+ via SurfaceControl.Transaction, - /// the engine passes this fence to the embedder. - /// - /// The embedder takes ownership of this file descriptor and must close it - /// (for example, by passing it to SurfaceControl.Transaction.setBuffer or - /// calling close()). A value of -1 indicates no fence. - int synchronization_fence_fd; -} FlutterBackingStorePresentInfo; -``` - -#### Synchronization Fence Lifecycle and Ownership - -1. **GPU Fence Export**: When Impeller submits render passes for a backing store, it exports the GPU completion semaphore or fence into a POSIX `sync_fence` file descriptor using the [`VK_KHR_external_fence_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_fence_fd.html) extension (`vkGetFenceFdKHR`). -2. **Transfer to Embedder**: The engine populates `FlutterBackingStorePresentInfo.synchronization_fence_fd` and delivers the layers to [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321). Ownership of the file descriptor transfers to the embedder. -3. **Buffer Attachment**: In HCPP mode, `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) sequence directly into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`). The embedder attaches the backing store `AHardwareBuffer` and fence file descriptor to the layer via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)). Passing the fence to Android's transaction wraps or closes the descriptor; if unused or on error, the embedder must close it with `::close(fd)`. -4. **SurfaceFlinger Wait & Buffer Release**: Android's window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) waits on the acquire fence on the GPU before compositing. Once compositing completes, SurfaceFlinger signals the transaction's release fence callback, which enables the embedder to recycle the backing store back to its surface pool. +1. **Engine Render Pass Submission**: During [`EmbedderExternalViewEmbedder::SubmitFlutterView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L381-L387), `builder.Render()` runs [`impeller::RenderToTarget()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/display_list/aiks_dl_atlas_benchmarks.cc) → [`Canvas::EndReplay()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/display_list/canvas.cc#L2762) → [`ContextVK::FlushCommandBuffers()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc#L703-L715), which calls `vkQueueSubmit` on the `VkQueue` provided in [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L995-L1035) before invoking [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321). +2. **Embedder Barrier and Semaphore Export**: Because Vulkan executes submissions on the same `VkQueue` in submission order, `AndroidSurfaceControl` submits its image-layout transition barrier on that `VkQueue` inside `present_view_callback`, signals an embedder-owned exportable `VkSemaphore` ([`VK_KHR_external_semaphore_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_semaphore_fd.html) with `VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT`, matching [`external_semaphore_vk.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/android/external_semaphore_vk.cc#L13-L59)), and calls `vkGetSemaphoreFdKHR` to obtain a POSIX `sync_fence` file descriptor. +3. **Buffer Attachment**: `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) sequence into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`) and attach each backing store `AHardwareBuffer` and acquire fence file descriptor via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)) (or NDK [`ASurfaceTransaction_setBuffer`](https://developer.android.com/ndk/reference/group/surface-control#asurfacetransaction_setbuffer)). +4. **SurfaceFlinger Wait & Buffer Release**: Android's window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) waits on the acquire fence on the GPU before compositing. Once compositing completes, SurfaceFlinger signals the transaction's release fence callback, allowing `AndroidSurfaceControl` to recycle the backing store back to its surface pool. ```mermaid sequenceDiagram autonumber participant Impeller as Impeller Vulkan Backend participant Engine as Flutter Engine Core - participant Embedder as Android Embedder C++ + participant Embedder as AndroidSurfaceControl C++ participant SC as SurfaceControl.Transaction Java and NDK participant SF as SurfaceFlinger Android OS - Impeller->>Impeller: Submit Vulkan render commands - Impeller->>Impeller: Export VkFence / VkSemaphore to sync_fence fd via vkGetFenceFdKHR - Impeller->>Engine: Deliver backing store + fence fd - Engine->>Embedder: present_view_callback with view_id and synchronization_fence_fd + Impeller->>Impeller: Record and submit render pass to VkQueue (FlushCommandBuffers) + Impeller->>Engine: Finish backing store render + Engine->>Embedder: present_view_callback with ordered FlutterLayers + Embedder->>Embedder: Submit barrier on VkQueue and export VkSemaphore via vkGetSemaphoreFdKHR Embedder->>SC: setBuffer with surfaceControl, hardwareBuffer, fenceFd Embedder->>SC: sealFrameTransactions and swapTransactions SC->>SF: Commit transaction with acquire fence @@ -864,14 +840,6 @@ sequenceDiagram Embedder->>Embedder: Return buffer to AndroidSurfaceControl for recycling ``` -#### Frame Synchronization When Multiple Raster Frames Run Before a Choreographer Traversal ([#194591](https://github.com/flutter/flutter/pull/194591)) - -Because HCPP keeps the raster and platform threads unmerged, the raster thread can finish frame `N + 1` before the platform thread runs frame `N`'s task or before `Choreographer.doFrame()` draws frame `N`. [`flutter/flutter#194591`](https://github.com/flutter/flutter/pull/194591) fixes three multi-frame synchronization hazards across those boundaries, and each maps directly onto this `FlutterCompositor` design without additional `embedder.h` surface: - -1. **Per-Frame Raster Transaction Batching (`sealFrameTransactions`)**: In `AndroidExternalViewEmbedder2::SubmitFlutterView()`, the raster thread publishes each layer's `SurfaceControl.Transaction` and calls `sealFrameTransactions()` before posting the platform-thread task so `PlatformViewsController2` queues one sealed transaction batch per frame (`pendingRasterFrames`) instead of draining a flat transaction list across overlapping frames. Under `FlutterCompositor`, [`EmbedderExternalViewEmbedder::SubmitFlutterView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L571-L641) invokes `present_view_callback` once per frame on the raster thread with the complete `FlutterLayer` array and `synchronization_fence_fd` descriptors. That single callback is the exact per-frame boundary where the embedder seals the frame's raster transactions (or captures the transaction batch by value inside the posted closure) before dispatching to the platform thread. -2. **One Staged Frame per `Choreographer` Traversal (`QueuedFrame`)**: On the platform thread, `PlatformViewsController2` stages at most one frame's view hierarchy mutations (`onDisplayPlatformView`, `hidePlatformView`, `showOverlaySurface`, `hideOverlaySurface`) and `applyTransactionOnDraw()` call per `Choreographer` traversal, deferring subsequent frames into `pendingFrames` until the active traversal callback (`traversalFrameCallback` or `flushCompletedTraversalIfNeeded()`) fires. This logic lives in `PlatformViewsController2.java` and relies on the engine-managed `fml::MessageLoopAndroid` ALooper on the platform thread (`FlutterCustomTaskRunners.platform_task_runner = nullptr`), both of which this RFC preserves unchanged. -3. **Out-of-Order `ASurfaceTransaction_setOnComplete` Callbacks (`present_id`)**: When three or more frames arrive within a single VSync and coalesce in `pendingFrames`, two transactions targeting the same `SurfaceControl` merge into one draw pass, and Android's `SurfaceComposerClient` can invoke the newer transaction's `OnComplete` callback before the older, superseded callback. Following [#194591](https://github.com/flutter/flutter/pull/194591), the embedder-owned `AndroidSurfaceControl` swapchain tracks a monotonically increasing `present_id` per transaction so a superseded callback (`present_id <= last_completed_present_id_`) recycles its own dropped `AHardwareBuffer` texture into the pool rather than displacing `currently_displayed_texture_`. - ### Platform View Mutator Stack Serialization Platform views embedded within Flutter require spatial transformations, opacity adjustments, and geometric clips across Android composition modes (HCPP, legacy HC, and TLHC). To pass these mutators across [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) without exposing internal `//flutter/flow` or `SkPath` headers: @@ -1460,38 +1428,15 @@ Internal consumers relying on the `kMergeAfterLaunch` threading model are suppor To eliminate the remaining [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) header inclusions in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108): -- `FlutterEngineInitializeCallbackCache` and `FlutterEngineGetCallbackInformation` (`FlutterDartCallbackInformation`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and `FlutterEngineProcTable` for the process-wide Dart callback cache. +- No callback-cache functions or structs are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): the non-Dart `{name, class_name, library_path}` JSON cache table in [`DartCallbackCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) is moved from `//flutter/lib/ui/plugins/callback_cache.{h,cc}` to [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) (keeping only `LookupDartClosure` in `//flutter/lib/ui`) and lazily loads `flutter_callback_cache.json` from [`fml::paths::GetCachesDirectory()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/paths.h#L27) on first lookup or insert. - `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`, `FlutterImageGenerator`, `FlutterImageGeneratorInfo`) is added to `embedder.h` so the Android embedder can register its fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats) without subclassing `flutter::ImageGenerator`. - `FlutterEnginePrefetchDefaultFontManager` is added to `embedder.h` and `FlutterEngineProcTable` to warm up the process-wide default font manager on a background thread during `FlutterLoader.initAsync`. - `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`) is added to `embedder.h` to deliver the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. - No screenshot C API is added to `embedder.h`; `FlutterRenderer.getBitmap()` is implemented on the platform side via Android [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) so that Hybrid Composition and HCPP platform views are captured alongside Flutter content. -The Dart callback cache and the default font manager are process-wide singletons that Android uses before any engine exists, so their functions take no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`: - -1. **Process-Scoped Callback Cache ([`//flutter/lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) Severance)**: [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L148-L157) sets the cache directory and loads persisted handles during process initialization, and [`FlutterCallbackInformation.lookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java#L27-L29) resolves handles for background-execution plugins (`workmanager`, `android_alarm_manager_plus`). Because the iOS framework already exports an Objective-C class named [`FlutterCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h#L16-L17), the C struct is named `FlutterDartCallbackInformation`: +The default font manager is a process-wide singleton that Android warms up before any engine exists, so `FlutterEnginePrefetchDefaultFontManager` takes no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`: - ```c - typedef struct { - /// The size of this struct. Must be sizeof(FlutterDartCallbackInformation). - size_t struct_size; - /// Null-terminated UTF-8 name of the Dart function (owned by the engine). - const char* name; - /// Null-terminated UTF-8 declaring class name, or empty string for a - /// top-level function. Never null. - const char* class_name; - /// Null-terminated UTF-8 URI of the declaring Dart library. - const char* library_path; - } FlutterDartCallbackInformation; - - FLUTTER_EXPORT - FlutterEngineResult FlutterEngineInitializeCallbackCache( - const char* directory); - - FLUTTER_EXPORT - FlutterEngineResult FlutterEngineGetCallbackInformation( - int64_t handle, - FlutterDartCallbackInformation* info_out); - ``` +1. **Process-Scoped Callback Cache ([`//flutter/lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) Severance)**: In [`//flutter/lib/ui/plugins/callback_cache.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.cc#L38-L148), the persisted `{name, class_name, library_path}` JSON map (`SetCachePath`, `LoadCacheFromDisk`, `GetCallbackInformation`, and `GetCallbackHandle`) has no dependency on `dart:ui` or `dart_api.h`; only `GetCallback(int64_t) -> Dart_Handle` (called by `LookupDartClosure` in [`dart_runtime_hooks.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/dart_runtime_hooks.cc#L255)) touches the Dart VM C API. Moving the non-Dart JSON cache table into [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) (resolving the existing `TODO` in [`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143)) and lazily loading `flutter_callback_cache.json` from [`fml::paths::GetCachesDirectory()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/paths.h#L27) on the first `GetCallbackInformation` or `GetCallbackHandle` call severs [`//flutter/lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h) from `shell/platform/android/` without adding callback-cache functions to `embedder.h`, and removes synchronous disk I/O from the Android main thread during [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L148-L157). 2. **Embedder Image Decoders ([`//flutter/lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h#L27) Severance)**: [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L173-L178) registers a fallback decoder backed by Android's [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (API 28+, using [`ALLOCATOR_SOFTWARE`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/image/ImageDecoderDefaultImpl.java#L49-L53)) at priority `-1` for formats the built-in decoders reject, such as HEIF. Because the `ImageGeneratorRegistry` belongs to each `Engine`, decoders are configured via `FlutterProjectArgs.image_generators` (`image_generators_count`), and the engine keeps the encoded byte buffer alive until the generator's `destruction_callback` runs so the embedder can wrap it in a direct `ByteBuffer` without copying: @@ -1870,7 +1815,7 @@ In the completed target architecture, [`AndroidShellHolder`](https://github.com/ - `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Is the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12)). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. - `JniRouter`, `JniDelegate`, and `JvmInvoker` (`jni_router.h`, `jni_delegate.h`, `jvm_invoker.h`): Decouple outbound C++-to-Java JNI invocations from live Android `JNIEnv*` state through injectable delegate and provider interfaces (`FlutterJniProvider`, `HardwareBufferProvider`, `SurfaceControlProvider`, `SurfaceTransactionProvider`, and `ChoreographerProvider`). This allows the entire C-API embedder (`flutter_embedder_native_unittests`) to be unit-tested on the host machine without an Android JVM or device. - `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, and implements the window ownership contract: in [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) it stops handing out backing stores and waits only for the frame in flight before it releases the window. -- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition (HC), Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+), Texture Layer Hybrid Composition (TLHC), and Virtual Display (VD), deserialize [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays, capture per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) frame state by value to prevent cross-thread multi-view races, and attach presentation sync fences (`synchronization_fence_fd`). +- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition (HC), Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+), Texture Layer Hybrid Composition (TLHC), and Virtual Display (VD), deserialize [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays, capture per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) frame state by value to prevent cross-thread multi-view races, and attach exported Vulkan acquire fences to `SurfaceControl.Transaction` commits. - `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` (`android_hardware_buffer.cc`, `android_vulkan_external_texture.cc`): Manage [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) and `EGLImageKHR` (OpenGL ES) with UV transformation matrices (`FlutterOpenGLExternalTexture` and `FlutterOpenGLTextureFrameCallback`) and zero-copy [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) ([`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imported into `VkImage` on Vulkan) texture callbacks (`FlutterVulkanExternalTexture`, `FlutterVulkanYcbcrConversionInfo`, and `FlutterVulkanTextureFrameCallback`). - `AndroidSemanticsMapper` and `AndroidWindowMetricsMapper` (`android_semantics_mapper.cc`, `android_window_metrics_mapper.cc`): Translate [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) trees into JNI byte buffers for Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) and pack viewport metrics, insets, corner radii, and foldable display features into [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). - `AndroidVsyncWaiter` (`android_vsync_waiter.cc`): Bridges Android NDK [`AChoreographer`](https://developer.android.com/ndk/reference/group/choreographer) and Java [`Choreographer`](https://developer.android.com/reference/android/view/Choreographer) frame callbacks to `FlutterProjectArgs.vsync_callback` and forwards frame pacing timestamps via `FlutterEngineOnVsync`. @@ -1922,9 +1867,9 @@ The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec | Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnInfo`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`)
- `FlutterEngineInitializeCallbackCache` & `FlutterEngineGetCallbackInformation` (`FlutterDartCallbackInformation`)
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriCallback`) | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnInfo`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`)
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriCallback`) | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - `kDeferred` ([`FlutterDeferredRendererConfig`](#deferred-backend-selection-kdeferred), `FlutterRendererResolveInfo`)
- [`FlutterCompositor.avoid_backing_store_cache`](#window-ownership-contract) window ownership contract
- [`FlutterEngineNotifyLowMemoryWarning`](#releasing-transient-rendering-caches-flutterenginenotifylowmemorywarning) (extended `Rasterizer::NotifyLowMemoryWarning()`)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support, and releases each view's window on `surfaceDestroyed` without an engine round trip. | -| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`, [`merge_raster_thread_on_platform_views`](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views))
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath`
- `FlutterBackingStorePresentInfo.synchronization_fence_fd` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Delegates dynamic raster thread merging for HC to `EmbedderExternalViewEmbedder`, captures per-`FlutterViewId` frame state by value, and eliminates `external_view_embedder/` from `shell/platform/android/`. | +| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`, [`merge_raster_thread_on_platform_views`](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views))
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Delegates dynamic raster thread merging for HC to `EmbedderExternalViewEmbedder`, captures per-`FlutterViewId` frame state by value, and eliminates `external_view_embedder/` from `shell/platform/android/`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3262-L3264) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | | **[`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetMapping` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping across hot restarts and split APK installs. | @@ -1956,9 +1901,7 @@ Phase 1 also includes the engine changes those APIs depend on: - Renderer resolution in `PlatformViewEmbedder::SetupImpellerContext()`, including the retry and Impeller's known-bad driver check for `kDeferred` configurations. - Non-blocking rendering surface creation in `PlatformViewEmbedder::NotifyCreated()` (`Shell::OnPlatformViewCreatedAsync()`). -- The window ownership contract on `FlutterCompositor`, with refused backing stores logged at a low level. - Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) in `EmbedderExternalViewEmbedder` (`SupportsDynamicThreadMerging()` and `PostPrerollAction()`). -- Extending `Rasterizer::NotifyLowMemoryWarning()` (called by `FlutterEngineNotifyLowMemoryWarning`) to release Impeller's glyph atlases, text shadow cache, and per-frame host buffers in addition to its render-target and texture caches, and to collect any cached backing stores on the raster thread. - Impeller rendering into Vulkan backing stores, which every Vulkan `FlutterCompositor` path on Android needs and `embedder.cc` doesn't implement yet ([#187525](https://github.com/flutter/flutter/issues/187525)). Because Phase 1 modifies only `shell/platform/embedder/` and `embedder_unittests`, it proceeds in parallel with Phase 2's in-place modularization of `shell/platform/android/`, and both converge before Phase 3 cutover. @@ -2261,7 +2204,7 @@ Because Android is Flutter's largest mobile deployment target, verification must ### Engine Unit Tests (`flutter_embedder_native_unittests`) - Host-executable C++ unit tests (`flutter_embedder_native_unittests`) for `FlutterEmbedderNative` and each modularized component (`AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, and `AndroidEngineGroup`) using mock [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827) entries and injectable `JniDelegate` / `JvmInvoker` provider interfaces without requiring a live Android JVM or device. -- [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) cases for the engine behavior this RFC adds: `kDeferred` resolution and retry (a rejected Vulkan configuration leads to a second call that returns OpenGL ES), `FlutterEngineRunInitialized` returning while the resolve callback is still running, refused backing stores (the frame presents its other layers, and rendering resumes after `FlutterEngineScheduleFrame`), a window released while a frame is in flight, and `FlutterEngineNotifyLowMemoryWarning` releasing and recreating the transient rendering caches. +- [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) cases for the engine behavior this RFC adds: `kDeferred` resolution and retry (a rejected Vulkan configuration leads to a second call that returns OpenGL ES), `FlutterEngineRunInitialized` returning while the resolve callback is still running, and opt-in dynamic raster thread merging (`merge_raster_thread_on_platform_views`). - Unit tests validating the serialization and deserialization of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) structs and per-`FlutterViewId` frame state capture. ### Android Integration Tests From 03a12d9d04be0a198df58bbd434e669774bbba17 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 21 Oct 2026 17:51:28 +0000 Subject: [PATCH 31/43] Hatch any unnecessary context and simplify with sequence diagram --- ...lutter-android-embedder-c-api-migration.md | 1681 ++++------------- 1 file changed, 420 insertions(+), 1261 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 213d632..0780c0b 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -20,7 +20,8 @@ authors: ## Executive Summary and Overview -This RFC proposes a phased, in-place migration of the Flutter Android embedder ([`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android)) to the public Flutter Embedder C-API ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)). +> [!IMPORTANT] +> This RFC proposes a phased, in-place migration of the Flutter Android embedder ([`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android)) to the public Flutter Embedder C-API ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)), reducing direct internal engine `#include`s in `shell/platform/android/` from **98 to 0** while keeping `libflutter.so` as a single shared library and preserving the public Java/Kotlin API. Today, the Android embedder interacts directly with engine internals: it allocates task runners, instantiates [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113), inspects internal graphics contexts, and manages platform view compositing via private `flow` headers. This coupling causes several problems: @@ -59,99 +60,7 @@ All direct dependencies on internal engine subsystems ([`//flutter/shell/common` ## Background and Problem Statement -The Android embedder is located in [`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android). It does not use [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). Instead, it is a customized implementation that interacts directly with core engine components. - -### Current Production Architecture ([`flutter_shell_native`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L69-L75)) - -In the existing architecture, the Java embedding coordinates with native code through JNI interfaces, primarily [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108), [`PlatformViewAndroidJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h#L38), and [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38). When initializing the engine from the platform side, [`FlutterJNI.attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) invokes native attachment via [`nativeAttach`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L449), which dispatches across the JNI registration table ([`RegisterApi`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L754-L760)) into [`AttachJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L185-L197) to instantiate the shell holder: - -```cpp -// Existing JNI attachment in shell/platform/android/platform_view_android_jni_impl.cc -static jlong AttachJNI(JNIEnv* env, jclass clazz, jobject flutterJNI) { - fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI); - std::shared_ptr jni_facade = - std::make_shared(java_object); - auto shell_holder = std::make_unique( - FlutterMain::Get().GetSettings(), jni_facade, - FlutterMain::Get().GetAndroidRenderingAPI()); - if (shell_holder->IsValid()) { - return reinterpret_cast(shell_holder.release()); - } else { - return 0; - } -} -``` - -[`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) manages the lifecycle of the engine. It creates a [`flutter::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21) on the [native side](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L98-L113), instantiates a [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) object [directly](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L158-L164) through [`flutter::Shell::Create(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L170-L176), and [owns an instance](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L117-L128) of [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43): - -```cpp -// Existing Shell and ThreadHost instantiation in shell/platform/android/android_shell_holder.cc -flutter::ThreadHost::ThreadHostConfig host_config( - thread_label, mask, AndroidPlatformThreadConfigSetter); -// ... -thread_host_ = std::make_shared(host_config); - -Shell::CreateCallback on_create_platform_view = - [&jni_facade, &weak_platform_view, rendering_api](Shell& shell) { - auto platform_view_android = std::make_unique( - shell, shell.GetTaskRunners(), jni_facade, rendering_api); - weak_platform_view = platform_view_android->GetWeakPtr(); - return platform_view_android; - }; - -Shell::CreateCallback on_create_rasterizer = [](Shell& shell) { - return std::make_unique(shell); -}; - -shell_ = Shell::Create(GetDefaultPlatformData(), // window data - task_runners, // task runners - settings_, // settings - on_create_platform_view, // platform view factory - on_create_rasterizer // rasterizer factory -); -``` - -To start execution, [`FlutterJNI.runBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) forwards to [`RunBundleAndSnapshotFromLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L298-L318). This function calls [`AndroidShellHolder::Launch`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L287-L305), which instructs the shell to [run the engine](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L304) with the resolved [`RunConfiguration`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h#L29). - -[`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) [subclasses the engine's internal `flutter::PlatformView` class](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L151-L160) (defined in [`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L51)): - -```cpp -// Existing subclassing of internal engine PlatformView in shell/platform/android/platform_view_android.h -class PlatformViewAndroid final : public PlatformView { - public: - PlatformViewAndroid(PlatformView::Delegate& delegate, - const flutter::TaskRunners& task_runners, - const std::shared_ptr& jni_facade, - AndroidRenderingAPI rendering_api); - // ... - void NotifyCreated(fml::RefPtr native_window); - void NotifyDestroyed(); -}; -``` - -This inheritance allows Android to interact directly with internal classes such as [`flutter::Rasterizer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h#L114) (created via [rasterizer factory callbacks](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L130-L132)), [`flutter::Surface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h#L26) (created directly by [`PlatformViewAndroid::CreateRenderingSurface()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L426-L431)), [`flutter::TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h#L68) (manipulated via [`PlatformViewAndroid::RegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L380)), and [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211) (dispatched across JNI via [`PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1748-L1775)). In addition, window and surface lifecycle events flow from Android callbacks directly into [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L248): [`SurfaceCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L258-L269) calls [`NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L184-L219) to bind the native window with a synchronous waitable event latch: - -```cpp -// Existing synchronous surface lifecycle blocking in shell/platform/android/platform_view_android.cc -void PlatformViewAndroid::NotifyCreated( - fml::RefPtr native_window) { - if (android_surface_) { - InstallFirstFrameCallback(); - fml::AutoResetWaitableEvent latch; - fml::TaskRunner::RunNowOrPostTask( - task_runners_.GetRasterTaskRunner(), - [&latch, surface = android_surface_.get(), - native_window = std::move(native_window), jni_facade = jni_facade_]() { - surface->SetNativeWindow(native_window, jni_facade); - latch.Signal(); - }); - latch.Wait(); - } - PlatformView::NotifyCreated(); -} -``` - -Similarly, [`SurfaceChanged`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L285-L292) triggers [`NotifyChanged`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L236-L248) to resize the onscreen surface, and [`SurfaceDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L294-L296) triggers [`NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L221-L234) to tear down the raster surface context synchronously. +The Android embedder in [`engine/src/flutter/shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) bypasses [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108) instantiates [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), which creates [`flutter::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21) and [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) directly, while [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) subclasses internal [`flutter::PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L51): ```mermaid flowchart TD @@ -190,27 +99,34 @@ flowchart TD ```mermaid sequenceDiagram - participant FlutterJNI - participant AndroidShellHolder - participant ThreadHost - participant Shell - - FlutterJNI->>AndroidShellHolder: Initialize with Surface, Looper, TaskRunners - AndroidShellHolder->>ThreadHost: Create ThreadHost: Platform, UI, Raster, IO - AndroidShellHolder->>Shell: Shell::Create with settings and task runners - Shell-->>AndroidShellHolder: Shell Instance - FlutterJNI->>AndroidShellHolder: Launch with entrypoint and args - AndroidShellHolder->>Shell: RunEngine with RunConfiguration + autonumber + participant FlutterJNI as "FlutterJNI (platform_view_android_jni_impl.cc)" + participant Holder as "AndroidShellHolder" + participant PV as "PlatformViewAndroid" + participant Shell as "Shell" + participant Raster as "Raster Thread" + + FlutterJNI->>Holder: AttachJNI (construct AndroidShellHolder) + Holder->>Holder: Create ThreadHost (Platform, UI, Raster, IO) + Holder->>Shell: Shell::Create(settings, task_runners, on_create_platform_view) + Shell->>PV: on_create_platform_view (uninitialized AndroidContextDynamicImpeller) + Shell-)Raster: Post CreateImpellerContext (async on FIFO raster queue) + Shell-->>Holder: Return Shell (does not wait for SetupImpellerContext) + par Raster Thread Initializes Impeller Context + Raster->>PV: SetupImpellerContext() (probe Vulkan / fallback to GLES) + and Platform Thread Launches Dart Entrypoint + FlutterJNI->>Holder: RunBundleAndSnapshotFromLibrary (Launch) + Holder->>Shell: RunEngine(RunConfiguration) + end + FlutterJNI->>PV: SurfaceCreated(ANativeWindow) -> NotifyCreated (runs on Raster after SetupImpellerContext) ``` ### Dependency Entanglement in [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) -Because [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) reach directly into internal engine classes, [`flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) in [`engine/src/flutter/shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) depends on internal engine targets. +[`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) includes 98 internal engine headers across 9 subsystems:
-An audit of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) reveals 98 internal engine headers included across Android shell files, categorized across 9 distinct subsystems: - | Subsystem & Headers | Direct GN Dependencies | Key Internal Headers | Functional Purpose in Embedder | | :--- | :--- | :--- | :--- | | **Impeller & Graphics Context Setup**
(29 headers) | [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller)
[`//flutter/impeller/toolkit/android`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/android)
[`//flutter/impeller/toolkit/egl`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/egl)
[`//flutter/impeller/toolkit/gles`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/gles)
[`//flutter/impeller/toolkit/glvk`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/glvk) | [`renderer/backend/vulkan/context_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h)
[`surface_context_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.h)
[`renderer/backend/gles/context_gles.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h)
[`toolkit/android/hardware_buffer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/toolkit/android/hardware_buffer.h)
[`renderer/backend/vulkan/android/ahb_texture_source_vk.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h) | Initializing rendering contexts on the raster thread ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)), creating Vulkan and EGL swapchains, and managing zero-copy [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imports. | @@ -224,94 +140,29 @@ An audit of [`shell/platform/android/`](https://github.com/flutter/flutter/tree/ | **Foundational Utilities & Concurrency**
(24 headers) | [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | [`fml/raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)
[`fml/task_runner.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h)
[`fml/message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.h)
[`fml/concurrent_message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/concurrent_message_loop.h)
[`fml/platform/android/jni_util.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/jni_util.h)
[`fml/platform/android/scoped_java_ref.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/scoped_java_ref.h) | Message loops, JNI handle wrappers, task scheduling, and raster thread leasing. |
-### Technical Consequences of Coupling - -This level of coupling creates several concrete maintenance problems: - -- Fragile refactoring: Any change to private engine internals (such as Impeller backend refactors or Flow mutator stack updates) breaks the Android embedder. -- Prolonged rebuild times: Compiling `libflutter.so` requires rebuilding internal engine translation units even when only platform-specific code changes. -- Lack of API contract: No stable boundary prevents Android embedder code from reaching into private engine internals, which inhibits engine modularization. -- Feature parity lag: Because Android and iOS bypass the embedder API, public engine capabilities often lag behind engine internals, which prevents out-of-tree platforms from achieving feature parity with mobile platforms. - ## Detailed Design -To allow the Android embedder to communicate with the engine exclusively through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the C-API must be extended to cover Android-specific subsystem requirements. Each proposed extension corresponds directly to uncoupling one of the internal subsystems identified in the dependency audit and supporting a target embedder component (`FlutterEmbedderNative`, `JniRouter` / `JniDelegate` / `JvmInvoker`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17)). +The following sections specify the [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) additions and behavioral changes required to decouple the Android embedder from internal engine headers. -### Graphics Surface Setup, Context Ownership, and Surface Lifecycle ([`SetupImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L128)) +### Initialization -To let the Android embedder choose between Vulkan (`kVulkan`) and OpenGL ES (`kOpenGL`) on the raster thread without blocking the Android UI thread during startup, this RFC adds a `kDeferred` renderer type (`FlutterDeferredRendererConfig`) and a `FlutterRendererConfigResolveCallback` callback to [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) (see [Deferred Backend Selection (`kDeferred`)](#deferred-backend-selection-kdeferred)): - -- At startup, the Android embedder passes `type = kDeferred` with its `resolve_callback` to [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) so the UI thread does not load `libvulkan.so` or initialize a GPU context synchronously. -- While the engine launches, the raster thread runs `CreateImpellerContext` ([`PlatformViewEmbedder::SetupImpellerContext()`](#deferring-impellercontext-and-surface-creation-in-shellplatformembedder-setupimpellercontext)) and invokes `FlutterRendererConfigResolveCallback`, which probes the device and fills in a concrete `kVulkan` or `kOpenGL` `FlutterRendererConfig`. -- If the engine rejects the returned `kVulkan` configuration (because context creation fails or the driver matches Impeller's known-bad driver list), it re-invokes `FlutterRendererConfigResolveCallback` with a `FlutterRendererResolveInfo` rejection reason so the embedder can destroy its Vulkan handles and return `kOpenGL`. - -#### How `SetupImpellerContext` Works on Android Today +> [!IMPORTANT] +> - **Embedder API additions (`embedder.h`)**: Adds `kDeferred` (`FlutterDeferredRendererConfig`), `FlutterRendererConfigResolveCallback`, `FlutterRendererResolveInfo`, and `FlutterRendererRejectReason` to [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055). +> - **Behavioral changes**: +> - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36), [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h), and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h) move [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) creation from their UI-thread constructors into `PlatformViewEmbedder::SetupImpellerContext()` on the raster thread, and `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the FIFO raster task runner instead of blocking the UI thread on a latch. +> - If the engine rejects a `kVulkan` configuration returned by `resolve_callback` (because context creation fails or the driver matches [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)), it re-invokes `resolve_callback` with the rejection reason so the embedder can destroy its `VkDevice` and return `kOpenGL`.
+Why shell/platform/embedder/ must change to preserve non-blocking startup -[`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added in [#167765](https://github.com/flutter/flutter/pull/167765) because Vulkan driver and context initialization can take 100+ ms on some Android devices. During engine startup, [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) creates [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) (which constructs [`AndroidContextDynamicImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h#L23) as an empty shell) on the Android UI thread and posts a `CreateImpellerContext` task ([`shell.cc#L314-L333`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L314-L333)) to the raster task runner to run [`PlatformViewAndroid::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L567-L570): - -```mermaid -sequenceDiagram - autonumber - participant UI as "Android UI Thread" - participant PV as "PlatformViewAndroid / AndroidContextDynamicImpeller" - participant Raster as "Engine Raster Thread" - - UI->>PV: Shell::CreateWithSnapshot() creates empty AndroidContextDynamicImpeller shell - UI-)Raster: Post CreateImpellerContext task (non-blocking) - UI-->>UI: Continue startup without waiting for GPU initialization - Raster->>PV: PlatformViewAndroid::SetupImpellerContext() - PV->>PV: GetActualRenderingAPIForImpeller() (SoC/property checks, VkInstance/VkDevice, ContextVK::Create, IsKnownBadDriver) - opt vk_context_ is null (Vulkan unavailable or driver rejected) - PV->>PV: Construct AndroidContextGLImpeller (egl::Display, ContextGLES::Create) - end - Raster->>PV: GetImpellerContext() - PV-->>Raster: std::shared_ptr -``` - -That single raster-thread call currently performs two distinct jobs inside [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android): - -1. **Choosing the backend and creating the underlying GPU device**: [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120) checks system properties and a known-bad SoC list, loads `libvulkan.so`, creates the [`VkInstance`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkInstance.html) and [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), checks Impeller's known-bad driver list ([`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)) in [`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.cc#L65-L69), and falls back to OpenGL ES if any step fails. -2. **Constructing the [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70)**: [`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.h#L16) and [`AndroidContextGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h#L15) call [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h) or [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h) to set up shader library mappings, the pipeline cache, the memory allocator, and descriptor/command pools. - -#### Why Moving to `embedder.h` Requires Changes on Both Sides - -When Android migrates to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `PlatformViewAndroid` and `AndroidContextDynamicImpeller` are removed. The Android embedder no longer creates or owns `impeller::Context`; it creates only raw Vulkan (`VkInstance`, `VkPhysicalDevice`, `VkDevice`, [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html)) or EGL (`EGLDisplay`, `EGLContext`, `EGLConfig`) handles and passes them in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) to [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970). Inside the engine, [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h) (`EmbedderSurfaceVulkanImpeller` and `EmbedderSurfaceGLImpeller`) own the `impeller::Context`. +[`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added in [#167765](https://github.com/flutter/flutter/pull/167765) because Vulkan driver and context initialization can take 100+ ms on Android devices, but [#167765](https://github.com/flutter/flutter/pull/167765) only wired it up in `PlatformViewAndroid`: -Preserving Android's non-blocking startup behavior while staying on the existing two-phase initialization API ([`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007)) requires three coordinated changes: +- [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) currently construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread during `FlutterEngineRunInitialized`. +- [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) (called by `FlutterEngineRunInitialized` right after `LaunchShell()`) posts `CreateRenderingSurface()` to the FIFO raster task runner behind `CreateImpellerContext` and blocks the UI thread on `latch.Wait()`. -1. **The backend choice must move to the raster thread.** [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1042-L1055) requires an explicit `type` (`kVulkan` or `kOpenGL`) and live Vulkan handles when [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) is called, before [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) or the raster thread runs. Android makes this choice inside `CreateImpellerContext` today. A new `kDeferred` renderer type lets the embedder make it there too (see [Deferred Backend Selection](#deferred-backend-selection-kdeferred)). -2. **[`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36) and [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) must be wired up to [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) in [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder)**, because [#167765](https://github.com/flutter/flutter/pull/167765) only implemented [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) on [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43). In [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder) today: - - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) does not override `SetupImpellerContext()`. It only overrides `GetImpellerContext()`, which calls `embedder_surface_->CreateImpellerContext()`. - - Despite the name [`CreateImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L25), both [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread (inside [`InferVulkanPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L619-L691) and [`InferOpenGLPlatformViewCreationCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L307-L500)), and their `CreateImpellerContext()` methods simply return the already-created `context_` member. -3. **The internal `NotifyCreated()` call in [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) must not block on the raster thread.** In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2515-L2526), `FlutterEngineRunInitialized` calls `embedder_engine->LaunchShell()`, `embedder_engine->NotifyCreated()`, and `embedder_engine->RunRootIsolate()`: - - ```cpp - // Existing implementation in shell/platform/embedder/embedder.cc (L2515-L2534) - // Step 1: Launch the shell. - if (!embedder_engine->LaunchShell()) { - return LOG_EMBEDDER_ERROR(kInvalidArguments, ...); - } - - // Step 2: Tell the platform view to initialize itself. - if (!embedder_engine->NotifyCreated()) { - return LOG_EMBEDDER_ERROR(kInternalInconsistency, ...); - } - - // Step 3: Launch the root isolate. - if (!embedder_engine->RunRootIsolate()) { - return LOG_EMBEDDER_ERROR(kInvalidArguments, ...); - } - ``` - - [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) posts `CreateRenderingSurface()` to the FIFO raster task runner, queued **behind** `CreateImpellerContext`, and blocks the UI thread on a latch (`latch.Wait()`) before passing the surface to [`Shell::OnPlatformViewCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L973-L1062). Once context creation moves to the raster thread, that latch makes the UI thread wait for `SetupImpellerContext()` anyway, and with `kDeferred` it would also wait for the embedder's resolve callback. Launching the shell doesn't have this problem: the rasterizer is created in a raster task posted before `CreateImpellerContext` ([`shell.cc#L295-L335`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L295-L335)), and `Shell::Setup` waits only for the engine, the rasterizer and the IO manager ([`shell.cc#L438-L441`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L438-L441)), which the IO thread hands over before it waits for the context ([`shell.cc#L382-L391`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L382-L391)). `FlutterEngineRunInitialized` continues to call `embedder_engine->NotifyCreated()`, and `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the raster task runner instead of waiting on a latch (see [Deferring `impeller::Context` and Surface Creation](#deferring-impellercontext-and-surface-creation-in-shellplatformembedder-setupimpellercontext)). Creating that surface before Android provides an `ANativeWindow` is fine, because with a compositor it holds only the rendering context (see [Renderer Availability and Surface Lifecycle](#renderer-availability-and-surface-lifecycle)). +Moving context creation into `PlatformViewEmbedder::SetupImpellerContext()` and making `PlatformViewEmbedder::NotifyCreated()` queue [`Shell::OnPlatformViewCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L973-L1062) on the raster task runner without a latch keeps both backend probing (`resolve_callback`) and `impeller::Context` creation off the UI thread.
-#### Deferred Backend Selection (`kDeferred`) - -Android chooses between Vulkan and OpenGL ES on the raster thread today. To keep that, `embedder.h` adds a renderer type whose configuration is a callback. The engine calls it on the raster thread inside `CreateImpellerContext`, and the callback returns a concrete configuration: - ```c // FlutterRendererConfig gains a struct tag so that the callback below can // refer to it. Adding a tag to a typedef'd struct is source and ABI compatible. @@ -390,127 +241,6 @@ struct FlutterRendererConfig { }; ``` -**What the Android callback does.** It runs the checks Android runs today in [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L120): known emulators, Huawei devices (`ro.com.google.clientidbase`), MediaTek devices with `ro.vendor.build.version.sdk < 32`, and the `ro.product.board` deny-list ([`IsKnownBadSOC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L68-L77)). If one matches, it returns `kOpenGL` without loading Vulkan. Otherwise it loads `libvulkan.so`, creates the `VkInstance`, `VkDevice` and `VkQueue`, and returns `kVulkan`. `AndroidContextDynamicImpeller` does the same work on the raster thread today, so the UI thread never waits for it, and nothing has to start earlier on a `FlutterLoader` executor. A process-wide singleton (`VulkanDeviceOwner`) holds the device, so the callbacks of later [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) instances return the same handles without probing again. - -**When the engine rejects a configuration.** Android falls back to OpenGL ES today when Vulkan context creation fails or the driver is on Impeller's known-bad list ([`AndroidContextVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_vk_impeller.cc#L65-L69)). In the embedder path, [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc#L176-L188) still checks the embedder's device, but a failure leaves the engine without a renderer. For a `kDeferred` configuration the engine keeps Android's fallback: - -1. It validates the returned configuration and creates the rendering context from it. -2. It applies Impeller's known-bad driver check ([`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)). Explicit `kVulkan` configurations from other embedders keep their current behavior. -3. If either step fails, it releases what it created from that configuration and calls the callback again with the reason. The engine never takes ownership of embedder-provided Vulkan handles, so the Android callback destroys its `VkDevice` and returns `kOpenGL`. - -The driver blocklist stays in one place inside Impeller, and `embedder.h` needs no driver query function. - -The callback comes with these rules: - -- **Threading.** The callback runs on the raster task runner. It may block, for example while it creates the Vulkan device, but it must not wait on the platform thread, because `FlutterEngineDeinitialize` and `FlutterEngineShutdown` wait for it. No other call waits for it. The engine reads the rendering context only on the raster and IO threads, and it creates its rendering surface after resolution (see the next section). -- **Failure.** If the callback returns `false`, or the engine rejects every configuration, the engine runs without a renderer and the isolate keeps running. Frames are dropped before any backing store is requested. -- **Validation.** `FlutterEngineInitialize` checks only that `resolve_callback` is set. The checks it runs today on a concrete configuration, such as required callbacks being present and the backend being compiled in, run on the raster thread after resolution. Context-creation failures are only logged today, and `FlutterEngineRunInitialized` reports success either way. -- **External textures.** `FlutterEngineRegisterExternalTexture` may be called after `FlutterEngineRunInitialized` returns while `resolve_callback` is still running on the raster thread (textures registered before `FlutterEngineRunInitialized` are queued by `FlutterEmbedderNative` and flushed after `FlutterEngineRunInitialized`; see [Alternative Considered](#alternative-considered-splitting-startup-into-separate-shell-and-isolate-launch-apis)). Registration only records the texture ID. The engine binds it to the resolved configuration's frame callback on the raster thread, where every texture operation already runs after `CreateImpellerContext`. -- **Spawn.** Spawned engines share the parent's task runners ([`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L718-L722)), so the parent's resolution runs first. A spawned engine reuses the resolved configuration and rendering context, and the callback is not called for it. `FlutterEngineSpawn` may be called before the parent has resolved. -- **Settings.** The resolved type must be valid for the engine's settings. Under Impeller, Android chooses only between `kVulkan` and `kOpenGL`. - -#### Deferring `impeller::Context` and Surface Creation in `shell/platform/embedder/` (`SetupImpellerContext`) - -To keep [`impeller::ContextVK::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L106) and [`impeller::ContextGLES::Create`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L27-L30) on the raster thread instead of running them in the [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) constructors on the UI thread, and to keep [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) from waiting for the raster thread, [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder) is updated as follows: - -- [`EmbedderSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L15-L37) adds `virtual void SetupImpellerContext() {}`. -- [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L157-L160) overrides `PlatformView::SetupImpellerContext()`. For a `kDeferred` configuration it calls `resolve_callback`, validates the result, builds the `EmbedderSurface` and external texture resolver for the resolved type, and creates the context, retrying as described above. For a concrete configuration it forwards the call to `embedder_surface_->SetupImpellerContext()`. -- [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L20-L95) validates the `VulkanDispatchTable` callbacks and stores `impeller::ContextVK::Settings` (including `EmbedderData` handles and extension lists) in its constructor without creating the Impeller context. It overrides `SetupImpellerContext()` to run `context_ = impeller::ContextVK::Create(std::move(settings_))` on the raster thread when `Shell::CreateWithSnapshot` executes the `CreateImpellerContext` task. -- [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L77-L132) likewise validates `gl_dispatch_table_` in its constructor and moves `gl_make_current_callback()`, `ProcTableGLES` creation, `impeller::ContextGLES::Create(...)`, and `gl_clear_current_callback()` into `EmbedderSurfaceGLImpeller::SetupImpellerContext()` on the raster thread. -- `FlutterEngineRunInitialized` continues to call `embedder_engine->NotifyCreated()` (`platform_view->NotifyCreated()`) internally unchanged. [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L594) becomes `virtual`, and `PlatformViewEmbedder::NotifyCreated()` queues rendering surface creation on the FIFO raster task runner without waiting on a latch. That task runs right after `CreateImpellerContext`: it calls `CreateRenderingSurface()`, `Rasterizer::Setup`, and asks the UI thread to schedule a frame. Frames requested before then are dropped quietly, as they are today before a surface exists ([`rasterizer.cc#L523-L524`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L523-L524)). The latches in `PlatformView::NotifyCreated()` ([`platform_view.cc#L66-L79`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L66-L79)) and [`Shell::OnPlatformViewCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1023-L1054) only existed to keep `platform_view` alive while the raster and IO tasks ran and to create the IO resource context; [`Shell::~Shell()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L634-L673) already drains the raster task runner before destroying `platform_view_`, and the IO thread already creates its resource context during shell setup ([`shell.cc#L386-L391`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L386-L391)). - -```cpp -// In shell/platform/embedder/platform_view_embedder.cc -void PlatformViewEmbedder::SetupImpellerContext() { - if (!deferred_config_) { - if (embedder_surface_ != nullptr) { - embedder_surface_->SetupImpellerContext(); - } - return; - } - FlutterRendererResolveInfo info = {}; - info.struct_size = sizeof(FlutterRendererResolveInfo); - while (true) { - FlutterRendererConfig config = {}; - if (!deferred_config_->resolve_callback(user_data_, &info, &config)) { - FML_LOG(ERROR) << "The embedder could not provide a renderer."; - return; // The engine runs without a renderer. - } - // Validates the configuration, then builds the EmbedderSurface and the - // external texture resolver for its type. - FlutterRendererRejectReason reason = BuildEmbedderSurface(config); - if (reason == kFlutterRendererRejectReasonNone) { - embedder_surface_->SetupImpellerContext(); - // Context creation, then Impeller's known-bad driver check for Vulkan. - reason = CheckImpellerContext(); - } - if (reason == kFlutterRendererRejectReasonNone) { - return; - } - embedder_surface_.reset(); // Never destroys embedder-provided handles. - info.attempt++; - info.rejected_type = config.type; - info.reject_reason = reason; - } -} - -std::shared_ptr PlatformViewEmbedder::GetImpellerContext() - const { - if (embedder_surface_ == nullptr) { - return nullptr; - } - return embedder_surface_->CreateImpellerContext(); -} - -void PlatformViewEmbedder::NotifyCreated() { - // Called by FlutterEngineRunInitialized on the platform thread. - // Queues surface creation behind CreateImpellerContext on the FIFO raster - // task runner and returns without blocking the platform thread on a latch. - delegate_.OnPlatformViewCreatedAsync(); -} - -// In shell/platform/embedder/embedder_surface_vulkan_impeller.cc -void EmbedderSurfaceVulkanImpeller::SetupImpellerContext() { - if (context_ || !valid_) { - return; - } - context_ = impeller::ContextVK::Create(std::move(settings_)); - if (!context_) { - FML_LOG(ERROR) << "Failed to initialize Vulkan Context."; - valid_ = false; - } -} - -// In shell/common/shell.cc -// Called on the platform thread by PlatformViewEmbedder::NotifyCreated(). -// Returns without waiting; the raster task runs after CreateImpellerContext -// because the raster task runner is FIFO. -void Shell::OnPlatformViewCreatedAsync() { - fml::TaskRunner::RunNowOrPostTask( - task_runners_.GetRasterTaskRunner(), - [platform_view = platform_view_.get(), - rasterizer = rasterizer_->GetWeakPtr(), - engine = engine_->GetWeakPtr(), - ui_task_runner = task_runners_.GetUITaskRunner(), - &waiting_for_first_frame = waiting_for_first_frame_]() { - // ~Shell drains the raster task runner before destroying platform_view_. - std::unique_ptr surface = - platform_view->CreateRenderingSurface(); - if (!rasterizer || !surface || !surface->IsValid()) { - FML_LOG(ERROR) << "Could not create the rendering surface."; - return; - } - rasterizer->Setup(std::move(surface)); - waiting_for_first_frame.store(true); - fml::TaskRunner::RunNowOrPostTask(ui_task_runner, [engine]() { - if (engine) { - engine->ScheduleFrame(); - } - }); - }); -} -``` - ```mermaid sequenceDiagram autonumber @@ -522,7 +252,7 @@ sequenceDiagram UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig{kDeferred}) UI->>Emb: FlutterEngineRunInitialized() Emb-)Raster: LaunchShell() posts CreateImpellerContext - Emb-)Raster: NotifyCreated() queues surface creation + Emb-)Raster: NotifyCreated() queues surface creation (FIFO, no latch) Emb->>Emb: RunRootIsolate() Emb-->>UI: Return without waiting for the raster thread par Raster thread resolves the renderer @@ -530,13 +260,14 @@ sequenceDiagram Cb->>Cb: System property and SoC checks, then VkInstance, VkDevice and VkQueue Cb-->>Raster: kVulkan configuration Raster->>Raster: Validate, build EmbedderSurfaceVulkanImpeller, ContextVK::Create - Raster->>Raster: Known-bad driver check + Raster->>Raster: Known-bad driver check (DriverInfoVK::IsKnownBadDriver) opt Configuration rejected Raster->>Cb: resolve_callback(attempt 1, reject_reason) - Cb-->>Raster: kOpenGL configuration + Cb-->>Raster: Destroy VkDevice, return kOpenGL configuration Raster->>Raster: Build EmbedderSurfaceGLImpeller, ContextGLES::Create end - Raster->>Raster: Create the rendering surface (context only), Rasterizer::Setup + Raster->>Raster: CreateRenderingSurface (context only), Rasterizer::Setup + Raster-)UI: ScheduleFrame() and UI Thread Inflates Activity View Hierarchy UI->>UI: Activity.onStart() / onResume() / WindowManager layout end @@ -544,214 +275,53 @@ sequenceDiagram UI->>Emb: FlutterEngineScheduleFrame(engine) ``` -After that, the Android embedder creates and destroys `ANativeWindow` swapchains across Android [`Activity`](https://developer.android.com/reference/android/app/Activity) lifecycle events without recreating the `VkDevice`, `EGLContext`, or `impeller::Context`, and without a surface lifecycle call into the engine (see the next section). - -### Renderer Availability and Surface Lifecycle - -Android window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) are created and destroyed by the OS during [`Activity`](https://developer.android.com/reference/android/app/Activity) transitions, configuration changes, and backgrounding while the `FlutterEngine` remains alive. Under this RFC: - -- No surface lifecycle functions (`NotifyCreated` / `NotifyDestroyed`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). -- Because Android renders every frame through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) with `avoid_backing_store_cache = true`, the engine's internal GPU surface is context-only (`render_to_surface = false`) and never holds the `ANativeWindow`. -- The Android embedder owns window and swapchain lifetime across `surfaceCreated`, `surfaceChanged`, and `surfaceDestroyed`—returning `false` from `create_backing_store_callback` when no window is attached or when the OS invalidates the surface mid-frame, and re-arming the first-frame callback with [`FlutterEngineSetNextFrameCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3638-L3641). -- The Android embedder calls the existing [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) C API on `onTrimMemory` and when the last view detaches in `surfaceDestroyed` to release Impeller's offscreen render-target and cached texture pools (wired in [#193497](https://github.com/flutter/flutter/pull/193497)), requiring no changes to `embedder.h` or `Rasterizer::NotifyLowMemoryWarning()`. - -#### Decoupling Configuration from Surface Availability - -Static graphics configuration (the Vulkan device and queue handles, or the EGL context and callbacks) persists in [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) for the lifetime of the engine, while presentation surfaces attach and detach across `Activity` lifecycle events. Recreating `FlutterRendererConfig` on every window change would re-initialize the GPU context and discard Impeller pipeline caches. - -In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) calls [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89) as an `ANativeWindow` is attached or detached. Its `AndroidSurface` renders into that window, so the raster thread has to stop using the window before `surfaceDestroyed()` returns. - -Under this RFC the engine never holds the window, because Android renders every frame through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) with `avoid_backing_store_cache` set to `true`: - -- When a compositor is installed, the engine's own GPU surface holds only the rendering context and presents nothing. [`EmbedderSurfaceGLImpeller::CreateGPUSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L203-L214) passes `render_to_surface = false` in that case, and [#193000](https://github.com/flutter/flutter/pull/193000) made `EmbedderSurfaceVulkanImpeller` do the same. -- The engine requests every backing store inside the frame that uses it ([`embedder_external_view_embedder.cc#L561-L572`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L561-L572)). With the cache off, it collects each one as soon as `present_view_callback` returns ([`#L627-L643`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L627-L643)). -- If `create_backing_store_callback` returns `false`, the engine skips that layer ([`#L261-L265`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L261-L265)) and still presents the frame's other layers ([`#L465-L468`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L465-L468)). +### Platform Views -So `embedder.h` gains no surface lifecycle functions. The embedder owns the window's lifetime through the compositor. The engine keeps its context-only rendering surface from resolution until shutdown, and [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) releases the transient GPU caches inside it (see [Releasing Transient Rendering Caches (`FlutterEngineNotifyLowMemoryWarning`)](#releasing-transient-rendering-caches-flutterenginenotifylowmemorywarning)). +> [!IMPORTANT] +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterBackingStoreConfig.is_root_layer` (`bool`) and `FlutterCompositor.merge_raster_thread_on_platform_views` (`bool`). +> - **Behavioral changes**: +> - Replaces [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) with [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322), using `is_root_layer` to distinguish the bottom-most background layer from full-screen overlay layers above a platform view. +> - [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) implements `SupportsDynamicThreadMerging()` and `PostPrerollAction()` when `merge_raster_thread_on_platform_views` is `true`, dynamically merging the raster task runner onto the platform thread during Hybrid Composition (HC) frames. -#### Window Ownership Contract - -The Android embedder (`AndroidSurfaceControl`) follows these rules: - -- Because the Android OS owns the underlying `Surface` (`BufferQueue` / `SurfaceFlinger`) and can invalidate it or dispatch `surfaceDestroyed` while a raster frame is in flight, the embedder cannot guarantee that the window remains valid from `create_backing_store_callback` through `present_view_callback`. When no window is attached or acquiring or presenting a buffer fails (such as `VK_ERROR_SURFACE_LOST_KHR`, `VK_ERROR_OUT_OF_DATE_KHR`, or `EGL_BAD_SURFACE`), `create_backing_store_callback` returns `false` (or `present_view_callback` drops the presentation) and `collect_backing_store_callback` releases any per-frame resources. -- On OpenGL ES, the engine calls `make_current` at the start of every frame and outside frames, and doesn't call `clear_current` after each frame. `make_current` therefore binds a pbuffer or a surfaceless context, and the embedder binds the window surface only while it hands out the root backing store (`is_root_layer`). -- On Vulkan, the GPU has to finish with presented images before the swapchain is destroyed, so the embedder waits for its last present to complete first. - -Embedders that render without a compositor also get only per-frame handles from the engine, through `make_current`/`fbo_callback`/`present` or `get_next_image_callback`/`present_image_callback`, and can release their window the same way. Refusing in those callbacks fails the whole frame instead of one layer. - -#### Lifecycle Mapping - -The Android embedder maps [`SurfaceHolder.Callback`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback) events on the platform thread as follows: - -- [`surfaceCreated`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)): The embedder creates its swapchain (or `SurfaceControl` tree) for the [`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window) and starts handing out backing stores. It re-arms its first-frame callback with [`FlutterEngineSetNextFrameCallback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3638-L3641), which `PlatformViewAndroid::NotifyCreated` does internally today, and calls [`FlutterEngineScheduleFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3620-L3621). -- [`surfaceChanged`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder,%20int,%20int,%20int)): The embedder sends the updated physical dimensions and device pixel ratio via `FlutterEngineSendWindowMetricsEvent`. -- [`surfaceDestroyed`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)): The embedder stops handing out new backing stores, synchronizes with any active callback on the raster thread, destroys its swapchain, and releases the `ANativeWindow`. There is no engine teardown to wait for. If no other view of the engine is visible, it calls the existing [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554), which releases the same engine GPU caches that `NotifyDestroyed` frees today. - -#### Releasing Transient Rendering Caches (`FlutterEngineNotifyLowMemoryWarning`) - -`avoid_backing_store_cache` covers only the backing stores. Impeller also keeps transient offscreen render-target and cached `DlImage` texture pools inside the engine's rendering surface (`AiksContext`). The engine allocates these itself; they never go through `create_backing_store_callback`, so the embedder cannot free them directly. - -[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) already provides [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) ([`embedder.cc#L3597-L3617`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3597-L3617)): - -```c -// Existing API in shell/platform/embedder/embedder.h (L3535-L3554) -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineNotifyLowMemoryWarning( - FLUTTER_API_SYMBOL(FlutterEngine) engine); +```mermaid +flowchart TD + Req["Platform View Request in PlatformViewsController"] --> IsHCPP{"enable_surface_control AND API 34+ AND Impeller Vulkan?"} + IsHCPP -- "Yes" --> HCPP["HCPP: configureForHybridComposition++ (FlutterCompositor, unmerged threads)"] + IsHCPP -- "No" --> CreationPath{"Plugin Creation Method"} + CreationPath -- "initExpensiveAndroidView (createForPlatformViewLayer)" --> HC["HC: configureForHybridComposition (FlutterCompositor, merge_raster_thread_on_platform_views = true)"] + CreationPath -- "initAndroidView (createForTextureLayer)" --> HasSurfaceView{"View hierarchy contains SurfaceView (VIEW_TYPES_REQUIRE_NON_TLHC)?"} + HasSurfaceView -- "No" --> TLHC["TLHC: configureForTextureLayerComposition (External Texture C-API)"] + HasSurfaceView -- "Yes, TEXTURE_WITH_VIRTUAL_FALLBACK" --> VD["VD: configureForVirtualDisplay (External Texture C-API)"] + HasSurfaceView -- "Yes, TEXTURE_WITH_HYBRID_FALLBACK" --> HC ``` -`FlutterEngineNotifyLowMemoryWarning` calls [`Shell::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L750-L770) (which notifies the Dart VM and posts [`Rasterizer::NotifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L173-L192) to the raster thread) and sends the `{"type": "memoryPressure"}` message on the `flutter/system` channel. Since [#193497](https://github.com/flutter/flutter/pull/193497), `Rasterizer::NotifyLowMemoryWarning()` calls `aiks_context->ClearRenderTargetCache()` and `aiks_context->ClearCachedTextures()` while keeping `impeller::Context` and its pipeline caches intact, so no changes to `embedder.h` or `Rasterizer::NotifyLowMemoryWarning()` are needed: - -- When Android dispatches [`ComponentCallbacks2.onTrimMemory`](https://developer.android.com/reference/android/content/ComponentCallbacks2#onTrimMemory(int)) (including `TRIM_MEMORY_UI_HIDDEN = 20`, which satisfies [`level >= TRIM_MEMORY_RUNNING_LOW`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java#L1043-L1047) in `FlutterActivityAndFragmentDelegate.onTrimMemory`), [`FlutterJNI.notifyLowMemoryWarning()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1401-L1407) invokes `FlutterEngineNotifyLowMemoryWarning(engine)`. -- When the last view of an engine detaches in `surfaceDestroyed`, `AndroidSurfaceControl` also calls `FlutterEngineNotifyLowMemoryWarning(engine)` so that cached add-to-app engines whose host app stays in the foreground release the same GPU caches that `NotifyDestroyed` frees today. - -#### Alternative Considered: Splitting Startup into Separate Shell and Isolate Launch APIs - -An alternative design was prototyped that split [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) into separate shell-creation and isolate-launch C APIs so that [`FlutterJNI.attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) could create the [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) before [`DartExecutor.executeDartEntrypoint()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java#L133-L143) ([`FlutterJNI.runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)) specified the Dart entrypoint: - ```c -// Alternative C-API additions (not adopted): -// In FlutterProjectArgs: -bool defer_root_isolate_launch; -const char* initial_lifecycle_state; - typedef struct { + /// The size of this struct. Must be sizeof(FlutterBackingStoreConfig). size_t struct_size; - const char* assets_path; - const char* custom_dart_entrypoint; - const char* dart_entrypoint_library_uri; - size_t dart_entrypoint_argc; - const char* const* dart_entrypoint_argv; - size_t custom_asset_resolvers_count; - const FlutterCustomAssetResolver** custom_asset_resolvers; -} FlutterEngineRunConfiguration; - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineLaunchShell( - FLUTTER_API_SYMBOL(FlutterEngine) engine); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineLaunchEngine( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterEngineRunConfiguration* config); + /// The size of the render target the engine expects to render into. + FlutterSize size; + /// The identifier for the view that the engine will use this backing store to + /// render into. + FlutterViewId view_id; + /// True if this backing store is for the bottom-most layer of the frame for + /// `view_id`, with no platform view composited beneath it. The engine + /// requests at most one such backing store per view per frame, and none when + /// all Flutter contents are composited above platform views. False for all + /// other layers. Read this field only if `struct_size` covers it, and treat + /// it as false otherwise. + /// + /// An embedder may return the view's on-screen render target (for example, + /// the next swapchain image or default framebuffer) when this is true. The + /// engine's backing store cache ignores this field, so such an embedder must + /// set `FlutterCompositor.avoid_backing_store_cache` to true. + bool is_root_layer; +} FlutterBackingStoreConfig; ``` -
- -Under this alternative, [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) called [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) (with `defer_root_isolate_launch = true` and `initial_lifecycle_state = "AppLifecycleState.detached"`) followed by `FlutterEngineLaunchShell`, and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) later called `FlutterEngineLaunchEngine` with `FlutterEngineRunConfiguration`. The motivation was to mirror legacy [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) startup ordering so that platform messages (`flutter/localization`, `flutter/navigation` `setInitialRoute`, `flutter/settings`), viewport metrics, and external textures sent between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`executeDartEntrypoint()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java#L133-L143) could be buffered directly inside the engine's [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52), and so [`Shell::CreateWithSnapshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L449-L509) could post [`CreateImpellerContext`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface.h#L25) to the raster thread during [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442). - -Why does pre-run state exist in the two-phase [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) + [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) model? In [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L2301-L2537), `FlutterEngineInitialize` takes [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) (which includes the Dart entrypoint and asset resolvers) and constructs `EmbedderEngine` with `shell_ == nullptr`, while `FlutterEngineRunInitialized` calls both `LaunchShell()` (`Shell::Create`) and `RunRootIsolate()`. Every [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.cc#L143-L195) method (`SendPlatformMessage`, `SetViewportMetrics`, `RegisterTexture`) checks `IsValid()` (`shell_ != nullptr`) and fails before `FlutterEngineRunInitialized` runs. On Android, the [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L408-L433) constructor calls `attachToJni()` (`attachToNative()`), sends initial locales (`flutter/localization`), and registers plugins (`GeneratedPluginRegister.registerGeneratedPlugins`) **before** [`DartExecutor.executeDartEntrypoint()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java#L133-L143) calls [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) with the entrypoint and asset arguments. Because `FlutterEngineInitialize` and `FlutterEngineRunInitialized` run in `runBundleAndSnapshotFromLibrary()`, `FlutterEmbedderNative` records messages, viewport metrics, and texture IDs sent after `attachToNative()` and flushes them as soon as `FlutterEngineRunInitialized` returns. - -This alternative was rejected in favor of keeping the existing two-phase [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) sequence (paired with `kDeferred` and non-blocking `NotifyCreated()`) for three reasons: - -1. **Avoids a multi-stage initialization state machine in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)**: [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) already separates startup into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) so embedders can obtain the [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and bind custom task runners before any tasks are posted. Splitting startup further into [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) → `FlutterEngineLaunchShell` → `FlutterEngineLaunchEngine` introduces intermediate partially-initialized [`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) states, duplicates [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`assets_path`, [`custom_dart_entrypoint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2687), [`dart_entrypoint_argv`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2765), `custom_asset_resolvers`) into `FlutterEngineRunConfiguration`, and requires seeding `initial_lifecycle_state` to keep the shell from scheduling frames before an isolate exists. -2. **Pre-run buffering is self-contained in `FlutterEmbedderNative`**: Queueing platform messages, initial route, viewport metrics, and external texture registrations in `FlutterEmbedderNative` between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)—and flushing them immediately after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007)—requires no new [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) entry points or [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) lifecycle flags. -3. **Non-blocking `NotifyCreated()` already keeps [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) off the UI thread**: [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) executes during `Activity.onCreate` / `onStart`, well before WindowManager dispatches `SurfaceHolder.Callback.surfaceCreated`, and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) queues surface creation on the raster thread without waiting. The raster thread therefore has the whole interval until the first frame to resolve the renderer and create the context. - -With `kDeferred`, the UI thread doesn't wait for backend selection in either design. The remaining startup difference is when context creation starts: at `runBundleAndSnapshotFromLibrary()` here, and at `attachToNative()` in the alternative. The startup benchmarks in [DeviceLab Benchmarks](#devicelab-benchmarks) measure whether that changes time to first frame on low-end devices. - -| Dimension | Proposed: Two-Phase Init ([`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) + [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007) + `kDeferred`) | Alternative: Split Shell & Isolate Launch (`FlutterEngineLaunchShell` + `FlutterEngineLaunchEngine`) | -| :--- | :--- | :--- | -| **Public C-API Surface ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h))** | **Pro:** Reuses existing [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970-L2975) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007); adds the `kDeferred` renderer type and no lifecycle functions. | **Con:** Adds two new functions (`FlutterEngineLaunchShell`, `FlutterEngineLaunchEngine`), a new `FlutterEngineRunConfiguration` struct duplicating [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields, and two [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) fields (`defer_root_isolate_launch`, `initial_lifecycle_state`). | -| **[`EmbedderEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_engine.h#L23-L102) State Machine** | **Pro:** Preserves the existing two-state model (initialized → running) used by all desktop embedders and `FlutterEngineSpawn`. | **Con:** Introduces a three-stage startup state machine (initialized → shell launched without isolate → isolate launched) that must also be mirrored in `FlutterEngineSpawnInfo`. | -| **Raster-Thread [`SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) Overlap ([`ContextVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h#L64) / [`ContextGLES`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h#L23))** | **Pro:** Runs asynchronously on the raster thread across the entire [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) → `surfaceCreated()` window without blocking the UI thread. | **Pro:** Starts earlier (at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) rather than [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089)). Whether that shortens time to first frame on low-end devices has not been measured. | -| **Pre-Run State (`flutter/localization`, `setInitialRoute`, `flutter/settings`, Textures)** | **Con:** Because `FlutterProjectArgs` requires the Dart entrypoint and asset arguments passed in [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) (and `EmbedderEngine` rejects calls until `FlutterEngineRunInitialized` creates the `Shell`), `FlutterEmbedderNative` must queue messages, viewport metrics, and external textures sent between [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442) and [`runBundleAndSnapshotFromLibrary()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1072-L1089) and flush them right after [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006-L3007).
**Pro:** Keeps pre-run queueing internal to the Android embedder without exposing [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) state in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). | **Pro:** [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) and [`RuntimeController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/runtime_controller.h#L52) exist at [`attachToNative()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L433-L442), so pre-run platform messages and viewport metrics can be dispatched directly into the engine before the isolate launches.
**Con:** Requires `initial_lifecycle_state = "AppLifecycleState.detached"` in [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) to prevent premature frame scheduling while the shell has no isolate. | -
- -### Replacing Internal [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) with `FlutterCompositor` (Preserving Hybrid Composition) - -To eliminate the internal C++ [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) subclasses while preserving all four Android platform view composition modes, this RFC routes platform views through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) and the external texture C API with two targeted `FlutterCompositor` additions: - -- `FlutterBackingStoreConfig.is_root_layer` identifies whether a requested backing store is the bottom-most Flutter layer beneath all platform views (so the embedder can return the primary on-screen surface) or an overlay layer above a platform view (so the embedder allocates a translucent overlay buffer). -- `FlutterCompositor.merge_raster_thread_on_platform_views` is an opt-in flag that dynamically merges the raster task runner onto the platform thread during Hybrid Composition (HC) frames so Java [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L54) requires no modifications. - -Android's four platform view composition modes and their mechanisms map to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) as follows: - -| Mode | Android Mechanism | Flutter UI Above View | `embedder.h` Boundary | -| :--- | :--- | :--- | :--- | -| **Hybrid Composition (HC)** | Places the native [`View`](https://developer.android.com/reference/android/view/View) directly in the view hierarchy (`createForPlatformViewLayer`) and converts the root surface to [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42) ([`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)). | Rendered into [`PlatformViewWrapper`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L40) overlay surfaces (`createOverlaySurface` / `onDisplayOverlaySurface`) on the merged platform/raster thread. | `FlutterCompositor` (`kFlutterLayerContentTypePlatformView`, `is_root_layer`, `merge_raster_thread_on_platform_views = true`) | -| **Hybrid Composition++ (HCPP)** | Places the native `View` in the view hierarchy (`createPlatformViewHcpp`) and composites layers in [`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager) via NDK [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) and GPU sync fences (API 34+, Vulkan). | Rendered into [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) swapchain slices attached to overlay `SurfaceControl` nodes on unmerged threads. | `FlutterCompositor` (`kFlutterLayerContentTypePlatformView`, `is_root_layer`, `merge_raster_thread_on_platform_views = false`) | -| **Texture Layer Hybrid Composition (TLHC)** | Attaches the native `View` to `PlatformViewWrapper` while redirecting its draw output into an offscreen [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) or [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`configureForTextureLayerComposition`). | Rendered directly in the single Flutter scene around the `Texture` widget. | External texture C API (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`) | -| **Virtual Display (VD)** | Hosts the native `View` inside an offscreen [`VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay) (`VirtualDisplayController`) backed by a `SurfaceProducer` or `SurfaceTexture` (`configureForVirtualDisplay`). | Rendered directly in the single Flutter scene around the `Texture` widget. | External texture C API (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`) | - -**All four platform view composition modes are preserved; retiring Hybrid Composition (HC) or Virtual Display (VD) is not an option.** - -Standard Hybrid Composition and Virtual Display remain required across Android devices, API levels, and embedded view hierarchies (such as views containing `SurfaceView` or `TextureView` children where [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L54) falls back from TLHC to Virtual Display). What this migration eliminates is **only the internal C++ [`flutter::ExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L432) subclass ([`shell/platform/android/external_view_embedder/AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29)) and its direct dependencies on [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h)) and [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31)**, rather than any platform view composition mode. - -In the legacy architecture, [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h) subclassed `flutter::ExternalViewEmbedder` directly and used [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) in [`PostPrerollAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213) to dynamically lease and merge the raster thread onto the platform thread during HC frames: - -```cpp -// Existing internal ExternalViewEmbedder subclass in shell/platform/android/external_view_embedder/external_view_embedder.cc -PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( - const fml::RefPtr& raster_thread_merger) { - if (!FrameHasPlatformLayers()) { - return PostPrerollResult::kSuccess; - } - if (!raster_thread_merger->IsMerged()) { - CancelFrame(); - raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration); - return PostPrerollResult::kSkipAndRetryFrame; - } - raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration); - if (previous_frame_view_count_ == 0) { - return PostPrerollResult::kResubmitFrame; - } - return PostPrerollResult::kSuccess; -} -``` +#### Opt-In Dynamic Raster Thread Merging for Hybrid Composition -#### How All Four Platform View Modes Work in the C-API Architecture - -At the C-API boundary ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)), the four composition modes partition into two external-texture modes and two compositor-layer modes: - -- **Texture Layer Hybrid Composition (TLHC) and Virtual Display (VD) via External Textures**: Both [`configureForTextureLayerComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L643-L685) and [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596) allocate a `PlatformViewRenderTarget` from `TextureRegistry` (`SurfaceProducer` / `AHardwareBuffer` or `SurfaceTexture`) in Java and return its texture ID to the Dart framework `Texture` widget. At the native engine boundary, both TLHC and VD operate strictly through the C-API external texture callbacks (`FlutterEngineRegisterExternalTexture`, `FlutterEngineMarkExternalTextureFrameAvailable`, `FlutterVulkanTextureFrameCallback`, and `FlutterOpenGLTextureFrameCallback`) without creating `FlutterPlatformView` layers. -- **Hybrid Composition (HC) and Hybrid Composition++ (HCPP) via [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322)**: The engine's internal [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h) implements `flutter::ExternalViewEmbedder` behind the public [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) interface (`create_backing_store_callback`, `collect_backing_store_callback`, and `present_layers_callback` / `present_view_callback`). `AndroidCompositor` and `AndroidPlatformViewsController` drive both HC and HCPP over `FlutterCompositor` without including internal `//flutter/flow` or `fml::RasterThreadMerger` headers: - -1. **Backing Store Allocation ([`create_backing_store_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2284))**: `create_backing_store_callback` is invoked during layer rasterization *before* `present_view_callback` receives the ordered `FlutterLayer` list, and [`FlutterBackingStoreConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2160-L2168) currently passes only `size` and `view_id`: - - An overlay layer rendered above a full-screen platform view has the exact same `size` and `view_id` as the primary window. - - When a platform view sits at the bottom of the scene with no Flutter background behind it, the first backing store requested in the frame is an overlay (`is_root_layer == false`), not the root surface. - - Adding `bool is_root_layer` to `FlutterBackingStoreConfig` (with [`FlutterCompositor.avoid_backing_store_cache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2312) set to `true` because the engine's render-target cache keys only on size) distinguishes the bottom-most background slice from overlay slices at allocation time so the embedder can back the root slice with the primary `ANativeWindow` surface (`GetFBO()` on OpenGL ES, or the primary swapchain image on Vulkan) and allocate a translucent overlay buffer ([`PlatformViewWrapper`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L40) or child [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl)) for layers above a platform view: - - ```c - typedef struct { - /// The size of this struct. Must be sizeof(FlutterBackingStoreConfig). - size_t struct_size; - /// The size of the render target the engine expects to render into. - FlutterSize size; - /// The identifier for the view that the engine will use this backing store to - /// render into. - FlutterViewId view_id; - /// True if this backing store is for the bottom-most layer of the frame for - /// `view_id`, with no platform view composited beneath it. The engine - /// requests at most one such backing store per view per frame, and none when - /// all Flutter contents are composited above platform views. False for all - /// other layers. Read this field only if `struct_size` covers it, and treat - /// it as false otherwise. - /// - /// An embedder may return the view's on-screen render target (for example, - /// the next swapchain image or default framebuffer) when this is true. The - /// engine's backing store cache ignores this field, so such an embedder must - /// set `FlutterCompositor.avoid_backing_store_cache` to true. - bool is_root_layer; - } FlutterBackingStoreConfig; - ``` - -2. **Layer and Overlay Presentation ([`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2302) / [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321))**: `AndroidCompositor::PresentLayers` begins the platform view frame (`OnBeginFrame`), iterates through the ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) array, and dispatches each layer by type: - - The base [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173) is presented to the primary `ANativeWindow`. - - Each [`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175) layer forwards the view identifier, physical bounds (`offset`, `size`), and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) stack to `AndroidPlatformViewsController::OnDisplayPlatformView` ([`PlatformViewsController.onDisplayPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289) in Java). - - Any [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173) layer positioned above a platform view acquires an Android overlay surface ([`createOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1402-L1425) / `GetOverlayWindow`), blits and swaps the offscreen FBO into that overlay window (`BlitAndSwapOverlaySurface`), and updates the overlay geometry ([`onDisplayOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1291-L1309) / `showOverlaySurface`), followed by `OnEndFrame`. -3. **Removing [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder)**: Because `AndroidCompositor` and `AndroidPlatformViewsController` handle HC overlay blitting and HCPP `SurfaceControl` transactions through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) (while TLHC and VD operate through the C-API external texture pipeline), [`shell/platform/android/external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) ([`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34)) is deleted in Phase 4 without removing or degrading any of the four platform view modes. No [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31) handles or internal flow types are exposed in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). - -#### Opt-In Dynamic Raster Thread Merging for Hybrid Composition (`merge_raster_thread_on_platform_views`) - -Hybrid Composition (HC) is used whenever HCPP is not active. HCPP (`enable_surface_control`) is opt-in ([`settings.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/settings.h#L237-L238)) and also requires Android 14+ (API level 34+) and Impeller Vulkan with external fence and semaphore file-descriptor extensions ([`platform_view_android.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L171-L174), [`external_view_embedder_wrapper.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_wrapper.cc#L33-L42)). When HCPP is not active, two runtime paths route a platform view to HC instead of Texture Layer Hybrid Composition (TLHC): - -1. **Explicit Hybrid Composition ([`initExpensiveAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L194-L210))**: The plugin calls [`createForPlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L168-L177), routing directly to [`PlatformViewsController.configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L581-L585). -2. **Embedded Child `SurfaceView` ([`VIEW_TYPES_REQUIRE_NON_TLHC`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L68))**: When a plugin uses [`initAndroidView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/packages/flutter/lib/src/services/platform_views.dart#L134-L152) ([`createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L194-L247)) and the embedded Android `View` hierarchy contains a `SurfaceView` child whose out-of-band buffer queue bypasses `ViewParent.onDescendantInvalidated()`, [`PlatformViewsController.createForTextureLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L223-L246) branches by `request.displayMode`: - - **[`TEXTURE_WITH_VIRTUAL_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.java#L24) (Virtual Display / VD)**: Calls [`configureForVirtualDisplay`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L596-L638), hosting the view inside an offscreen `VirtualDisplay` backed by a [`TextureRegistry`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L19) render target ([`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227-L3229)). - - **[`TEXTURE_WITH_HYBRID_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.java#L28) (Hybrid Composition / HC)**: Calls [`configureForHybridComposition`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L581-L585) and returns [`NON_TEXTURE_FALLBACK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java#L316) (`-2`), instructing the Dart framework to composite the view via a [`PlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/platform_view_layer.h#L13) ([`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175)). - -Rather than changing HC to run with the raster and platform threads unmerged (which would require cross-thread latches during `present_view_callback` ([`onEndFrame`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391)), overlay allocation ([`createOverlaySurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1402-L1425)), and the first-frame [`convertToImageView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1390) surface switch), [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) is extended with an opt-in `merge_raster_thread_on_platform_views` flag: +The Android embedder sets `merge_raster_thread_on_platform_views = true` when HCPP (`enable_surface_control`) is not active, and `false` when HCPP is active: ```c typedef struct { @@ -773,51 +343,43 @@ typedef struct { } FlutterCompositor; ``` -The Android embedder sets `merge_raster_thread_on_platform_views` to `true` when HCPP is not active, and `false` when HCPP (`enable_surface_control`) is active. Inside [`shell/platform/embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder), [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) implements this mode in three steps: - -1. **Dynamic Lease & Thread Merge (`SupportsDynamicThreadMerging` & `PostPrerollAction`)**: - Today, [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) overrides neither `SupportsDynamicThreadMerging()` nor `PostPrerollAction()`, so [`Rasterizer::Setup()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L94-L103) never creates an [`fml::RasterThreadMerger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h#L31). When `merge_raster_thread_on_platform_views` is `true`, `EmbedderExternalViewEmbedder::SupportsDynamicThreadMerging()` returns `true` and `PostPrerollAction()` mirrors [`AndroidExternalViewEmbedder::PostPrerollAction()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L188-L213): - - If the prerolled frame has no platform views (`composition_order_.size() == 1`), `PostPrerollAction()` returns `PostPrerollResult::kSuccess`, and [`Rasterizer::DoDraw()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L618-L626) decrements the lease at the end of each frame until the queues unmerge. - - If the frame contains platform views and `!raster_thread_merger->IsMerged()`, it calls `CancelFrame()`, merges the queues with a 10-frame lease (`MergeWithLease(kDefaultMergedLeaseDuration)`), and returns `PostPrerollResult::kSkipAndRetryFrame` so the frame is retried on the platform thread. - - Once merged, it extends the lease (`ExtendLeaseTo(kDefaultMergedLeaseDuration)`). -2. **First-HC-Frame Surface Switch (`PostPrerollResult::kResubmitFrame` and `convertToImageView`)**: - On the first frame that composites a platform view (`previous_frame_view_count_ == 0`), `EmbedderExternalViewEmbedder::PostPrerollAction()` returns `PostPrerollResult::kResubmitFrame` (matching [`external_view_embedder.cc#L209-L211`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc#L209-L211)). In [`CompositorContext::ScopedFrame::Raster()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/compositor_context.cc#L141-L143) and [`Rasterizer::DrawToSurfaceUnsafe()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L844-L864): - - Returning `kResubmitFrame` skips `PaintLayerTree`, so no Flutter drawing operations are recorded into the layer slices ([`EmbedderExternalView::HasEngineRenderedContents()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view.cc#L68-L78) is `false`). - - [`EmbedderExternalViewEmbedder::SubmitFlutterView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L571-L641) therefore skips `create_backing_store_callback` and `Render()` and invokes `present_view_callback` on the merged platform thread with the `kFlutterLayerContentTypePlatformView` layers. - - Inside `present_view_callback`, [`PlatformViewsController.onDisplayPlatformView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1252-L1289) calls [`initializeRootImageViewIfNeeded()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1174-L1179) → [`flutterView.convertToImageView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L1375-L1390), switching the primary `ANativeWindow` from [`FlutterSurfaceView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java#L35) to the [`ImageReader`](https://developer.android.com/reference/android/media/ImageReader)-backed [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L42), while [`onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1321-L1391) keeps the platform view `View.GONE` because no `ImageReader` image has been queued yet. - - `DrawToSurfaceUnsafe()` returns `DrawSurfaceStatus::kRetry` for `RasterStatus::kResubmit`, and `Rasterizer` immediately resubmits the frame on the merged platform thread (`previous_frame_view_count_ > 0`). On the resubmitted pass, `PostPrerollAction()` returns `PostPrerollResult::kSuccess`, `create_backing_store_callback` acquires the root backing store from the newly attached `FlutterImageView` (and any overlay backing stores), and `present_view_callback` presents all layers and runs `onEndFrame()`, which acquires the queued `ImageReader` buffers and sets the [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) to `View.VISIBLE` in the same `ViewRootImpl.performTraversals()` pass. -3. **Unmerge and Revert to `FlutterSurfaceView`**: - When the last HC view is removed (`currentFrameUsedPlatformViewIds.isEmpty()`), [`PlatformViewsController.onEndFrame()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L1326-L1334) calls `flutterView.revertImageView()` to return to direct `FlutterSurfaceView` presentation, and the raster thread unmerges once the 10-frame lease expires. Zero changes to the Java `PlatformViewsController` HC implementation are required. - -### Hybrid Composition++ (HCPP), SurfaceControl Compositing, and Per-View Frame State - -Starting with Android 14 (API level 34), Flutter supports Hybrid Composition++ (HCPP) under the Impeller Vulkan backend ([`shell/platform/android/external_view_embedder/external_view_embedder_2.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h)). To support HCPP over [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322): - -- No new fields are added to [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198): because `AndroidSurfaceControl` owns the [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html) and [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html) passed in [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L995-L1035) and Impeller flushes its render pass command buffers to that `VkQueue` ([`ContextVK::FlushCommandBuffers()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc#L703-L715)) before invoking `present_view_callback`, `AndroidSurfaceControl` signals its own exportable `VkSemaphore` ([`VK_KHR_external_semaphore_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_semaphore_fd.html) / `vkGetSemaphoreFdKHR`) on `VkQueue` inside `present_view_callback`. -- `AndroidSurfaceControl` and `AndroidPlatformViewsController` replace the internal [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) subclass, allocating [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains in `create_backing_store_callback` and assembling [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits (`setBuffer`, `setLayer`, `setCrop`, `setMatrix`) in `present_view_callback` on unmerged threads. -- Per-frame composition state is captured by value per [`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) inside `present_view_callback` before dispatching to the platform thread to prevent cross-view and cross-frame data races. - -Unlike legacy Hybrid Composition (see [Opt-In Dynamic Raster Thread Merging for Hybrid Composition](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views)), which merges the raster thread into the platform thread to avoid synchronization deadlocks, HCPP delegates layer compositing directly to Android's OS window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) using [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transactions: - -- Rasterization and Android view updates execute on separate threads without leasing or merging overhead. -- The embedder arranges the base Flutter surface, native platform views, and overlay slices into a tree of [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) nodes. -- Visual synchronization occurs at the GPU level via hardware sync fences ([`ASyncFence`](https://developer.android.com/ndk/reference/group/sync) exported from Vulkan binary semaphores) passed through [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits, synchronizing buffer availability without CPU blocking. -- **Per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) Value-Captured Frame State**: In a multiview or multi-window environment, the raster thread renders View A (`FlutterPresentViewInfo.view_id = 0`) and immediately begins rasterizing View B (`view_id = 1`) while the platform thread is still applying View A's platform view layout mutations. Storing per-frame composition state (such as `composition_order_`, `picture_bounds_`, or `views_to_recomposite_`) as mutable fields on a shared controller causes cross-thread data races between consecutive views. `AndroidPlatformViewsController` eliminates this hazard by keying active platform view hierarchies by [`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) and capturing each view's ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) slice and [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) parameters by value inside the [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321) closure before dispatching to the platform thread. - -To support HCPP through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the public C-API provides multi-layer compositing primitives through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322): - -1. **Compositor Backing Stores**: The embedder registers a [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) configuration where [`create_backing_store_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2284) allocates [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed Vulkan swapchains managed by an embedder-owned surface pool (`AndroidSurfaceControl`). -2. **Layer Slicing and Dispatch**: During frame presentation, [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321) (or [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2302)) delivers an ordered list of [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) items scoped to [`FlutterPresentViewInfo.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2237): - - [`kFlutterLayerContentTypeBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2173): Rendered Flutter graphics slices (base surface or overlay surfaces). - - [`kFlutterLayerContentTypePlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2175): Embedded native Android [`View`](https://developer.android.com/reference/android/view/View) nodes, accompanied by [`FlutterPlatformViewIdentifier`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1525) and serialized [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays (see Platform View Mutator Stack Serialization). -3. **Embedder-Owned Acquire Semaphore Export and Atomic Commit**: Because `AndroidSurfaceControl` owns the [`VkDevice`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDevice.html), [`VkQueue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueue.html), and [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer)-backed `VkImage` passed to the engine, it exports the acquire `sync_fence` file descriptor directly on its `VkQueue` inside `present_view_callback` without adding a fence field to [`FlutterBackingStorePresentInfo`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2190-L2198). - -#### Acquire Fence Lifecycle and Ownership +```mermaid +sequenceDiagram + autonumber + participant Raster as "Raster Thread (Rasterizer)" + participant Plat as "Platform Thread (Merged Queue)" + participant Emb as "EmbedderExternalViewEmbedder" + participant PVC as "PlatformViewsController & FlutterView (Java)" + + Note over Raster,PVC: First Frame with HC Platform View (previous_frame_view_count_ == 0) + Raster->>Emb: Preroll() + PostPrerollAction() (!IsMerged) + Emb->>Emb: CancelFrame() + MergeWithLease(10 frames) + Emb-->>Raster: kSkipAndRetryFrame + Raster-)Plat: Retry frame on merged platform thread + + Plat->>Emb: Preroll() + PostPrerollAction() (IsMerged, previous_frame_view_count_ == 0) + Emb->>Emb: ExtendLeaseTo(10) + Emb-->>Plat: kResubmitFrame (skips PaintLayerTree & create_backing_store_callback) + Plat->>Emb: SubmitFlutterView() -> present_view_callback(platform view layers only) + Emb->>PVC: onDisplayPlatformView() -> convertToImageView() (attaches ImageReader) + PVC->>PVC: onEndFrame() keeps FlutterMutatorView View.GONE (no image queued yet) + Plat->>Plat: DrawToSurfaceUnsafe() returns kRetry -> immediate resubmit pass + + Note over Plat,PVC: Resubmitted Pass on Merged Platform Thread (previous_frame_view_count_ > 0) + Plat->>Emb: Preroll() + PostPrerollAction() -> kSuccess + Plat->>Emb: PaintLayerTree() + SubmitFlutterView() + Emb->>PVC: create_backing_store_callback (FlutterImageView root + overlay surfaces) + Emb->>PVC: present_view_callback(all layers) + onEndFrame() + PVC->>PVC: Acquire ImageReader buffer & set FlutterMutatorView View.VISIBLE + + Note over Raster,PVC: Subsequent Frames After Last HC View Removed + Plat->>PVC: onEndFrame() -> revertImageView() (returns to FlutterSurfaceView) + loop 10 frames without platform views + Plat->>Plat: Rasterizer::DoDraw() decrements lease until unmerged + end +``` -1. **Engine Render Pass Submission**: During [`EmbedderExternalViewEmbedder::SubmitFlutterView()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L381-L387), `builder.Render()` runs [`impeller::RenderToTarget()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/display_list/aiks_dl_atlas_benchmarks.cc) → [`Canvas::EndReplay()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/display_list/canvas.cc#L2762) → [`ContextVK::FlushCommandBuffers()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc#L703-L715), which calls `vkQueueSubmit` on the `VkQueue` provided in [`FlutterVulkanRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L995-L1035) before invoking [`present_view_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2321). -2. **Embedder Barrier and Semaphore Export**: Because Vulkan executes submissions on the same `VkQueue` in submission order, `AndroidSurfaceControl` submits its image-layout transition barrier on that `VkQueue` inside `present_view_callback`, signals an embedder-owned exportable `VkSemaphore` ([`VK_KHR_external_semaphore_fd`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_semaphore_fd.html) with `VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT`, matching [`external_semaphore_vk.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/android/external_semaphore_vk.cc#L13-L59)), and calls `vkGetSemaphoreFdKHR` to obtain a POSIX `sync_fence` file descriptor. -3. **Buffer Attachment**: `AndroidPlatformViewsController` and `AndroidSurfaceControl` translate the ordered [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) sequence into atomic [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) operations (`setLayer`, `setCrop`, `setMatrix`) and attach each backing store `AHardwareBuffer` and acquire fence file descriptor via [`setBuffer(SurfaceControl, HardwareBuffer, SyncFence, Consumer)`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction#setBuffer(android.view.SurfaceControl,%20android.hardware.HardwareBuffer,%20android.hardware.SyncFence,%20java.util.function.Consumer)) (or NDK [`ASurfaceTransaction_setBuffer`](https://developer.android.com/ndk/reference/group/surface-control#asurfacetransaction_setbuffer)). -4. **SurfaceFlinger Wait & Buffer Release**: Android's window compositor ([`SurfaceFlinger`](https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager)) waits on the acquire fence on the GPU before compositing. Once compositing completes, SurfaceFlinger signals the transaction's release fence callback, allowing `AndroidSurfaceControl` to recycle the backing store back to its surface pool. +When HCPP (`enable_surface_control` on API 34+ with Impeller Vulkan) is active, `merge_raster_thread_on_platform_views` is `false` and `AndroidSurfaceControl` exports an acquire fence on its `VkQueue` inside `present_view_callback`: ```mermaid sequenceDiagram @@ -842,13 +404,11 @@ sequenceDiagram ### Platform View Mutator Stack Serialization -Platform views embedded within Flutter require spatial transformations, opacity adjustments, and geometric clips across Android composition modes (HCPP, legacy HC, and TLHC). To pass these mutators across [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) without exposing internal `//flutter/flow` or `SkPath` headers: - -- `FlutterPath` (`FlutterPathFillType`, `FlutterPathVerb`), `kFlutterPlatformViewMutationTypeClipPath`, and `FlutterCompositor.platform_view_mutation_capabilities` (`FlutterPlatformViewMutationCapability`) are added to `embedder.h` so arbitrary path clips serialize into an ABI-stable structure-of-arrays layout. -- Rounded-superellipse clips (`MutatorType::kClipRSE`) continue to be approximated as `kFlutterPlatformViewMutationTypeClipRoundedRect` by the engine, and conic path verbs (`kFlutterPathVerbConic`) are converted to quadratic Beziers by the engine unless the compositor opts in via `kFlutterPlatformViewMutationCapabilityPathConics` (which Android sets on API 34+). -- `AndroidMutatorsMapper` replaces direct `flutter::MutatorsStack` iteration in `PlatformViewAndroidJNIImpl`, translating the `FlutterPlatformView.mutations` array into Java [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) and `SurfaceControl.Transaction` transforms. - -Internally, these operations are managed by [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211) in [`flow/embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h). Because `flow` and `SkPath` headers cannot cross the C-API boundary, mutators must be serialized into an ABI-stable array of C structs: +> [!IMPORTANT] +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterPath` (`FlutterPathFillType`, `FlutterPathVerb`), `kFlutterPlatformViewMutationTypeClipPath`, and `FlutterCompositor.platform_view_mutation_capabilities` (`FlutterPlatformViewMutationCapability`). +> - **Behavioral changes**: +> - Arbitrary path clips serialize into an ABI-stable structure-of-arrays `FlutterPath` layout instead of exposing `SkPath` or `//flutter/flow` headers. +> - On Android API 34+ (`android.graphics.Path.conicTo`), the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` to receive exact conic path verbs (`kFlutterPathVerbConic`), and `AndroidMutatorsMapper` translates `FlutterPlatformView.mutations` into Java [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) and [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) transforms. ```c typedef enum { @@ -869,13 +429,7 @@ typedef struct { const FlutterPath* clip_path; }; } FlutterPlatformViewMutation; -``` - -Rounded-superellipse clips ([`MutatorType::kClipRSE`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L38)) continue to be approximated as [`kFlutterPlatformViewMutationTypeClipRoundedRect`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2077) by the engine ([`embedder_layers.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_layers.cc#L162-L167)), matching what Android HC and HCPP do today (`DlRoundSuperellipse::ToApproximateRoundRect()`). - -Arbitrary clips need a path representation that does not drag [`SkPath`](https://github.com/google/skia/blob/main/include/core/SkPath.h) across the boundary. In [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), `FlutterPath` uses a structure-of-arrays layout with an explicit fill rule and conservative control-point bounds: -```c typedef enum { kFlutterPathFillTypeNonZero, kFlutterPathFillTypeEvenOdd, @@ -932,13 +486,7 @@ typedef enum { /// paths to the embedder. kFlutterPlatformViewMutationCapabilityPathConics = 1 << 0, } FlutterPlatformViewMutationCapability; -``` - -[`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) is extended with `uint64_t platform_view_mutation_capabilities;` (a bitmask of `FlutterPlatformViewMutationCapability` values, defaulting to `0` when omitted by older embedders). On Android API 34+, where `android.graphics.Path.conicTo` is available and HCPP applies path clips to [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32), the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` so conic arcs are passed through without quadratic approximation. -Mutations ride on [`FlutterPlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2094-L2117), which the engine hands to the embedder inside a [`FlutterLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2200-L2231) through the compositor's [`present_layers_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2302): - -```c typedef struct { size_t struct_size; FlutterPlatformViewIdentifier identifier; @@ -950,26 +498,38 @@ typedef struct { } FlutterPlatformView; ``` -The array is an array of pointers rather than an array of values. Indexing strides by pointer width instead of `sizeof(FlutterPlatformViewMutation)`, so a future mutation type that widens the union does not shift the stride out from under an already-compiled embedder. This is the same technique [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) uses. +### External Textures -Android does not apply backdrop filter mutators over HC or HCPP platform views today: neither [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) nor [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) overrides the no-op [`ExternalViewEmbedder::PushFilterToVisitedPlatformViews`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L538-L558), and both JNI serializers skip `kBackdropFilter` and the backdrop clip mutators ([HC](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1840-L1849), [HCPP](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L2355-L2362)). TLHC and VD render the platform view as an external texture into Flutter's own canvas ([`TextureLayer::Paint`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/layers/texture_layer.cc#L48-L67)), so backdrop filters already work in those modes without platform view mutators. The Android embedder skips `FlutterPlatformViewMutationType` values it does not handle, and any future backdrop-filter support over HC or HCPP views will consume the backdrop-blur mutation added by the iOS embedder migration RFC rather than defining an Android-specific mutation type. +> [!IMPORTANT] +> To replace the internal `flutter::Texture` subclasses ([`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36) and [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33)) across both [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) and [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`GL_TEXTURE_EXTERNAL_OES`) pipelines: +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterVulkanYcbcrConversionInfo` (`FlutterVulkanComponentMapping`, `FlutterVulkanComponentSwizzle`) and `FlutterVulkanExternalTexture.ycbcr_conversion_info` (extending [`FlutterVulkanExternalTexture` from #188855](https://github.com/flutter/flutter/pull/188855)); adds `FlutterOpenGLExternalTexture` (with `FlutterTransformation uv_transform`) and `FlutterOpenGLRendererConfig.external_texture_frame_callback` (`FlutterOpenGLTextureFrameCallback`, deprecating `gl_external_texture_frame_callback`). +> - **Behavioral changes**: The Vulkan renderer samples multi-planar and vendor-specific external YCbCr camera and video frames (`VkSamplerYcbcrConversion`) without exposing Android `AHardwareBuffer` types in `embedder.h`, and the OpenGL ES renderer applies `SurfaceTexture.getTransformMatrix()` UV transforms while keeping [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) unchanged for backing store render targets. -### External Textures (Zero-Copy Vulkan and OpenGL ES) - -To replace the internal `flutter::Texture` subclasses ([`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36) and [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33)) across both [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) and [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`GL_TEXTURE_EXTERNAL_OES`) pipelines: - -- `FlutterVulkanYcbcrConversionInfo` and `FlutterVulkanExternalTexture.ycbcr_conversion_info` are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) so the Vulkan renderer can sample multi-planar and vendor-specific external YCbCr camera and video frames (`VkSamplerYcbcrConversion`) without exposing Android `AHardwareBuffer` types in `embedder.h`. -- `FlutterOpenGLExternalTexture` (with `FlutterTransformation uv_transform`) and `FlutterOpenGLRendererConfig.external_texture_frame_callback` are added to `embedder.h` (deprecating `gl_external_texture_frame_callback`) so the OpenGL ES renderer applies `SurfaceTexture.getTransformMatrix()` UV transforms while keeping `FlutterOpenGLTexture` unchanged for render targets. -- `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` import `AHardwareBuffer` frames into `VkImage` (Vulkan) or `EGLImageKHR` (OpenGL ES) inside the Android embedder and register textures through `FlutterEngineRegisterExternalTexture` and `FlutterEngineMarkExternalTextureFrameAvailable`. - -Android provides two distinct mechanisms for external textures: +```mermaid +flowchart LR + subgraph Producers["Android TextureRegistry Producers"] + SP["SurfaceProducer (ImageReader AHardwareBuffer, API 29+)"] + ST["SurfaceTexture (GL_TEXTURE_EXTERNAL_OES + getTransformMatrix)"] + end -- [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21): A zero-copy pipeline backed by `ImageReader` and [`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) on API 29+ (falling back to `SurfaceTexture` below API 29 and on Huawei devices at API 29). -- [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture): An OpenGL ES consumer path where native frames latch into a `GL_TEXTURE_EXTERNAL_OES` texture via [`SurfaceTexture.updateTexImage()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()). + subgraph Embedder["Android Embedder (Owns VkDevice & EGLContext)"] + VK_AHB["Import AHardwareBuffer -> VkImage + query VkAndroidHardwareBufferFormatPropertiesANDROID"] + VK_BLIT["SurfaceTextureExternalTextureVKImpeller: GL-to-AHardwareBuffer blit (bakes UV matrix)"] + GL_AHB["Import AHardwareBuffer -> EGLImageKHR (identity uv_transform)"] + GL_ST["updateTexImage() + read getTransformMatrix()"] + end -Because the Android embedder owns the `VkDevice` on Vulkan and the `EGLContext` on OpenGL ES, it imports `AHardwareBuffer` frames into backend-native GPU handles itself (`VkImage` via `VK_ANDROID_external_memory_android_hardware_buffer` on Vulkan, and `EGLImage` / `GL_TEXTURE_EXTERNAL_OES` via `eglCreateImageKHR(EGL_NATIVE_BUFFER_ANDROID)` on OpenGL ES, mirroring [`ImageExternalTextureGL`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture_gl.cc#L87-L115)). This keeps `embedder.h` free of Android-specific `AHardwareBuffer` types. + subgraph CAPI["embedder.h External Texture Callbacks"] + VK_OUT["FlutterVulkanExternalTexture (+ optional FlutterVulkanYcbcrConversionInfo)"] + GL_OUT["FlutterOpenGLExternalTexture (+ FlutterTransformation uv_transform)"] + end -On Vulkan, [`flutter/flutter#188855`](https://github.com/flutter/flutter/pull/188855) added [`FlutterVulkanExternalTexture`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/shell/platform/embedder/embedder.h#L971-L1001), `FlutterVulkanTextureFrameCallback`, and `FlutterVulkanRendererConfig.external_texture_frame_callback`. However, camera and video decoder `AHardwareBuffer`s on Android frequently use vendor-specific external YCbCr formats (`VkAndroidHardwareBufferFormatPropertiesANDROID.format == VK_FORMAT_UNDEFINED`) that cannot be sampled without a `VkSamplerYcbcrConversion`. To support multi-planar and external-format `VkImage`s without leaking `AHardwareBuffer` into `embedder.h`, `FlutterVulkanExternalTexture` is extended with an optional `FlutterVulkanYcbcrConversionInfo` pointer: + SP -- "Impeller Vulkan" --> VK_AHB --> VK_OUT + ST -- "Impeller Vulkan" --> VK_BLIT --> VK_OUT + SP -- "Impeller OpenGL ES" --> GL_AHB --> GL_OUT + ST -- "Impeller OpenGL ES" --> GL_ST --> GL_OUT +``` ```c /// VkComponentSwizzle values for FlutterVulkanComponentMapping. @@ -1030,13 +590,7 @@ typedef struct { /// external-format images. NULL for standard RGBA sampling. const FlutterVulkanYcbcrConversionInfo* ycbcr_conversion_info; } FlutterVulkanExternalTexture; -``` -For [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture)-backed textures—used by the public `TextureRegistry.createSurfaceTexture()` / `registerSurfaceTexture()` APIs, by `SurfaceProducer` below API 29 (and on Huawei devices at API 29), and by TLHC/VD render targets below API 29—[`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float[])) provides a 4x4 UV coordinate transformation matrix accounting for camera sensor rotation, video decoder padding, and vertical coordinate inversion (`ImageReader` buffers carry no matrix because `handlesCropAndRotation()` is `false`). Under Vulkan, `SurfaceTexture` requires an OpenGL ES context to latch frames, so the GL-to-Vulkan blit into an `AHardwareBuffer` ([`SurfaceTextureExternalTextureVKImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc#L24-L161)) runs inside the Android embedder with the UV matrix baked into the blit. Under OpenGL ES, however, the engine samples the texture directly and needs the UV matrix from the embedder. - -Because [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) has no `struct_size` and is embedded by value inside [`FlutterOpenGLBackingStore`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1982-L1996) as a render target, it stays unchanged and undeprecated. Instead, `embedder.h` introduces `FlutterOpenGLExternalTexture` (mirroring `FlutterMetalExternalTexture` and `FlutterVulkanExternalTexture`) and appends `FlutterOpenGLTextureFrameCallback external_texture_frame_callback;` to `FlutterOpenGLRendererConfig` (marking `gl_external_texture_frame_callback` `@deprecated` in documentation and rejecting configs that set both): - -```c typedef struct { /// The size of this struct. Must be sizeof(FlutterOpenGLExternalTexture). size_t struct_size; @@ -1074,17 +628,13 @@ typedef bool (*FlutterOpenGLTextureFrameCallback)( FlutterOpenGLTextureFrameCallback external_texture_frame_callback; ``` -Under OpenGL ES, platform view compositing also faces constraints: because [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) transaction sync fences depend on Vulkan hardware synchronization, OpenGL platform views must use Texture Layer Hybrid Composition (TLHC) or legacy Hybrid Composition rather than HCPP. - -### Accessibility and Semantics Parity ([`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783) In-Place Extension) - -To deliver the complete semantics tree to Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) without including internal [`lib/ui/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h) headers: +### Accessibility and Semantics Parity -- `FlutterSemanticsRole` and nine node fields (`max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count`) are appended in place to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783) in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). -- `hit_test_child_count` separates the length of `children_in_hit_test_order` from `child_count` (`children_in_traversal_order`), which can differ when the framework grafts nodes such as `OverlayPortal`. -- `AndroidSemanticsMapper` consumes `FlutterSemanticsUpdate2` via `FlutterProjectArgs.update_semantics_callback2` and packs the direct `ByteBuffer` and string pool arrays expected by `AccessibilityBridge`, while accessibility actions route back through `FlutterEngineSendSemanticsAction`. - -`FlutterSemanticsRole` mirrors [`flutter::SemanticsRole`](https://github.com/flutter/flutter/blob/c5b32231ecba26acab0179de3a054163bb71a324/engine/src/flutter/lib/ui/semantics/semantics_node.h#L74-L111), and the remaining fields required by Android's [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) are appended to the end of [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783): +> [!IMPORTANT] +> To deliver the complete semantics tree to Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) without including internal [`lib/ui/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h) headers: +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterSemanticsRole` and appends nine node fields (`max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count`) in place to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783). +> - **Behavioral changes**: `hit_test_child_count` separates the length of `children_in_hit_test_order` from `child_count` (`children_in_traversal_order`, which can differ when the framework grafts nodes such as `OverlayPortal`). `AndroidSemanticsMapper` consumes `FlutterSemanticsUpdate2` via `FlutterProjectArgs.update_semantics_callback2` to pack the direct `ByteBuffer` and string pool arrays expected by `AccessibilityBridge`, while accessibility actions route back through `FlutterEngineSendSemanticsAction`. ```c typedef enum { @@ -1143,28 +693,11 @@ size_t hit_test_child_count; ### Dart Deferred Library Loading -Android supports dynamic delivery through Google Play Feature Delivery / split APKs: when Dart code requests a deferred library via `deferred as`, the embedder downloads or extracts the split APK and loads the compiled snapshot into the running isolate. To support deferred loading through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): - -- `FlutterRequestDartDeferredLibraryCallback` (`FlutterProjectArgs.request_dart_deferred_library_callback`), `FlutterDartDeferredLibrary` (with `destruction_callback`), `FlutterEngineLoadDartDeferredLibrary`, and `FlutterEngineNotifyDartDeferredLibraryLoadError` (`FlutterDartDeferredLibraryLoadError`) are added to `embedder.h`. -- Instead of calling internal [`PlatformViewAndroid::LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L508-L514) methods directly, the Android embedder receives loading unit requests via `request_dart_deferred_library_callback`, resolves the split APK snapshot buffers, and supplies them to `FlutterEngineLoadDartDeferredLibrary` (or reports failures via `FlutterEngineNotifyDartDeferredLibraryLoadError`), releasing mapped resources when `destruction_callback` fires. - -Currently, this is handled through private calls across JNI in [`LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L701-L735) into [`PlatformViewAndroid::LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L508-L514): - -```cpp -// Existing JNI deferred loading invocation in shell/platform/android/platform_view_android_jni_impl.cc -static void LoadDartDeferredLibrary(JNIEnv* env, - jobject obj, - jlong shell_holder, - jint jLoadingUnitId, - jobjectArray jSearchPaths) { - // ... resolve symbols data_mapping and instructions_mapping via dlopen ... - ANDROID_SHELL_HOLDER->GetPlatformView()->LoadDartDeferredLibrary( - loading_unit_id, std::move(data_mapping), - std::move(instructions_mapping)); -} -``` - -[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) is extended with callbacks and entry points mirroring the Dart SDK deferred loading API: +> [!IMPORTANT] +> To support Google Play Feature Delivery / split APK deferred loading through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) without calling internal [`PlatformViewAndroid::LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L508-L514) methods: +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterRequestDartDeferredLibraryCallback` (`FlutterProjectArgs.request_dart_deferred_library_callback`), `FlutterDartDeferredLibrary` (with `destruction_callback`), `FlutterEngineLoadDartDeferredLibrary`, and `FlutterEngineNotifyDartDeferredLibraryLoadError` (`FlutterDartDeferredLibraryLoadError`). +> - **Behavioral changes**: The engine invokes `request_dart_deferred_library_callback` on the platform task runner when Dart requests a `loading_unit_id`, wraps the embedder's `snapshot_data` and `snapshot_instructions` buffers in a shared-ownership `fml::NonOwnedMapping`, and fires `destruction_callback` once the Dart VM releases both buffers. ```c /// Callback invoked by the engine when Dart requests loading of a deferred @@ -1228,51 +761,44 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineNotifyDartDeferredLibraryLoadError( FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterDartDeferredLibraryLoadError* error); -``` -In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841): - -```c - /// The callback invoked by the engine when Dart requests a deferred library - /// (loading unit). The callback is invoked from a task posted to the - /// platform thread. The embedder answers each request with - /// `FlutterEngineLoadDartDeferredLibrary` or - /// `FlutterEngineNotifyDartDeferredLibraryLoadError`. If null, the engine - /// fails every request with a non-transient load error. - FlutterRequestDartDeferredLibraryCallback - request_dart_deferred_library_callback; +// Appended to FlutterProjectArgs: +FlutterRequestDartDeferredLibraryCallback + request_dart_deferred_library_callback; ``` -#### Deferred Library Memory Lifetime Management - -Because split APK assets on Android are loaded via `AAssetManager` or memory-mapped (`mmap`) from disk, buffer lifetime must be explicitly managed: - -1. When Dart requests a library, [`FlutterRequestDartDeferredLibraryCallback`](#dart-deferred-library-loading) runs asynchronously (`void` return type), so the embedder can initiate download or extraction without blocking the engine thread. -2. When loading completes, `FlutterDartDeferredLibrary.destruction_callback` provides the unmap trigger. In the proposed [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc) implementation, the engine captures this callback in a [`DeferredLibraryLifetime`](#deferred-library-memory-lifetime-management) handle held by [`fml::NonOwnedMapping`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/mapping.h#L115). When the Dart VM finishes loading and drops its references to both data and instruction buffers, the destructor triggers `destruction_callback(user_data)`, which permits the Android embedder to safely `munmap()` or close the underlying [`AAsset`](https://developer.android.com/ndk/reference/group/asset). - -### Custom Asset and Kernel Resolution (APK / In-Memory Mapping) - -To resolve assets packaged inside APK or AAB archives without subclassing internal C++ [`flutter::AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23) or extracting files to disk: - -- `FlutterAssetMapping`, `FlutterCustomAssetResolver` (`FlutterAssetResolverGetAsMappingCallback`), and `FlutterProjectArgs.custom_asset_resolvers` (`custom_asset_resolvers_count`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). -- [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) supplies a `FlutterCustomAssetResolver` that maps assets directly from the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) in `get_as_mapping_callback` and atomically swaps its `AAssetManager*` pointer inside `user_data` when `FlutterJNI.updateJavaAssetManager` runs after a split APK install. - -Desktop embedders load assets from loose files in a filesystem directory passed to [`FlutterProjectArgs.assets_path`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2533). On Android, assets are packaged inside APK or AAB archives and accessed through the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26), which previously subclassed the internal engine [`AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23): - -```cpp -// Existing internal AssetResolver inheritance in shell/platform/android/apk_asset_provider.h -class APKAssetProvider final : public AssetResolver { - public: - explicit APKAssetProvider(JNIEnv* env, - jobject assetManager, - std::string directory); - std::unique_ptr Clone() const; -}; +```mermaid +sequenceDiagram + autonumber + participant VM as "Dart VM (Root Isolate)" + participant Engine as "embedder.cc & fml::NonOwnedMapping" + participant Emb as "Android Embedder (FlutterJNI)" + participant Play as "PlayStoreDeferredComponentManager" + + VM->>Engine: loadLibrary() requests loading_unit_id + Engine-)Emb: request_dart_deferred_library_callback(loading_unit_id, user_data) + Emb->>Play: installDeferredComponent(loading_unit_id) + alt Split APK installed & symbols resolved + Play->>Emb: loadAssets() -> updateJavaAssetManager() + loadDartDeferredLibrary() + Emb->>Emb: dlopen / mmap or AAsset map snapshot_data & snapshot_instructions + Emb->>Engine: FlutterEngineLoadDartDeferredLibrary(engine, &library) + Engine->>VM: LoadDartDeferredLibrary(loading_unit_id, data_mapping, instr_mapping) + Note over Engine,Emb: When Dart VM drops both mappings (or on isolate shutdown) + Engine->>Emb: destruction_callback(user_data) -> munmap / AAsset_close + else Download or extraction failed + Play->>Emb: deferredComponentInstallFailure() + Emb->>Engine: FlutterEngineNotifyDartDeferredLibraryLoadError(engine, &error) + Engine->>VM: LoadDartDeferredLibraryError(loading_unit_id, error_message, transient) + end ``` -Passing file paths would require extracting all assets to disk during startup, which causes latency and disk consumption. +### Custom Asset and Kernel Resolution -[`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives a custom asset resolver callback interface on [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) that maps assets into memory directly from the APK archive: +> [!IMPORTANT] +> To resolve assets packaged inside APK or AAB archives without subclassing internal C++ [`flutter::AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23) or extracting files to disk: +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterAssetMapping`, `FlutterCustomAssetResolver` (`FlutterAssetResolverGetAsMappingCallback`), and `FlutterProjectArgs.custom_asset_resolvers` (`custom_asset_resolvers_count`). +> - **Behavioral changes**: The engine's internal `CustomAssetResolver` wrapper returns `true` from `IsValidAfterAssetManagerChange()` so custom resolvers stay attached across hot reload and hot restart. [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) supplies a `FlutterCustomAssetResolver` that maps assets directly from the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) in `get_as_mapping_callback` and atomically swaps its `AAssetManager*` pointer inside `user_data` when `FlutterJNI.updateJavaAssetManager` runs after a split APK install. ```c typedef struct { @@ -1294,47 +820,19 @@ typedef struct { FlutterAssetResolverGetAsMappingCallback get_as_mapping_callback; VoidCallback destruction_callback; } FlutterCustomAssetResolver; -``` - -In [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841): -```c - /// The number of entries in `custom_asset_resolvers`. - size_t custom_asset_resolvers_count; - - /// Embedder-provided resolvers that the engine queries in array order for - /// assets, including `kernel_blob.bin` in JIT mode. When - /// `custom_asset_resolvers_count` is non-zero, `assets_path` may be null. - const FlutterCustomAssetResolver** custom_asset_resolvers; +// Appended to FlutterProjectArgs: +size_t custom_asset_resolvers_count; +const FlutterCustomAssetResolver** custom_asset_resolvers; ``` -Resolvers configured via `FlutterProjectArgs.custom_asset_resolvers` remain active across the lifetime of the engine, including hot reload, hot restart, and dynamic feature installation: - -- **Hot Restart and Hot Reload**: [`Shell::OnServiceProtocolRunInView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1992-L2043) and [`Shell::OnServiceProtocolSetAssetBundlePath`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L2168-L2203) automatically re-attach every existing resolver whose `IsValidAfterAssetManagerChange()` returns `true` (which the engine's `FlutterCustomAssetResolver` wrapper implements), so the engine continues invoking `get_as_mapping_callback` alongside the updated `devFS` directory. -- **Play Store Deferred Component Split Installation**: When [`PlayStoreDeferredComponentManager.loadAssets`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java#L383-L399) creates a refreshed package context after a split APK install and calls `FlutterJNI.updateJavaAssetManager`, the Android embedder atomically swaps the `AAssetManager*` and global Java `AssetManager` reference inside the struct pointed to by the resolver's `user_data`. - -### Multi-Engine Spawning and Add-to-App (`FlutterEngineSpawn`) - -To support [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) and Add-to-App multi-engine sharing without calling internal [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) methods: +### Multi-Engine Spawning and Add-to-App -- `FlutterEngineSpawn` and `FlutterEngineSpawnInfo` are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). -- `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285), spawning child `FlutterEngine` instances that share the parent engine's Dart VM isolate group, `io_manager`, and resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) (including when the parent engine initialized with `type = kDeferred`). - -In the existing architecture, [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) reaches directly into [`shell_->Spawn(...)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210): - -```cpp -// Existing spawning in shell/platform/android/android_shell_holder.cc -std::unique_ptr shell = - shell_->Spawn(std::move(config.value()), initial_route, - on_create_platform_view, on_create_rasterizer); - -return std::unique_ptr(new AndroidShellHolder( - GetSettings(), jni_facade, thread_host_, std::move(shell), - apk_asset_provider_->Clone(), weak_platform_view, - android_context->RenderingApi())); -``` - -The public C-API lacked a spawn API, which prevented multi-engine groups from operating without internal [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h) methods. [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) receives [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn): +> [!IMPORTANT] +> To support [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) and Add-to-App multi-engine sharing without calling internal [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) methods: +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterEngineSpawn` and `FlutterEngineSpawnInfo`. +> - **Behavioral changes**: `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285), spawning child `FlutterEngine` instances that share the parent engine's Dart VM isolate group, `io_manager`, and resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) (including when the parent engine initialized with `type = kDeferred` or when `renderer_config` is `nullptr`). ```c typedef struct { @@ -1356,222 +854,123 @@ FlutterEngineResult FlutterEngineSpawn( FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); ``` -The [`FlutterEngineSpawnInfo`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) struct uses the info struct pattern (`struct_size`) to maintain forward ABI compatibility. The `initial_route` parameter matches [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204) and [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) so that spawned isolates mount distinct route hierarchies immediately upon launch. When `renderer_config` is `nullptr` (or when the parent engine used `kDeferred`), the spawned engine reuses the parent's resolved renderer configuration and [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70). - -Spawned shells share the parent engine's resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) (Vulkan instance, physical device, logical device, queue handles, pipeline cache, and texture pools) in addition to sharing the Dart VM and root isolate group. This sharing avoids driver re-initialization and eliminates pipeline compilation spikes when creating secondary Flutter views in Add-to-App configurations. - -#### Relationship to Single-Isolate Multiview (`FlutterEngineAddView`) - -`FlutterEngineSpawn` and single-isolate multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)) address distinct concurrency and isolation trade-offs on Android: - -1. **Multi-Isolate Engine Groups (`FlutterEngineSpawn` / [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41))**: Each spawned [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) runs an independent Dart root isolate within the shared Dart VM isolate group and [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70). A long-running computation or frame drop in one view cannot stall the UI thread of another view, while native memory overhead remains low (~180 KB per additional isolate). -2. **Single-Isolate Multiview ([`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) / [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062-L3064))**: A single [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L90) and single Dart isolate drive multiple [`FlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java#L112) targets simultaneously, allowing widgets in distinct native windows or surfaces to read and mutate the same Dart object graph directly without cross-isolate message serialization. - -Migrating the Android embedder onto [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) unlocks both patterns: `FlutterEngineSpawn` supports [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41), while per-view [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) callbacks ([`FlutterPresentViewInfo.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2237) and [`FlutterBackingStoreConfig.view_id`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2167)) provide the foundation for single-isolate multiview and multi-window rendering. - -### Task Runner Configuration, Thread Priorities, and Threading Modes - -To configure Android thread scheduling, [`ALooper`](https://developer.android.com/ndk/reference/group/looper) binding, and thread merging through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) without instantiating internal [`flutter::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21) directly: - -- No new functions or structs are added to `embedder.h`. -- The Android embedder sets [`FlutterCustomTaskRunners.thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975) to apply Android's Linux scheduling priorities (`setpriority()`) and CPU core affinity while leaving `platform_task_runner` and `render_task_runner` set to `nullptr`. -- Leaving `platform_task_runner` and `render_task_runner` `nullptr` causes [`EmbedderThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc#L175-L216) to bind [`fml::MessageLoopAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/message_loop_android.cc#L66-L86) to the calling Android UI thread's NDK `ALooper` and create engine-managed worker threads with valid [`fml::TaskQueueId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_queue_id.h#L16)s required for dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`). -- Merged platform/UI threading (`kMergeAfterLaunch`) is configured by passing `--merged-platform-ui-thread=mergeAfterLaunch` via `FlutterProjectArgs.command_line_argv`. - -When `platform_task_runner` and `render_task_runner` are `nullptr`, [`EmbedderThreadHost::CreateEmbedderManagedPlatformThreadHost()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc#L175-L216) calls [`fml::MessageLoop::EnsureInitializedForCurrentThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.cc#L46-L56) on the calling Android UI thread (which binds [`fml::MessageLoopAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/message_loop_android.cc#L66-L86) directly to the thread's NDK [`ALooper`](https://developer.android.com/ndk/reference/group/looper) via `ALooper_forThread()`) and spawns the engine's dedicated worker threads (`ui`, `raster`, `io`). Because both the platform and raster runners are backed by [`fml::TaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h#L26) instances with valid [`fml::TaskQueueId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_queue_id.h#L16)s (unlike [`EmbedderTaskRunner`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc#L14-L20), which uses `TaskQueueId::kInvalid`), [`fml::MessageLoopTaskQueues::Merge()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop_task_queues.cc#L212-L261) can dynamically merge and unmerge the raster and platform queues when `FlutterCompositor.merge_raster_thread_on_platform_views` is enabled. - -Thread priority configuration on Android requires system-level tuning. In the existing architecture, [`AndroidPlatformThreadConfigSetter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35) (located in [`android_shell_holder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L35-L75)) uses `setpriority()` system calls to set Linux thread scheduling priorities: - -```cpp -// Existing thread priority configuration in shell/platform/android/android_shell_holder.cc -static void AndroidPlatformThreadConfigSetter( - const fml::Thread::ThreadConfig& config) { - fml::Thread::SetCurrentThreadName(config); - switch (config.priority) { - case fml::Thread::ThreadPriority::kBackground: { - fml::RequestAffinity(fml::CpuAffinity::kEfficiency); - if (::setpriority(PRIO_PROCESS, 0, 10) != 0) { - FML_LOG(ERROR) << "Failed to set IO task runner priority"; - } - break; - } - case fml::Thread::ThreadPriority::kDisplay: { - fml::RequestAffinity(fml::CpuAffinity::kNotEfficiency); - if (::setpriority(PRIO_PROCESS, 0, -1) != 0) { - FML_LOG(ERROR) << "Failed to set UI task runner priority"; - } - break; - } - case fml::Thread::ThreadPriority::kRaster: { - fml::RequestAffinity(fml::CpuAffinity::kNotEfficiency); - if (::setpriority(PRIO_PROCESS, 0, -5) != 0) { - if (::setpriority(PRIO_PROCESS, 0, -2) != 0) { - FML_LOG(ERROR) << "Failed to set raster task runner priority"; - } - } - break; - } - default: - fml::RequestAffinity(fml::CpuAffinity::kNotPerformance); - if (::setpriority(PRIO_PROCESS, 0, 0) != 0) { - FML_LOG(ERROR) << "Failed to set priority"; - } - } -} -``` - -The C-API embedder wires [`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975) in [`FlutterCustomTaskRunners`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1960-L1980) to execute `setpriority()`. This reconciles the priority discrepancy between Android's embedder (which configures the IO task runner to `kNormal` priority, Linux nice value 0) and the engine-managed default (`kBackground`, Linux nice value 10). - -Internal consumers relying on the `kMergeAfterLaunch` threading model are supported without adding public fields to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). The embedder passes the engine switch `--merged-platform-ui-thread=mergeAfterLaunch` via `FlutterProjectArgs.command_line_argv`. In Java, [`FlutterLoader.ensureInitializationComplete`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L303-L465) synthesizes this command-line argument dynamically from application manifest metadata and runtime configuration. - -### Process-Scoped Utilities, Image Decoding, and Diagnostics C-APIs +### Image Generators, Fonts, and VM URI -To eliminate the remaining [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) header inclusions in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108): +> [!IMPORTANT] +> To eliminate the remaining [`//flutter/lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h#L27), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime/dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h#L22) inclusions in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108): +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`, `FlutterImageGenerator`, `FlutterImageGeneratorInfo`), `FlutterEnginePrefetchDefaultFontManager`, and `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`). +> - **Behavioral changes**: The Android embedder registers its fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats at priority `-1`) without subclassing `flutter::ImageGenerator`, and the engine keeps the encoded byte buffer alive until `destruction_callback` runs. `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists, and `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. -- No callback-cache functions or structs are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): the non-Dart `{name, class_name, library_path}` JSON cache table in [`DartCallbackCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) is moved from `//flutter/lib/ui/plugins/callback_cache.{h,cc}` to [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) (keeping only `LookupDartClosure` in `//flutter/lib/ui`) and lazily loads `flutter_callback_cache.json` from [`fml::paths::GetCachesDirectory()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/paths.h#L27) on first lookup or insert. -- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`, `FlutterImageGenerator`, `FlutterImageGeneratorInfo`) is added to `embedder.h` so the Android embedder can register its fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats) without subclassing `flutter::ImageGenerator`. -- `FlutterEnginePrefetchDefaultFontManager` is added to `embedder.h` and `FlutterEngineProcTable` to warm up the process-wide default font manager on a background thread during `FlutterLoader.initAsync`. -- `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`) is added to `embedder.h` to deliver the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. -- No screenshot C API is added to `embedder.h`; `FlutterRenderer.getBitmap()` is implemented on the platform side via Android [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) so that Hybrid Composition and HCPP platform views are captured alongside Flutter content. - -The default font manager is a process-wide singleton that Android warms up before any engine exists, so `FlutterEnginePrefetchDefaultFontManager` takes no engine handle, like the existing [`FlutterEngineRunsAOTCompiledDartCode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3501-L3502). Image decoders and the VM service URI notification are per-engine `FlutterProjectArgs` fields. Every new function is added to `FlutterEngineProcTable`, because `flutter_embedder_native_src` builds with `FLUTTER_ENGINE_NO_PROTOTYPES`: - -1. **Process-Scoped Callback Cache ([`//flutter/lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) Severance)**: In [`//flutter/lib/ui/plugins/callback_cache.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.cc#L38-L148), the persisted `{name, class_name, library_path}` JSON map (`SetCachePath`, `LoadCacheFromDisk`, `GetCallbackInformation`, and `GetCallbackHandle`) has no dependency on `dart:ui` or `dart_api.h`; only `GetCallback(int64_t) -> Dart_Handle` (called by `LookupDartClosure` in [`dart_runtime_hooks.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/dart_runtime_hooks.cc#L255)) touches the Dart VM C API. Moving the non-Dart JSON cache table into [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) (resolving the existing `TODO` in [`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143)) and lazily loading `flutter_callback_cache.json` from [`fml::paths::GetCachesDirectory()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/paths.h#L27) on the first `GetCallbackInformation` or `GetCallbackHandle` call severs [`//flutter/lib/ui/plugins/callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h) from `shell/platform/android/` without adding callback-cache functions to `embedder.h`, and removes synchronous disk I/O from the Android main thread during [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L148-L157). - -2. **Embedder Image Decoders ([`//flutter/lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h#L27) Severance)**: [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L173-L178) registers a fallback decoder backed by Android's [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (API 28+, using [`ALLOCATOR_SOFTWARE`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/image/ImageDecoderDefaultImpl.java#L49-L53)) at priority `-1` for formats the built-in decoders reject, such as HEIF. Because the `ImageGeneratorRegistry` belongs to each `Engine`, decoders are configured via `FlutterProjectArgs.image_generators` (`image_generators_count`), and the engine keeps the encoded byte buffer alive until the generator's `destruction_callback` runs so the embedder can wrap it in a direct `ByteBuffer` without copying: - - ```c - typedef enum { - kFlutterImageGeneratorColorTypeRGBA8888, - kFlutterImageGeneratorColorTypeBGRA8888, - } FlutterImageGeneratorColorType; - - typedef enum { - kFlutterImageGeneratorAlphaTypeOpaque, - kFlutterImageGeneratorAlphaTypePremul, - kFlutterImageGeneratorAlphaTypeUnpremul, - } FlutterImageGeneratorAlphaType; - - typedef struct { - size_t struct_size; - uint32_t width; - uint32_t height; - FlutterImageGeneratorColorType color_type; - FlutterImageGeneratorAlphaType alpha_type; - } FlutterImageGeneratorInfo; - - typedef bool (*FlutterImageGeneratorGetInfoCallback)( - void* user_data, - FlutterImageGeneratorInfo* info_out); - - typedef bool (*FlutterImageGeneratorGetPixelsCallback)( - void* user_data, - const FlutterImageGeneratorInfo* info, - void* pixels, - size_t row_bytes); - - typedef struct { - size_t struct_size; - void* user_data; - FlutterImageGeneratorGetInfoCallback get_info; - FlutterImageGeneratorGetPixelsCallback get_pixels; - VoidCallback destruction_callback; - } FlutterImageGenerator; - - typedef bool (*FlutterImageGeneratorFactoryCallback)( - void* user_data, - const uint8_t* data, - size_t data_size, - FlutterImageGenerator* generator_out); +```c +typedef enum { + kFlutterImageGeneratorColorTypeRGBA8888, + kFlutterImageGeneratorColorTypeBGRA8888, +} FlutterImageGeneratorColorType; - typedef struct { - size_t struct_size; - FlutterImageGeneratorFactoryCallback create_generator; - int32_t priority; - void* user_data; - VoidCallback destruction_callback; - } FlutterImageGeneratorRegistration; +typedef enum { + kFlutterImageGeneratorAlphaTypeOpaque, + kFlutterImageGeneratorAlphaTypePremul, + kFlutterImageGeneratorAlphaTypeUnpremul, +} FlutterImageGeneratorAlphaType; - // Appended to FlutterProjectArgs: - size_t image_generators_count; - const FlutterImageGeneratorRegistration** image_generators; - ``` +typedef struct { + size_t struct_size; + uint32_t width; + uint32_t height; + FlutterImageGeneratorColorType color_type; + FlutterImageGeneratorAlphaType alpha_type; +} FlutterImageGeneratorInfo; -3. **Font Manager Prefetching ([`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) Severance)**: During [`FlutterLoader.initAsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java#L276-L279), Android warms up the process-wide default system font manager on a background worker before `FlutterEngineInitialize` and passes `--prefetched-default-font-manager` in `command_line_argv`: +typedef bool (*FlutterImageGeneratorGetInfoCallback)( + void* user_data, + FlutterImageGeneratorInfo* info_out); - ```c - FLUTTER_EXPORT - FlutterEngineResult FlutterEnginePrefetchDefaultFontManager(void); - ``` +typedef bool (*FlutterImageGeneratorGetPixelsCallback)( + void* user_data, + const FlutterImageGeneratorInfo* info, + void* pixels, + size_t row_bytes); -4. **VM Service URI Discovery ([`//flutter/runtime/dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h#L22) Severance)**: Tooling and integration tests (such as the `espresso` package) read the Dart VM Service URL via [`FlutterJNI.getVMServiceUri()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L273-L275). [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) adds `vm_service_uri_callback`, invoked on the platform thread once the VM service is listening (and never invoked after [`FlutterEngineShutdown`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2947-L2948)): +typedef struct { + size_t struct_size; + void* user_data; + FlutterImageGeneratorGetInfoCallback get_info; + FlutterImageGeneratorGetPixelsCallback get_pixels; + VoidCallback destruction_callback; +} FlutterImageGenerator; - ```c - typedef struct { - size_t struct_size; - const char* uri; - } FlutterVMServiceUriInfo; +typedef bool (*FlutterImageGeneratorFactoryCallback)( + void* user_data, + const uint8_t* data, + size_t data_size, + FlutterImageGenerator* generator_out); - typedef void (*FlutterVMServiceUriCallback)( - const FlutterVMServiceUriInfo* info, - void* user_data); +typedef struct { + size_t struct_size; + FlutterImageGeneratorFactoryCallback create_generator; + int32_t priority; + void* user_data; + VoidCallback destruction_callback; +} FlutterImageGeneratorRegistration; - // Appended to FlutterProjectArgs: - FlutterVMServiceUriCallback vm_service_uri_callback; - ``` +typedef struct { + size_t struct_size; + const char* uri; +} FlutterVMServiceUriInfo; -**Platform-Side Screenshot Capture ([`FlutterRenderer.getBitmap()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java#L1342-L1345))**: No screenshot entry point is added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). Engine-side [`Rasterizer::Screenshot`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L1075-L1140) re-rasterizes the last layer tree with a null [`ExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L432) ([`rasterizer.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.cc#L910-L920)), which omits HC and HCPP platform views. Instead, the Android embedder implements `FlutterRenderer.getBitmap()` in platform code using [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) (available from `minSdk` 24), matching how the macOS embedder serves `flutter/screenshot` from its front `IOSurface`. +typedef void (*FlutterVMServiceUriCallback)( + const FlutterVMServiceUriInfo* info, + void* user_data); -### Platform Channel Messaging, Background Handlers, and Task Queues +FLUTTER_EXPORT +FlutterEngineResult FlutterEnginePrefetchDefaultFontManager(void); -To replace the internal [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18) subclass while preserving background `TaskQueue` dispatch and startup message ordering: - -- `FlutterProjectArgs.enable_direct_platform_message_dispatch` (`bool`, defaulting to `false`) is added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). -- When `enable_direct_platform_message_dispatch` is `true`, [`EmbedderPlatformMessageHandler`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) invokes `platform_message_callback` directly on the calling isolate's thread (and returns `false` from `DoesHandlePlatformMessageOnPlatformThread()`), so root-isolate messages avoid an extra main-`Looper` post and background-isolate messages route directly to [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) executors without bouncing through the Android main thread. - -In the existing Android architecture, [`PlatformMessageHandlerAndroid::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.cc#L56-L67) invokes `FlutterJNI.handlePlatformMessage` synchronously on the calling thread—either the UI thread (via [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1532-L1579)) or a background Dart isolate's thread (via [`UIDartState::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L223-L240)): - -```cpp -// Existing Android message dispatch in shell/platform/android/platform_message_handler_android.cc -void PlatformMessageHandlerAndroid::HandlePlatformMessage( - std::unique_ptr message) { - // Called from any thread. - int response_id = next_response_id_.fetch_add(1); - if (auto response = message->response()) { - std::lock_guard lock(pending_responses_mutex_); - pending_responses_[response_id] = response; - } - jni_facade_->FlutterViewHandlePlatformMessage(std::move(message), - response_id); -} +// Appended to FlutterProjectArgs: +size_t image_generators_count; +const FlutterImageGeneratorRegistration** image_generators; +FlutterVMServiceUriCallback vm_service_uri_callback; ``` -Inside Java, [`DartMessenger.dispatchMessageToQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L299-L327) inspects the handler's registered `TaskQueue` and either dispatches to a background `SerialTaskQueue` / `ConcurrentTaskQueue` executor (`makeBackgroundTaskQueue()`) or posts to the main `Looper`'s `PlatformTaskQueue`. Meanwhile, [`PlatformMessageHandlerAndroid::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L23-L25) returns `false`, which tells [`Shell::OnEngineHandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L1532-L1571) to bounce messages emitted during engine startup (`route_messages_through_platform_thread_`) through the platform thread before dispatching them so that plugins registered immediately after `FlutterEngineRun` in the same platform-thread turn do not miss messages sent during `main()`. - -In contrast, [`EmbedderPlatformMessageHandler::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) in `shell/platform/embedder/` unconditionally posts every message to `platform_task_runner_` and returns `true` from `DoesHandlePlatformMessageOnPlatformThread()`. +### Platform Channel Messaging, Background Handlers, and Task Queues -#### Rationale for Direct Platform Message Dispatch +> [!IMPORTANT] +> To replace the internal [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18) subclass while preserving background `TaskQueue` dispatch and startup message ordering: +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.enable_direct_platform_message_dispatch` (`bool`, defaulting to `false`). +> - **Behavioral changes**: When `enable_direct_platform_message_dispatch` is `true`, [`EmbedderPlatformMessageHandler`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) invokes `platform_message_callback` directly on the calling isolate's thread (and returns `false` from `DoesHandlePlatformMessageOnPlatformThread()`), so root-isolate messages avoid an extra main-`Looper` post and background-isolate messages route directly to [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) executors without bouncing through the Android main thread. -Although the platform and UI threads are merged by default on Android ([`settings.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/settings.h#L381-L382))—meaning root-isolate platform messages already originate on the Android main thread and `makeBackgroundTaskQueue()` handlers still run on a background executor either way—invoking `platform_message_callback` directly in `HandlePlatformMessage` (and returning `false` from `DoesHandlePlatformMessageOnPlatformThread()`) preserves behavioral parity in two ways: +```mermaid +flowchart LR + subgraph Engine["Engine Isolate Threads"] + Root["Root Isolate (UI Thread / Shell::OnEngineHandlePlatformMessage)"] + BgIso["Background Isolate (UIDartState::HandlePlatformMessage)"] + end -1. **Avoiding an Extra Main-`Looper` Turn for Root-Isolate Messages**: With direct dispatch, [`DartMessenger.handleMessageFromDart`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L330-L361) runs synchronously inside the send call instead of queuing an extra `PostTask` at the tail of the main `Looper`. Handlers registered with [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) are submitted to their background executor immediately while the current frame finishes on the main thread, and default-queue handlers take one main-`Looper` post (via [`PlatformTaskQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformTaskQueue.java#L13-L22)) instead of two—matching the iOS embedder migration RFC. -2. **Keeping Background-Isolate Messages Off the Main Thread**: Background isolates registered via [`PlatformDispatcher.registerBackgroundIsolate`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L720-L722) invoke [`UIDartState::HandlePlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L223-L240) on their own isolate thread ([`ui_dart_state.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/ui_dart_state.cc#L223-L240)). Direct dispatch allows a background isolate communicating with a `makeBackgroundTaskQueue()` plugin handler to dispatch directly from the background isolate thread to the plugin's background executor without bouncing through the main `Looper`. + subgraph Handler["EmbedderPlatformMessageHandler (enable_direct_platform_message_dispatch = true)"] + Startup{"During FlutterEngineRunInitialized (route_messages_through_platform_thread_)?"} + Direct["Invoke platform_message_callback synchronously on calling thread"] + end -#### C-ABI Specification + subgraph Messenger["Java DartMessenger.dispatchMessageToQueue"] + QueueCheck{"Handler registered with makeBackgroundTaskQueue()?"} + BgExec["Background Executor (SerialTaskQueue / ConcurrentTaskQueue)"] + MainLooper["PlatformTaskQueue (Single post to Android Main Looper)"] + end -To reconcile this discrepancy without breaking existing desktop embedders, [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) appends an opt-in field to [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841) and updates the threading documentation of the existing `platform_message_callback`: + Root --> Startup + Startup -- "Yes (startup turn)" --> MainLooper + Startup -- "No" --> Direct + BgIso --> Direct + Direct --> QueueCheck + QueueCheck -- "Yes (background TaskQueue)" --> BgExec + QueueCheck -- "No (default queue)" --> MainLooper + BgExec -. "FlutterEngineSendPlatformMessageResponse (thread-safe)" .-> Engine + MainLooper -. "FlutterEngineSendPlatformMessageResponse" .-> Engine +``` ```c - /// The callback invoked by the engine in order to give the embedder the - /// chance to respond to platform messages from the Dart application. - /// The callback will be invoked on the thread on which the `FlutterEngineRun` - /// call is made, unless `enable_direct_platform_message_dispatch` is true. - /// The second parameter, `user_data`, is supplied when `FlutterEngineRun` or - /// `FlutterEngineInitialize` is called. - FlutterPlatformMessageCallback platform_message_callback; - - // ... existing members ... - + // Appended to FlutterProjectArgs: /// If true, the engine invokes `platform_message_callback` directly on the /// thread that sent the message (typically the UI thread) instead of posting /// a task to the platform thread. Defaults to false, which preserves the @@ -1579,52 +978,17 @@ To reconcile this discrepancy without breaking existing desktop embedders, [`emb bool enable_direct_platform_message_dispatch; ``` -#### Android and iOS Cross-Platform Parity - -This configuration field satisfies both mobile embedder targets: - -- **Zero-Initialization Safety**: Existing desktop embedders (Linux, macOS, Windows) zero-initialize [`FlutterProjectArgs`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2527-L2841), which defaults the field to `false` and preserves their single-threaded platform message assumptions. -- **Mobile Embedder Parity**: Both Android and iOS explicitly set `enable_direct_platform_message_dispatch = true`. On iOS, [`PlatformMessageHandlerIos::DoesHandlePlatformMessageOnPlatformThread()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/darwin/ios/platform_message_handler_ios.h#L22) returns `false` to dispatch directly to custom task queues or background GCD dispatch queues. On Android, incoming messages delivered to [`platform_message_callback`](#platform-channel-messaging-background-handlers-and-task-queues) on the UI thread inspect registered channel handlers in [`DartMessenger`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L32): - - If registered with a [`BinaryMessenger.TaskQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java#L37), the task is dispatched directly to that queue's background executor pool. - - If registered on the default queue, the task posts to the Android platform [`Looper`](https://developer.android.com/reference/android/os/Looper). - - Responses submitted via [`FlutterEngineSendPlatformMessageResponse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3185-L3189) execute safely from any thread without main-thread hops. - -For empty platform messages, the C-API guarantees that a message where `message_size == 0` and `message == nullptr` is delivered to the framework as an empty byte buffer without triggering null-pointer dereferences or serialization assertions. - -### Pointer Input Events, Viewport Metrics, and System Configuration - -To route pointer input, edge-to-edge/foldable window metrics, and system configuration without internal [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) headers or synchronous JNI calls: - -- `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, and `FlutterDisplayFeature` (`FlutterDisplayFeatureType`, `FlutterDisplayFeatureState`) are added to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), and `physical_padding`, `physical_system_gesture_insets`, `physical_touch_slop`, `display_corner_radii`, and `display_features` (`display_features_count`) are appended in place to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). -- Pointer input and locale updates use the existing [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082) and [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3479-L3481) C APIs without `embedder.h` additions. -- Android 14+ non-linear font scaling replaces synchronous UI-thread JNI calls (`PlatformView::GetScaledFontSize` and `SettingsChannel.ConfigurationQueue`) by sending the 9-point `FontScaleConverter` lookup table (`fontScaleCurves`) over the existing `flutter/settings` JSON channel and interpolating in `PlatformDispatcher.scaleFontSize`. - -#### Pointer Input Events - -Input events processed in [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L372-L445) provide axis and device metadata. The C-API embedder populates [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1362-L1409) with: +### Viewport Metrics and Non-Linear Font Scaling -- Device kind: [`kFlutterPointerDeviceKindTouch`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1321), [`kFlutterPointerDeviceKindMouse`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1320), [`kFlutterPointerDeviceKindStylus`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1322), or [`kFlutterPointerDeviceKindTrackpad`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1323). -- Axis values: `tilt`, `orientation`, `pressure`, and secondary scroll axes. -- Button states: [`kFlutterPointerButtonMousePrimary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1330), [`kFlutterPointerButtonMouseSecondary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1331), and stylus button bitmasks. +> [!IMPORTANT] +> To deliver Android window insets, display cutouts, foldable postures, and Android 14+ non-linear font scaling without internal [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) headers or synchronous per-layout JNI calls: +> +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, and `FlutterDisplayFeature` (`FlutterDisplayFeatureType`, `FlutterDisplayFeatureState`), and appends `physical_padding`, `physical_system_gesture_insets`, `physical_touch_slop`, `display_corner_radii`, and `display_features` (`display_features_count`) in place to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). +> - **Behavioral changes**: +> - [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433) populates the extended [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) and dispatches it via [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077); `embedder.cc` reads the new fields using `SAFE_ACCESS(event, member, default_value)`. +> - Android 14+ (API 34+) non-linear font scaling replaces synchronous UI-thread JNI calls ([`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1023-L1024) -> [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) and [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176)) by sending the 9-point `FontScaleConverter` lookup table (`fontScaleCurves`) over the `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) and interpolating in `PlatformDispatcher.scaleFontSize`. -#### Viewport Metrics, Edge-to-Edge Insets, and Foldables - -Android delivers window configuration metrics through [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433): - -- Physical padding (status bar, navigation bar, display cutouts) -- Physical view insets (software IME keyboard) -- System gesture insets (Android 10+ edge back gestures) -- Physical touch slop threshold for scroll disambiguation -- Physical display corner radii -- Display features (hinge bounds, display feature types, and device posture state) - -[`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) in `embedder.h` historically provided only `width`, `height`, and `pixel_ratio`. - -Rather than creating a separate struct, [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) is extended directly in place: - -1. [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) contains a `struct_size` field and is passed by pointer (`const FlutterWindowMetricsEvent* event`). -2. This follows the existing precedent in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), which appended window constraint fields directly to `FlutterWindowMetricsEvent`. -3. Engine implementations use `SAFE_ACCESS(event, member, default_value)` to read new fields, which guarantees that existing desktop embedders passing the baseline struct size continue to work without modification. +#### `FlutterWindowMetricsEvent` In-Place Extensions ```c /// Distances from each edge of a rectangle, in physical pixels. @@ -1667,7 +1031,7 @@ typedef struct { } FlutterDisplayFeature; ``` -The following fields are appended to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110): +Appended to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110): ```c /// Physical window padding (for example, status bar, navigation bar, cutouts). @@ -1693,45 +1057,48 @@ The following fields are appended to [`FlutterWindowMetricsEvent`](https://githu const FlutterDisplayFeature** display_features; ``` -#### System Channels: Locale and Non-Linear Font Scaling - -Features that communicate standard framework and platform configuration use platform message channels rather than custom additions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h): - -1. **Locale Configuration**: The embedder uses the standard `flutter/localization` platform message channel ([`LocalizationChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java#L61-L97)). In the engine ([`embedder.cc:3410-3467`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc#L3410-L3467)), [`FlutterEngineUpdateLocales`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3479-L3481) serializes locale data into a JSON message on this channel. No new C-API entry point is added for locale settings. - -2. **Android 14+ Non-Linear Font Scaling**: Android 14 (API level 34+) uses piecewise-linear font scaling curves (`FontScaleConverter`) so that large typography scales at lower ratios than body text, preventing UI clipping. In the legacy architecture, this was serviced through a synchronous JNI query ([`PlatformDispatcher._getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1634) -> [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1023-L1024) -> [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544)) paired with [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176). Because `PlatformViewEmbedder` does not override `GetScaledFontSize`, invoking `_getScaledFontSize` under `embedder.h` hits the base `PlatformView::GetScaledFontSize` implementation (`FML_UNREACHABLE()`). - - To support non-linear scaling without synchronous UI-thread JNI queries, [`ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176) generation tracking, or new C-APIs in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h), the existing `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) is extended: - - - During startup and configuration changes on API 34+ (when [`SettingsChannel.hasNonlinearTextScalingSupport()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L45-L47) is true and `textScaleFactor != 1.0`), [`SettingsChannel.MessageBuilder.send()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L105-L128) samples `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, displayMetrics) / displayMetrics.density` across Android's standard piecewise-linear lookup table breakpoints (`8, 10, 12, 14, 18, 20, 24, 30, 100` sp) and includes the resulting `(unscaled, scaled)` curve points in the `flutter/settings` JSON payload: +#### Android 14+ Non-Linear Font Scaling - ```json - { - "textScaleFactor": 1.15, - "platformBrightness": "light", - "alwaysUse24HourFormat": false, - "fontScaleCurves": [ - {"unscaled": 8.0, "scaled": 9.2}, - {"unscaled": 10.0, "scaled": 11.5}, - {"unscaled": 12.0, "scaled": 13.8}, - {"unscaled": 14.0, "scaled": 16.4}, - {"unscaled": 18.0, "scaled": 20.6}, - {"unscaled": 20.0, "scaled": 22.4}, - {"unscaled": 24.0, "scaled": 26.2}, - {"unscaled": 30.0, "scaled": 31.8}, - {"unscaled": 100.0, "scaled": 100.0} - ] - } - ``` +```mermaid +sequenceDiagram + autonumber + participant Settings as "SettingsChannel.MessageBuilder (Java, API 34+)" + participant Disp as "PlatformDispatcher (dart:ui)" + participant Layout as "Flutter Text Layout" + + Settings->>Settings: Sample TypedValue.applyDimension across 9 sp breakpoints (8..100 sp) + Settings-)Disp: flutter/settings JSON payload (textScaleFactor + fontScaleCurves) + Disp->>Disp: _updateUserSettingsData() stores fontScaleCurves, invalidates _cachedFontSizes, fires onTextScaleFactorChanged + loop Every Text Layout Pass (Zero JNI Calls) + Layout->>Disp: scaleFontSize(unscaledFontSize) + Disp->>Disp: Binary-search fontScaleCurves & linearly interpolate (matching FontScaleConverterImpl) + Disp-->>Layout: scaledFontSize + end +``` - - In `dart:ui`, [`PlatformDispatcher._updateUserSettingsData`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1286-L1366) parses `fontScaleCurves` into `_PlatformConfiguration` and compares it against `previousConfiguration.fontScaleCurves`. When either `textScaleFactorChanged` or `fontScaleCurvesChanged` is true, `_updateUserSettingsData` invalidates `_cachedFontSizes` and invokes `onTextScaleFactorChanged` (in addition to `onPlatformConfigurationChanged`). - - [`PlatformDispatcher.scaleFontSize(double unscaledFontSize)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/platform_dispatcher.dart#L1553-L1577) evaluates scaled sizes purely in Dart from `_configuration.fontScaleCurves`: - - If `textScaleFactor == 1.0` or `_configuration.fontScaleCurves` is null/empty, it returns `unscaledFontSize * textScaleFactor`. - - Otherwise, it binary-searches the sorted `(unscaled, scaled)` breakpoints in `fontScaleCurves` and linearly interpolates between the two bounding points (`lower.scaled + (upper.scaled - lower.scaled) * ((unscaledFontSize - lower.unscaled) / (upper.unscaled - lower.unscaled))`), interpolating from `(0.0, 0.0)` below the first breakpoint and scaling proportionally (`last.scaled * (unscaledFontSize / last.unscaled)`) above the final breakpoint to match Android's `FontScaleConverterImpl`. This removes synchronous JNI round-trips during text layout and eliminates [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176). +```json +{ + "textScaleFactor": 1.15, + "platformBrightness": "light", + "alwaysUse24HourFormat": false, + "fontScaleCurves": [ + {"unscaled": 8.0, "scaled": 9.2}, + {"unscaled": 10.0, "scaled": 11.5}, + {"unscaled": 12.0, "scaled": 13.8}, + {"unscaled": 14.0, "scaled": 16.4}, + {"unscaled": 18.0, "scaled": 20.6}, + {"unscaled": 20.0, "scaled": 22.4}, + {"unscaled": 24.0, "scaled": 26.2}, + {"unscaled": 30.0, "scaled": 31.8}, + {"unscaled": 100.0, "scaled": 100.0} + ] +} +``` ## Target Architecture and Design Principles -The target architecture establishes a strict C-ABI boundary between the Android embedder and the engine core, replacing the legacy [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) hierarchy with `FlutterEmbedderNative` and modular, single-responsibility C-API components. +> [!IMPORTANT] +> The target architecture replaces [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), and [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc) with `FlutterEmbedderNative` and single-responsibility C-API components in `:flutter_embedder_native_src` compiled with `FLUTTER_ENGINE_NO_PROTOTYPES`. Outbound C++-to-Java calls route through injectable `JniRouter` / `JniDelegate` / `JvmInvoker` interfaces so `flutter_embedder_native_unittests` run on the host without a live JVM or Android device. ```mermaid flowchart TD @@ -1743,11 +1110,12 @@ flowchart TD subgraph Android Embedder C++: flutter_embedder_native_src FlutterEmbedderNative["FlutterEmbedderNative: C-API Client & JNI Coordinator"] - JniRouter["JniRouter / JniDelegate / JvmInvoker"] - SurfaceControl["AndroidSurfaceControl"] + JniRouter["JniRouter / JniDelegate / JvmInvoker (Host-Testable JNI)"] + SurfaceControl["AndroidSurfaceControl (Keyed by FlutterViewId)"] PlatformViews["AndroidPlatformViewsController & AndroidMutatorsMapper"] Textures["AndroidHardwareBuffer & AndroidVulkanExternalTexture"] Mappers["AndroidSemanticsMapper & AndroidWindowMetricsMapper"] + VsyncAssets["AndroidVsyncWaiter, AndroidVMInit, AndroidEngineGroup, APKAssetProvider"] end subgraph Public C-API Boundary @@ -1755,129 +1123,66 @@ flowchart TD end subgraph Flutter Engine Core - FlutterEngineOpaque["FlutterEngine Opaque Handle"] + FlutterEngineOpaque["FlutterEngine Opaque Handle (PlatformViewEmbedder)"] Shell["Shell"] ImpellerFlow["Impeller / Flow Compositor"] DartVM["Dart VM"] end - Activity --> FlutterView - FlutterView --> FlutterJNI - FlutterJNI --> FlutterEmbedderNative + Activity --> FlutterView --> FlutterJNI --> FlutterEmbedderNative FlutterEmbedderNative --> JniRouter FlutterEmbedderNative --> SurfaceControl FlutterEmbedderNative --> PlatformViews FlutterEmbedderNative --> Textures FlutterEmbedderNative --> Mappers - FlutterEmbedderNative --> EmbedderH - EmbedderH --> FlutterEngineOpaque - FlutterEngineOpaque --> Shell + FlutterEmbedderNative --> VsyncAssets + FlutterEmbedderNative --> EmbedderH --> FlutterEngineOpaque --> Shell Shell --> ImpellerFlow Shell --> DartVM ``` ```mermaid sequenceDiagram + autonumber participant FlutterJNI - participant FlutterEmbedderNative as FlutterEmbedderNative: C-API Client - participant EmbedderAPI as embedder.h ProcTable - participant Core as FlutterEngine Core + participant FlutterEmbedderNative as "FlutterEmbedderNative (C-API Client)" + participant EmbedderAPI as "embedder.h ProcTable" + participant Core as "PlatformViewEmbedder & Shell" + participant Raster as "Raster Thread" FlutterJNI->>FlutterEmbedderNative: attachToNative (create FlutterEmbedderNative) FlutterJNI->>FlutterEmbedderNative: Pre-run calls (queued) FlutterJNI->>FlutterEmbedderNative: runBundleAndSnapshotFromLibrary (entrypoint and assets) - FlutterEmbedderNative->>EmbedderAPI: FlutterEngineInitialize (kDeferred, project_args) + FlutterEmbedderNative->>EmbedderAPI: FlutterEngineInitialize(kDeferred, project_args) EmbedderAPI-->>FlutterEmbedderNative: FlutterEngine Opaque Handle - FlutterEmbedderNative->>EmbedderAPI: FlutterEngineRunInitialized (engine_handle) - EmbedderAPI->>Core: LaunchShell, NotifyCreated (async), and RunRootIsolate - FlutterEmbedderNative->>EmbedderAPI: Flush queued pre-run calls -``` - -### Pure Embedder API Client (`FLUTTER_ENGINE_NO_PROTOTYPES`) - -In the target architecture: - -- `FlutterEmbedderNative` holds only an opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and dispatches all engine calls through an initialized `FlutterEngineProcTable` (`FlutterEngineGetProcAddresses`). -- The `:flutter_embedder_native_src` GN source set compiles with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`. This enforces at compile time that all C-API symbols are invoked through the function pointer table rather than direct static symbol linkage. -- Event dispatch (touch, window metrics, platform messages, accessibility actions) calls public functions: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082), [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077), [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114), and [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3330-L3336). -- Engine initialization delegates thread creation to the engine via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006). - -### Decomposition and Elimination of [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) - -In the legacy architecture, the native Android embedding is split across three tightly coupled classes that reach directly into 9 internal engine subsystems (98 internal headers): - -1. [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc): A monolithic JNI binding file that directly casts `jlong` handles to `AndroidShellHolder*` and calls internal C++ methods across `Shell` and `PlatformViewAndroid`. -2. [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38): The legacy C++ orchestrator instantiated by [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108). It allocates `fml::ThreadHost`, instantiates [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113), and owns [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43). -3. [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43): An internal engine class subclassing [`flutter::PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h) that centralizes window surfaces, platform view composition, external textures, and semantics. - -In the completed target architecture, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), and [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc) are completely deleted. In their place, `:flutter_embedder_native_src` organizes the Android embedder into a clean coordinator-component architecture: - -- `FlutterEmbedderNative` (`flutter_embedder_native.cc` / `flutter_embedder_native.h`): Is the exclusive top-level JNI coordinator (`FlutterEmbedderNative::RegisterJni(env)` in [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12)). It owns the opaque [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle, manages the `FlutterEngineProcTable`, and coordinates the modular subsystem components. -- `JniRouter`, `JniDelegate`, and `JvmInvoker` (`jni_router.h`, `jni_delegate.h`, `jvm_invoker.h`): Decouple outbound C++-to-Java JNI invocations from live Android `JNIEnv*` state through injectable delegate and provider interfaces (`FlutterJniProvider`, `HardwareBufferProvider`, `SurfaceControlProvider`, `SurfaceTransactionProvider`, and `ChoreographerProvider`). This allows the entire C-API embedder (`flutter_embedder_native_unittests`) to be unit-tested on the host machine without an Android JVM or device. -- `AndroidSurfaceControl` (`android_surface_control.cc`): Coordinates native window surfaces ([`ANativeWindow`](https://developer.android.com/ndk/reference/group/a-native-window)) and NDK [`ASurfaceControl`](https://developer.android.com/ndk/reference/group/surface-control) / [`ASurfaceTransaction`](https://developer.android.com/ndk/reference/group/surface-control) pipelines keyed by `FlutterViewId`, and implements the window ownership contract: in [`surfaceDestroyed()`](https://developer.android.com/reference/android/view/SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)) it stops handing out backing stores and waits only for the frame in flight before it releases the window. -- `AndroidPlatformViewsController` and `AndroidMutatorsMapper` (`android_platform_views_controller.cc`, `android_mutators_mapper.cc`): Manage platform view composition across Hybrid Composition (HC), Hybrid Composition++ (HCPP via `SurfaceControl` on API 34+), Texture Layer Hybrid Composition (TLHC), and Virtual Display (VD), deserialize [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) arrays, capture per-[`FlutterViewId`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L395) frame state by value to prevent cross-thread multi-view races, and attach exported Vulkan acquire fences to `SurfaceControl.Transaction` commits. -- `AndroidHardwareBuffer` and `AndroidVulkanExternalTexture` (`android_hardware_buffer.cc`, `android_vulkan_external_texture.cc`): Manage [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) and `EGLImageKHR` (OpenGL ES) with UV transformation matrices (`FlutterOpenGLExternalTexture` and `FlutterOpenGLTextureFrameCallback`) and zero-copy [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) ([`AHardwareBuffer`](https://developer.android.com/ndk/reference/group/a-hardware-buffer) imported into `VkImage` on Vulkan) texture callbacks (`FlutterVulkanExternalTexture`, `FlutterVulkanYcbcrConversionInfo`, and `FlutterVulkanTextureFrameCallback`). -- `AndroidSemanticsMapper` and `AndroidWindowMetricsMapper` (`android_semantics_mapper.cc`, `android_window_metrics_mapper.cc`): Translate [`FlutterSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1868-L1881) trees into JNI byte buffers for Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) and pack viewport metrics, insets, corner radii, and foldable display features into [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). -- `AndroidVsyncWaiter` (`android_vsync_waiter.cc`): Bridges Android NDK [`AChoreographer`](https://developer.android.com/ndk/reference/group/choreographer) and Java [`Choreographer`](https://developer.android.com/reference/android/view/Choreographer) frame callbacks to `FlutterProjectArgs.vsync_callback` and forwards frame pacing timestamps via `FlutterEngineOnVsync`. -- `AndroidVMInit` and `AndroidEngineGroup` (`android_vm_init.cc`, `android_engine_group.cc`): Coordinate initial Dart VM argument synthesis, Linux thread priority tuning ([`thread_priority_setter`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1975)), and multi-engine group spawning via `FlutterEngineSpawn`. -- [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) ([`apk_asset_provider.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.cc)): Implements `FlutterCustomAssetResolver` to stream kernel blobs and application assets directly from Android APK or AAB archives via NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) memory mapping without disk extraction. - -```mermaid -flowchart TD - subgraph Legacy["Legacy Monolithic Architecture (Deleted in Phase 4)"] - direction TB - JNI_Legacy["FlutterJNI & PlatformViewAndroidJNIImpl"] - ASH_Legacy["AndroidShellHolder
JNI Anchor, ThreadHost, Shell Owner"] - PVA_Legacy["PlatformViewAndroid: Subclasses flutter::PlatformView
Window Surfaces, Platform Views, Textures, Semantics"] - VWA_Legacy["VsyncWaiterAndroid: Subclasses flutter::VsyncWaiter"] - - JNI_Legacy --> ASH_Legacy - ASH_Legacy --> PVA_Legacy - ASH_Legacy --> VWA_Legacy - end - - subgraph Modular["Completed Target Architecture: flutter_embedder_native_src"] - direction TB - JNI_Target["FlutterJNI"] - FEN_Target["FlutterEmbedderNative
C-API Client & JNI Coordinator"] - JNIR_Target["JniRouter / JniDelegate / JvmInvoker
Host-Testable Outbound JNI"] - ASC_Target["AndroidSurfaceControl: Keyed by FlutterViewId"] - APVC_Target["AndroidPlatformViewsController & AndroidMutatorsMapper"] - ATR_Target["AndroidHardwareBuffer & AndroidVulkanExternalTexture"] - AAB_Target["AndroidSemanticsMapper & AndroidWindowMetricsMapper"] - AVW_Target["AndroidVsyncWaiter"] - AEG_Target["AndroidVMInit & AndroidEngineGroup"] - APK_Target["APKAssetProvider: FlutterCustomAssetResolver"] - - JNI_Target --> FEN_Target - FEN_Target --> JNIR_Target - FEN_Target --> ASC_Target - FEN_Target --> APVC_Target - FEN_Target --> ATR_Target - FEN_Target --> AAB_Target - FEN_Target --> AVW_Target - FEN_Target --> AEG_Target - FEN_Target --> APK_Target + FlutterEmbedderNative->>EmbedderAPI: FlutterEngineRunInitialized(engine_handle) + EmbedderAPI->>Core: LaunchShell(), NotifyCreated() (async), RunRootIsolate() + Core-)Raster: Post CreateImpellerContext & NotifyCreated (FIFO raster queue) + EmbedderAPI-->>FlutterEmbedderNative: Return without waiting for Raster Thread + par Raster Thread Resolves Renderer via C-API Callback + Raster->>Core: PlatformViewEmbedder::SetupImpellerContext() + Core->>FlutterEmbedderNative: resolve_callback(attempt 0) + FlutterEmbedderNative-->>Core: kVulkan (or kOpenGL fallback on retry) + Raster->>Core: CreateRenderingSurface (context-only) & Rasterizer::Setup + and Platform Thread Flushes Queued Calls + FlutterEmbedderNative->>EmbedderAPI: Flush queued pre-run calls end ``` -#### Mapping Embedder C-APIs to Target Architecture Components +### Mapping Embedder C-APIs to Target Architecture Components -The extensions to [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) correspond directly to severing private engine headers and uncoupling each internal subsystem into an independent target component: - -| Target Component | Eliminated Internal Engine Dependencies | Required Embedder C-API Additions | Architectural Decoupling Benefit | +| Target Component | Eliminated Internal Engine Dependencies | Embedder C-API Additions Used | Architectural Role | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), [`run_configuration.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/run_configuration.h))
[`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime) ([`dart_vm.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_vm.h), [`dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h))
[`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui) ([`platform_message.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/platform_message.h), [`callback_cache.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h), [`image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h))
[`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - `FlutterEngineInitialize` / `FlutterEngineRunInitialized`
- [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app-flutterenginespawn) (`FlutterEngineSpawnInfo`)
- `FlutterProjectArgs.enable_direct_platform_message_dispatch`
- `FlutterProjectArgs.request_dart_deferred_library_callback`, `FlutterEngineLoadDartDeferredLibrary` & `FlutterEngineNotifyDartDeferredLibraryLoadError`
- `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`)
- `FlutterEnginePrefetchDefaultFontManager` & `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriCallback`) | Holds only an opaque handle [`FLUTTER_API_SYMBOL(FlutterEngine)`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) invoked via `FlutterEngineProcTable` (`FLUTTER_ENGINE_NO_PROTOTYPES`). Completely eliminates `AndroidShellHolder` and `PlatformViewAndroid`. | -| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`rasterizer.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/rasterizer.h))
[`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller) (context setup headers)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) ([`vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h)) | - `kDeferred` ([`FlutterDeferredRendererConfig`](#deferred-backend-selection-kdeferred), `FlutterRendererResolveInfo`)
- [`FlutterCompositor.avoid_backing_store_cache`](#window-ownership-contract) window ownership contract
- [`FlutterEngineNotifyLowMemoryWarning`](#releasing-transient-rendering-caches-flutterenginenotifylowmemorywarning) (extended `Rasterizer::NotifyLowMemoryWarning()`)
- [`FlutterEngineAddView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3035-L3037) & [`FlutterEngineRemoveView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3062)
- `FlutterOpenGLRendererConfig` & `FlutterVulkanRendererConfig` | Replaces protected surface methods on `PlatformViewAndroid`. Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` for multi-window support, and releases each view's window on `surfaceDestroyed` without an engine round trip. | -| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h), [`surface.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/surface.h))
[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) ([`raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)) | - `FlutterCompositor` (`create_backing_store_callback`, `present_view_callback`, `avoid_backing_store_cache`, `platform_view_mutation_capabilities`, [`merge_raster_thread_on_platform_views`](#opt-in-dynamic-raster-thread-merging-for-hybrid-composition-merge_raster_thread_on_platform_views))
- `FlutterBackingStoreConfig.is_root_layer`
- `FlutterPlatformView` (`mutations`)
- `FlutterPlatformViewMutation`, `FlutterPlatformViewMutationCapability`, and `FlutterPath` | Unifies platform view composition behind `FlutterCompositor` without subclassing `flutter::ExternalViewEmbedder`. Delegates dynamic raster thread merging for HC to `EmbedderExternalViewEmbedder`, captures per-`FlutterViewId` frame state by value, and eliminates `external_view_embedder/` from `shell/platform/android/`. | -| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) ([`texture.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/texture.h), [`gl_context_switch.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics/gl_context_switch.h)) | - [`FlutterVulkanExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`ycbcr_conversion_info`), [`FlutterVulkanYcbcrConversionInfo`](#external-textures-zero-copy-vulkan-and-opengl-es), and [`FlutterVulkanTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterOpenGLExternalTexture`](#external-textures-zero-copy-vulkan-and-opengl-es) (`uv_transform`) and [`FlutterOpenGLTextureFrameCallback`](#external-textures-zero-copy-vulkan-and-opengl-es)
- [`FlutterEngineMarkExternalTextureFrameAvailable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3262-L3264) | Eliminates subclassing of internal C++ `flutter::Texture`. Supports zero-copy `AHardwareBuffer` import into `VkImage` (Vulkan) and `EGLImageKHR` (OpenGL ES) as well as UV transformation matrices for `SurfaceTexture` camera and video streams. | -| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) ([`flow/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h))
[`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1730-L1783) in-place extension (`FlutterSemanticsRole`, `hit_test_child_count`)
- `FlutterWindowMetricsEvent` in-place extension (`FlutterDisplayFeature`, `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`) | Delivers complete accessibility semantics metadata to Java `AccessibilityBridge` and foldable/edge-to-edge metrics to the engine without coupling to internal Flow or `lib/ui` headers. | -| **[`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)** | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) ([`asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h)) | - `FlutterProjectArgs.custom_asset_resolvers`
- `FlutterCustomAssetResolver` & `FlutterAssetMapping` | Decouples APK asset loading from internal `flutter::AssetResolver` C++ virtual tables while preserving zero-copy NDK `AAssetManager` memory mapping across hot restarts and split APK installs. | -| **`AndroidVsyncWaiter`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) ([`vsync_waiter.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/vsync_waiter.h#L24)) | None (uses existing [`FlutterProjectArgs.vsync_callback`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2678) and [`FlutterEngineOnVsync`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3366-L3369)) | Replaces internal inheritance from `flutter::VsyncWaiter`. Interacts with Android `AChoreographer` and Java `Choreographer` to forward vsync frame start and target timestamps to `FlutterEngineOnVsync`. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app) (`FlutterEngineSpawnInfo`)
- [`FlutterProjectArgs.enable_direct_platform_message_dispatch`](#platform-channel-messaging-background-handlers-and-task-queues)
- [`FlutterProjectArgs.request_dart_deferred_library_callback`](#dart-deferred-library-loading), `FlutterEngineLoadDartDeferredLibrary`, `FlutterEngineNotifyDartDeferredLibraryLoadError`
- [`FlutterProjectArgs.image_generators`](#image-generators-fonts-and-vm-uri) (`FlutterImageGeneratorRegistration`), `FlutterEnginePrefetchDefaultFontManager`, `FlutterProjectArgs.vm_service_uri_callback` | Owns the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and `FlutterEngineProcTable`; replaces `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | - [`kDeferred`](#initialization) (`FlutterDeferredRendererConfig`, `FlutterRendererResolveInfo`) | Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` and resolves the renderer asynchronously during startup. | +| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | - [`FlutterCompositor.merge_raster_thread_on_platform_views`](#platform-views) and `FlutterBackingStoreConfig.is_root_layer`
- [`FlutterCompositor.platform_view_mutation_capabilities`](#platform-view-mutator-stack-serialization), `FlutterPlatformView.mutations`, `FlutterPlatformViewMutation`, `FlutterPath` | Unifies HC, HCPP (`SurfaceControl`), TLHC, and VD behind `FlutterCompositor` and deletes `external_view_embedder/`. | +| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) | - [`FlutterVulkanExternalTexture`](#external-textures) (`ycbcr_conversion_info`), `FlutterVulkanYcbcrConversionInfo`, `FlutterVulkanTextureFrameCallback`
- [`FlutterOpenGLExternalTexture`](#external-textures) (`uv_transform`), `FlutterOpenGLTextureFrameCallback` | Imports zero-copy `AHardwareBuffer` (`VkImage` / `EGLImageKHR`) and `SurfaceTexture` UV transforms without subclassing `flutter::Texture`. | +| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](#accessibility-and-semantics-parity) (`role`, `hit_test_child_count`, `FlutterSemanticsRole`)
- [`FlutterWindowMetricsEvent`](#viewport-metrics-and-non-linear-font-scaling) (`FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, `FlutterDisplayFeature`) | Serializes semantics trees for Java `AccessibilityBridge` and packs insets, cutouts, and foldable features into `FlutterWindowMetricsEvent`. | +| **[`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)** | [`//flutter/assets/asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h) | - [`FlutterProjectArgs.custom_asset_resolvers`](#custom-asset-and-kernel-resolution), `FlutterCustomAssetResolver`, `FlutterAssetMapping` | Maps APK and split-AAB assets via NDK `AAssetManager` without subclassing `flutter::AssetResolver`. | ## Phased Implementation Strategy -To maintain continuous test coverage and avoid architectural traps, the migration is structured into four phases—with Phase 1 (`shell/platform/embedder/`) and Phase 2 (`shell/platform/android/` modularization and GN partitioning) executing in parallel before converging at Phase 3: +Phase 1 (`shell/platform/embedder/`) and Phase 2 (`shell/platform/android/` modularization and GN partitioning) execute in parallel before converging at Phase 3: ```mermaid flowchart LR @@ -1891,37 +1196,19 @@ flowchart LR Phase3 --> Phase4 ``` -### Phase 1: Embedder C-API Extensions ([`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h)) - -All C-API extensions specified in Detailed Design are implemented in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) and [`embedder.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.cc). - -Each new API is validated with automated engine unit tests ([`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390)) using mock platform delegates. No Android-specific files are modified in Phase 1. - -Phase 1 also includes the engine changes those APIs depend on: - -- Renderer resolution in `PlatformViewEmbedder::SetupImpellerContext()`, including the retry and Impeller's known-bad driver check for `kDeferred` configurations. -- Non-blocking rendering surface creation in `PlatformViewEmbedder::NotifyCreated()` (`Shell::OnPlatformViewCreatedAsync()`). -- Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) in `EmbedderExternalViewEmbedder` (`SupportsDynamicThreadMerging()` and `PostPrerollAction()`). -- Impeller rendering into Vulkan backing stores, which every Vulkan `FlutterCompositor` path on Android needs and `embedder.cc` doesn't implement yet ([#187525](https://github.com/flutter/flutter/issues/187525)). +### Phase 1: Embedder C-API Extensions -Because Phase 1 modifies only `shell/platform/embedder/` and `embedder_unittests`, it proceeds in parallel with Phase 2's in-place modularization of `shell/platform/android/`, and both converge before Phase 3 cutover. +Implements all C-API extensions and `shell/platform/embedder/` engine changes, validated by [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) with zero modifications to `shell/platform/android/`: -### Phase 2: Android Shell Modularization and GN Target Partitioning +- `kDeferred` renderer resolution (`PlatformViewEmbedder::SetupImpellerContext()` with fallback retry and Impeller's bad-driver blocklist check) and non-blocking surface initialization (`PlatformViewEmbedder::NotifyCreated()` -> `Shell::OnPlatformViewCreatedAsync()`). +- Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) in `EmbedderExternalViewEmbedder` and Impeller Vulkan backing store presentation ([#187525](https://github.com/flutter/flutter/issues/187525)). +- Platform view mutator serialization (`FlutterPlatformViewMutation`), external texture callbacks (`FlutterVulkanTextureFrameCallback`, `FlutterOpenGLTextureFrameCallback`), `FlutterSemanticsNode2` and `FlutterWindowMetricsEvent` in-place extensions, deferred library callbacks, `FlutterCustomAssetResolver`, `FlutterEngineSpawn`, direct UI-thread platform message dispatch, and process-scoped utilities. -Phase 2 reorganizes the Android embedder C++ codebase in-place to establish clean component boundaries before enabling C-API cutover in Phase 3. In the existing architecture, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) are monolithic entities that conflate engine execution, platform message delivery, surface lifecycle management, external texture tracking, and platform view composition. +### Phases 2–4: GN Target Partitioning, C-API Cutover, and Legacy Class Purge -Phase 2 accomplishes two mandatory structural goals: - -1. It establishes a hard compile-time firewall in GN (`:flutter_embedder_native_src`) that isolates new C-API code from legacy internal engine headers and enforces `FLUTTER_ENGINE_NO_PROTOTYPES`. -2. It decomposes the monolithic [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) into focused modular components (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)) and decouples them from internal engine classes. - -#### Compile-Time Firewall via GN Target Partitioning - -To guarantee that new modular components cannot inadvertently include private engine headers (such as [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), or [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller)), [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates the new C-API implementation inside `source_set("flutter_embedder_native_src")`: +In Phase 2, [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all new C-API components inside `source_set("flutter_embedder_native_src")` with `check_includes = true` and `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`, blocking private engine headers at compile time: ```gn -# GN Target Partitioning in shell/platform/android/BUILD.gn: - # 1. Modular C-API Source Set (Compile-Time Firewall) source_set("flutter_embedder_native_src") { visibility = [ ":*" ] @@ -1960,7 +1247,6 @@ source_set("flutter_embedder_native_src") { "jvm_invoker.h", ] - # Strict dependencies: ONLY public embedder headers and foundational utilities deps = [ "//flutter/assets", "//flutter/common", @@ -1969,16 +1255,14 @@ source_set("flutter_embedder_native_src") { ] } -# 2. Outer Shell Source Set (Reduced to 3 files in Phase 4 once legacy classes are purged) +# 2. Outer Shell Source Set (Reduced to 3 files in Phase 4) source_set("flutter_shell_native_src") { visibility = [ ":*" ] - sources = [ "flutter_main.cc", "flutter_main.h", "library_loader.cc", ] - public_deps = [ ":flutter_embedder_native_src", "//flutter/fml", @@ -1990,169 +1274,83 @@ source_set("flutter_shell_native_src") { ```mermaid flowchart TD subgraph BuildTarget["Build Target: //flutter/shell/platform/android:flutter_shell_native"] - SharedLib["shared_library flutter_shell_native
libflutter.so"] - ShellSrc["source_set flutter_shell_native_src
flutter_main.cc, library_loader.cc"] + SharedLib["shared_library flutter_shell_native (libflutter.so)"] + ShellSrc["source_set flutter_shell_native_src (flutter_main.cc, library_loader.cc)"] end subgraph SourceSets["Partitioned Source Sets in BUILD.gn"] - CAPISourceSet["source_set flutter_embedder_native_src
defines = FLUTTER_ENGINE_NO_PROTOTYPES
----------------------------------------
flutter_embedder_native.cc
jni_router.cc / jni_delegate.h / jvm_invoker.cc
android_surface_control.cc
android_platform_views_controller.cc
android_hardware_buffer.cc / android_vulkan_external_texture.cc
android_semantics_mapper.cc / android_window_metrics_mapper.cc"] - LegacySourceSet["Transitional Phase 2-3 Legacy Files (Deleted in Phase 4)
----------------------------------------
android_shell_holder.cc
platform_view_android.cc / platform_view_android_jni_impl.cc
shell_android_engine.cc / embedder_android_engine.cc
external_view_embedder/"] + CAPISourceSet["source_set flutter_embedder_native_src
check_includes = true, FLUTTER_ENGINE_NO_PROTOTYPES
----------------------------------------
flutter_embedder_native.*, jni_router.*, jni_delegate.h, jvm_invoker.*
android_surface_control.*, android_platform_views_controller.*, android_mutators_mapper.*
android_hardware_buffer.*, android_vulkan_external_texture.*
android_semantics_mapper.*, android_window_metrics_mapper.*
android_vsync_waiter.*, android_vm_init.*, android_engine_group.*, apk_asset_provider.*"] + LegacySourceSet["Transitional Phase 2-3 Legacy Files (Deleted in Phase 4)
----------------------------------------
android_shell_holder.*, platform_view_android.*, platform_view_android_jni_impl.*
shell_android_engine.*, embedder_android_engine.*, external_view_embedder/"] end - subgraph AllowedCAPIDeps["Allowed Dependencies: Strict Firewall"] - EmbedderHeaders["//flutter/shell/platform/embedder:embedder_headers"] - EmbedderLib["//flutter/shell/platform/embedder:embedder_as_internal_library"] + subgraph AllowedCAPIDeps["Allowed Dependencies"] + EmbedderHeaders["//flutter/shell/platform/embedder:embedder_headers & embedder_as_internal_library"] FML["//flutter/fml, //flutter/common, //flutter/assets"] end - subgraph ForbiddenCAPIDeps["Forbidden Dependencies: Blocked by GN check_includes"] - Flow["//flutter/flow: Flow Layers, MutatorsStack, ExternalViewEmbedder"] - Runtime["//flutter/runtime: Dart VM, Isolates, Service Isolate"] - ShellCommon["//flutter/shell/common: Shell, Rasterizer, PlatformView"] - SkiaImpeller["//flutter/skia, //flutter/impeller, //flutter/lib/ui, //flutter/txt"] + subgraph ForbiddenCAPIDeps["Forbidden Internal Dependencies (Blocked by GN check_includes)"] + Internal["//flutter/flow, //flutter/runtime, //flutter/shell/common,
//flutter/skia, //flutter/impeller, //flutter/lib/ui, //flutter/txt"] end SharedLib --> ShellSrc ShellSrc --> CAPISourceSet ShellSrc -.->|Phases 2-3 Only| LegacySourceSet - CAPISourceSet --> AllowedCAPIDeps CAPISourceSet -.->|COMPILE ERROR if included| ForbiddenCAPIDeps - LegacySourceSet --> ForbiddenCAPIDeps ``` -GN enforces `check_includes = true` and `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` on `flutter_embedder_native_src`. If any file in `flutter_embedder_native_src` attempts to include a private engine header (such as [`platform_view.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h), [`shell.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h), or [`embedded_views.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h)) or call an `embedder.h` function outside `FlutterEngineProcTable`, the build fails immediately. - -#### Elimination and Decomposition of `AndroidShellHolder` and `PlatformViewAndroid` - -In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) directly [subclasses the internal engine class `flutter::PlatformView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L151-L160). This inheritance models Android as an internal engine subsystem rather than an external client embedder. Both `AndroidShellHolder` and `PlatformViewAndroid` are eliminated by decomposing their responsibilities across `FlutterEmbedderNative` and the modular components in `:flutter_embedder_native_src`. - -In contrast, external embedders communicating via [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) never subclass `flutter::PlatformView`. Instead, the embedder library ([`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206)) internally instantiates a generic [`flutter::PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) to satisfy the [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) class contract. - -#### Lifecycle and Deletion of Legacy Classes (`AndroidShellHolder` and `PlatformViewAndroid`) - -[`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) progress across four migration stages culminating in their complete removal: - ```mermaid flowchart TD - Pre["Current State: Monolithic Production
AndroidShellHolder & PlatformViewAndroid active in flutter_shell_native_src.
All CI and presubmits exercise this path."] - - P2["Phase 2: Target Partitioning and Modularization
Modular C-API components built in flutter_embedder_native_src.
Transitional AndroidEngine seam bridges AndroidShellHolder during migration."] + Pre["Pre-Migration: Monolithic Production
AndroidShellHolder & PlatformViewAndroid active in flutter_shell_native_src (98 internal headers)."] + P2["Phase 2: Partitioning & Strangler-Fig Seam
Modular C-API components built in flutter_embedder_native_src.
Transitional AndroidEngine (ShellAndroidEngine / EmbedderAndroidEngine) bridges AndroidShellHolder."] + P3["Phase 3: C-API Cutover
FlutterEmbedderNative::RegisterJni(env) owns FlutterJNI bindings and drives FlutterEngineProcTable.
Core engine runs PlatformViewEmbedder and EmbedderExternalViewEmbedder."] + P4["Phase 4: Legacy Class Purge & Build Decoupling
Delete AndroidShellHolder, PlatformViewAndroid, PlatformViewAndroidJNIImpl, AndroidEngine, VsyncWaiterAndroid,
PlatformMessageHandlerAndroid, SurfaceTextureExternalTexture, ImageExternalTexture, plus external_view_embedder/.
Prune internal GN deps and lock CI header ratchet to 0."] - P3["Phase 3: Genuine C-API Cutover
FlutterEmbedderNative registers JNI bindings directly.
Internal PlatformViewEmbedder handles engine shell duties."] - - P4["Phase 4: Legacy Class Purge and GN Decoupling
git rm android_shell_holder.* platform_view_android* external_view_embedder/
flutter_shell_native_src reduced to flutter_main.cc & library_loader.cc."] - - Pre --> P2 - P2 --> P3 - P3 --> P4 + Pre --> P2 --> P3 --> P4 ``` -1. **Pre-Migration**: [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) handle 100% of production traffic and link to 98 internal engine headers. -2. **Phase 2 (Partitioning & Strangler-Fig Seam)**: Modular components are constructed inside `flutter_embedder_native_src` with `check_includes = true` and `FLUTTER_ENGINE_NO_PROTOTYPES`. During incremental slice development, a transitional polymorphic delegate interface (`AndroidEngine`, implemented by `ShellAndroidEngine` and `EmbedderAndroidEngine`) allows individual subsystems to be migrated and tested against [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) without breaking existing [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L42-L67). -3. **Phase 3 (C-API Cutover)**: `FlutterEmbedderNative` assumes direct ownership of the [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108) native bindings (`FlutterEmbedderNative::RegisterJni(env)`) and drives the engine exclusively through [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827). Inside the core engine, the embedder runtime uses [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and `EmbedderExternalViewEmbedder` to fulfill the shell's internal requirements. -4. **Phase 4 (Legacy Class Purge & Build Decoupling)**: Once `FlutterEmbedderNative` and `flutter_embedder_native_unittests` reach full parity, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), `AndroidEngine`, `ShellAndroidEngine`, `EmbedderAndroidEngine`, and [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) are permanently deleted from the repository (`legacy-class-purge`). [`:flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) is reduced to just three files ([`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc), [`flutter_main.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17), and [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12)), and the Android embedder retains zero subclassing of internal engine classes. - -### Phase 3: Genuine C-API Cutover (`FlutterEmbedderNative`) - -In Phase 3, `FlutterEmbedderNative` initializes [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006) through `FlutterEngineProcTable`. - -Event dispatch routes directly to public C-API entry points: - -- Touch events: [`FlutterEngineSendPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3082) -- Viewport metrics: [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077) -- Platform messages: [`FlutterEngineSendPlatformMessage`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3114) -- Semantics actions: [`FlutterEngineSendSemanticsAction`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3330-L3336) -- Texture registrations: [`FlutterEngineRegisterExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3227) -- Compositor and multi-surface presentation: [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) layer presentation (`present_view_callback`) coordinating [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) commits via `AndroidSurfaceControl` and `AndroidPlatformViewsController`. - -#### Transitional Polymorphic Delegation via `AndroidEngine` (Phases 2–3) - -During Phases 2 and 3, before [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) is deleted in Phase 4, incremental subsystem migration is bridged using the transitional `AndroidEngine` seam: - -1. **`ShellAndroidEngine` (Legacy Path)**: - - Directs lifecycle and events to internal engine components: [`flutter::Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), and [`fml::ThreadHost`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/thread_host.h#L21). - - Retains private engine dependencies ([`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller)) until Phase 4 deletion. - -2. **`EmbedderAndroidEngine` / `FlutterEmbedderNative` (C-API Target Path)**: - - Encapsulates the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827). - - Forwards all operations strictly through the public C-API in [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). - - Once `FlutterEmbedderNative` registers JNI directly in [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12), both [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and the transitional `AndroidEngine` classes become dead code and are removed in Phase 4. - -### Phase 4: Build Decoupling and Legacy Class Purge - -Once `FlutterEmbedderNative` passes all functional and performance tests, [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) and `shell/platform/android/` are permanently decoupled: - -- Delete [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), `AndroidEngine`, `EmbedderAndroidEngine`, `ShellAndroidEngine`, [`VsyncWaiterAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/vsync_waiter_android.h#L19), [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18), [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33), [`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36), and the [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) directory. -- Reduce [`:flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) to [`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc), [`flutter_main.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17), and [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12) (which calls `FlutterEmbedderNative::RegisterJni(env)`). -- Remove dependencies on [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common). -- Restrict dependencies strictly to: - - [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210) and [`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206) (with `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`) - - [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) - - [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) - - [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) -- Lock the CI ratchet tool to zero allowed internal headers. - ### The Dependency Ratchet Mechanism -In a large codebase with active development, multi-quarter refactoring efforts face persistent regression risks: developers resolving defects or introducing features may inadvertently add new `#include` directives referencing private engine headers, or introduce `// nogncheck` annotations to bypass visibility boundaries. - -To enforce that dependency reduction is strictly monotonic throughout the migration, an automated ratchet system operates across three distinct layers: +> [!IMPORTANT] +> To guarantee monotonic removal of the 98 internal headers across Phases 2–4 without regressions or `// nogncheck` bypasses, CI enforces a three-layer ratchet driven by `check_android_embedder_deps.py` and `shell/platform/android/allowed_internal_headers.yaml`. ```mermaid flowchart TD - subgraph Layer1["Layer 1: GN Target Partitioning - Compile-Time Firewall"] - NewCAPI["flutter_embedder_native_src
FlutterEmbedderNative, AndroidSurfaceControl, etc."] - StrictDeps["Strict deps only:
embedder_headers, fml, common, assets"] - LegacyTarget["Transitional Legacy Files (Phases 2-3)
AndroidShellHolder, PlatformViewAndroid"] - LegacyDeps["Legacy internal deps:
//flutter/flow, //flutter/runtime, etc."] - + subgraph Layer1["Layer 1: GN Target Partitioning (Compile-Time Firewall)"] + NewCAPI["flutter_embedder_native_src (check_includes = true, FLUTTER_ENGINE_NO_PROTOTYPES)"] + StrictDeps["Only embedder_headers, fml, common, assets allowed"] NewCAPI --> StrictDeps - LegacyTarget --> LegacyDeps end - subgraph Layer2["Layer 2: CI Presubmit Linter - Monotonic Header Ratchet"] - PR["Pull Request touching shell/platform/android/"] - Script["check_android_embedder_deps.py"] - YAML["allowed_internal_headers.yaml
Tracked baseline of 98 headers"] - Check1{"New internal header
or unlisted file?"} - Check2{"Include removed without
decrementing baseline?"} - Reject1["Reject PR: New internal header forbidden"] - Reject2["Reject PR: Ratchet down baseline in YAML"] + subgraph Layer2["Layer 2: CI Presubmit Linter (check_android_embedder_deps.py)"] + PR["PR touching shell/platform/android/"] + YAML["allowed_internal_headers.yaml (Starts at 98 headers)"] + Check1{"Unlisted file, new internal #include,
// nogncheck, or widened visibility?"} + Check2{"Internal #include removed without
decrementing YAML baseline?"} + Reject1["Fail Presubmit"] + Reject2["Fail Presubmit: Ratchet down YAML"] Accept["Pass Presubmit"] - PR --> Script - YAML --> Script - Script --> Check1 + PR --> Check1 + YAML --> Check1 Check1 -- Yes --> Reject1 Check1 -- No --> Check2 Check2 -- Yes --> Reject2 Check2 -- No --> Accept end - subgraph Layer3["Layer 3: Terminal Build Decoupling - Permanent Lock"] - DeleteLegacy["Purge legacy classes & external_view_embedder/"] - PruneDeps["Prune BUILD.gn to pure embedder targets"] - ZeroTolerance["Lock CI check to total_allowed_internal_headers: 0"] - - DeleteLegacy --> PruneDeps --> ZeroTolerance + subgraph Layer3["Layer 3: Phase 4 Terminal Lock"] + Purge["Delete legacy classes & prune internal BUILD.gn deps"] + Zero["Lock allowed_internal_headers.yaml to total_allowed_internal_headers: 0"] + Purge --> Zero end Layer1 --> Layer2 --> Layer3 ``` -#### Layer 1: GN Target Partitioning (Compile-Time Firewall) - -In Phase 2, the Android native source set in [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all C-API code in `flutter_embedder_native_src` (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and `APKAssetProvider`). This target sets `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]` and declares dependencies strictly on [`//flutter/shell/platform/embedder:embedder_headers`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L210), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml), [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common), and [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets). Because GN evaluates `check_includes = true` during builds, any attempt to include headers from undeclared targets (such as [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow) or [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime)) causes a compilation error immediately. - -#### Layer 2: Monotonic Header Inclusion Ratchet ([`check_android_embedder_deps.py`](#the-dependency-ratchet-mechanism)) - -To prevent the quarantined legacy files from accumulating additional internal includes during Phase 2 and Phase 3, an automated presubmit linter enforces a monotonic reduction policy. - -The linter references a version-controlled baseline configuration file located at [`shell/platform/android/allowed_internal_headers.yaml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android): - ```yaml +# shell/platform/android/allowed_internal_headers.yaml (ratcheted from 98 to 0 in Phase 4) version: 1 total_allowed_internal_headers: 98 subsystems: @@ -2175,54 +1373,15 @@ subsystems: - "flutter/shell/common/platform_view.h" ``` -The presubmit linter enforces four deterministic rules on incoming pull requests: - -1. *Zero new files*: Only source files explicitly registered in [`allowed_internal_headers.yaml`](#the-dependency-ratchet-mechanism) may contain internal engine includes. Any inclusion in an unlisted file fails presubmit. -2. *Zero new headers*: New `#include "flutter/..."` directives not in the allowlist fail presubmit, requiring the API to be routed through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h). -3. *Monotonic decrement*: When an incremental refactoring removes an internal header, the pull request must remove it from `allowed_internal_headers.yaml` and decrement `total_allowed_internal_headers`. Once merged, the ratchet locks the lower count permanently. -4. *No escape hatches*: The linter parses [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) and rejects modifications introducing `// nogncheck` or widening GN target visibility. - -#### Layer 3: Terminal Build Decoupling (Permanent Lock) - -Following Phase 3 cutover validation: - -1. All legacy classes ([`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder)) are deleted from the repository. -2. The allowlist configuration is locked permanently: - - ```yaml - total_allowed_internal_headers: 0 - subsystems: {} - ``` - -3. All internal GN dependencies ([`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common)) are pruned from [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn). -4. Any future pull request including an internal header will fail both `gn check` at compile time and the presubmit ratchet script, which permanently prevents architectural regressions. - ## Verification and Testing Plan -Because Android is Flutter's largest mobile deployment target, verification must confirm that behavioral correctness, performance benchmarks, and accessibility support match the existing embedder. - -### Engine Unit Tests (`flutter_embedder_native_unittests`) - -- Host-executable C++ unit tests (`flutter_embedder_native_unittests`) for `FlutterEmbedderNative` and each modularized component (`AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, and `AndroidEngineGroup`) using mock [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827) entries and injectable `JniDelegate` / `JvmInvoker` provider interfaces without requiring a live Android JVM or device. -- [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) cases for the engine behavior this RFC adds: `kDeferred` resolution and retry (a rejected Vulkan configuration leads to a second call that returns OpenGL ES), `FlutterEngineRunInitialized` returning while the resolve callback is still running, and opt-in dynamic raster thread merging (`merge_raster_thread_on_platform_views`). -- Unit tests validating the serialization and deserialization of [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) structs and per-`FlutterViewId` frame state capture. - -### Android Integration Tests - -- [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L42-L67) and `flutter_embedder_native_unittests` executed across the CI test matrix. -- Platform view lifecycle and composition tests verifying Hybrid Composition++ (HCPP with [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) on API 34+), legacy Hybrid Composition (HC), Texture Layer Hybrid Composition (TLHC), and Virtual Displays (VD) under both flag configurations. -- Android embedding integration tests verifying JNI message channels, background isolate callbacks, and deferred library loading. -- Activity stop/start and configuration-change loops with HC and HCPP on Vulkan and OpenGL ES, checking that nothing renders into a window after `surfaceDestroyed()` returns and that rendering resumes after `surfaceCreated()`. - -### DeviceLab Benchmarks +### Engine Unit Tests -- Frame pacing and rasterization latency benchmarks (comparing legacy vs C-API) to verify that the C-ABI abstraction does not introduce frame drops or scheduling jitter. -- Platform view scrolling benchmarks across HCPP and HC modes to measure GPU sync fence latency, [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) submission timing, and touch response. -- Startup latency benchmarks on low-end and high-end devices to verify that [`FlutterAssetResolver`](#custom-asset-and-kernel-resolution-apk--in-memory-mapping) memory mapping maintains zero-copy asset loading performance. -- Cold-start traces on low-end and high-end devices that compare when `CreateImpellerContext` runs and the time to first frame, legacy versus C-API, to check that resolving the renderer at `runBundleAndSnapshotFromLibrary()` doesn't delay the first frame. -- Memory after `surfaceDestroyed()`, legacy (`NotifyDestroyed`) versus C-API (`FlutterEngineNotifyLowMemoryWarning`), to check that a hidden engine releases the same GPU memory. +- Host-executable C++ unit tests (`flutter_embedder_native_unittests`) test `FlutterEmbedderNative` and each modular component using mock [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827) entries and injectable `JniDelegate` / `JvmInvoker` providers without an Android JVM or device. +- [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) cover `kDeferred` renderer resolution and fallback retry, non-blocking `FlutterEngineRunInitialized` while `resolve_renderer_callback` runs on the raster thread, opt-in dynamic raster thread merging (`merge_raster_thread_on_platform_views`), [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) serialization, and per-`FlutterViewId` frame state capture. -### Golden and Conformance Tests +### Android Integration and DeviceLab Tests -- Platform view rendering goldens validating that clipping paths, rounded rectangles, and opacity mutators render identically across both compositor modes (HCPP with [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) Vulkan and HC) and external-texture modes (TLHC and VD). -- External texture playback verification across video player and camera plugins. +- Run [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L42-L67), platform view composition tests across all four Android composition modes (HCPP, HC, TLHC, VD), activity stop/start and configuration-change loops (`surfaceDestroyed()` / `surfaceCreated()`), JNI platform channels, background isolate callbacks, and deferred library loading. +- Verify with golden tests that clip paths, rounded rectangles, and opacity mutators render identically across all four composition modes (HCPP, HC, TLHC, VD), and validate external texture playback across camera and video player plugins. +- Compare legacy vs C-API DeviceLab benchmarks for frame pacing, rasterization latency, HCPP/HC scrolling and fence timing, [`FlutterCustomAssetResolver`](#custom-asset-and-kernel-resolution) startup latency, cold-start time-to-first-frame (`CreateImpellerContext`), and post-`surfaceDestroyed()` GPU memory reclamation (`FlutterEngineNotifyLowMemoryWarning`). From 3ff3759eca0536887756b48a3f8a76b7b1c5e3af Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 21 Oct 2026 18:26:56 +0000 Subject: [PATCH 32/43] Add back some context (but keep it hidden). --- ...lutter-android-embedder-c-api-migration.md | 87 +++++++++++++++---- 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 0780c0b..f42c238 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -121,11 +121,8 @@ sequenceDiagram FlutterJNI->>PV: SurfaceCreated(ANativeWindow) -> NotifyCreated (runs on Raster after SetupImpellerContext) ``` -### Dependency Entanglement in [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) - -[`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) includes 98 internal engine headers across 9 subsystems: -
+shell/platform/android/ includes 98 internal engine headers across 9 subsystems | Subsystem & Headers | Direct GN Dependencies | Key Internal Headers | Functional Purpose in Embedder | | :--- | :--- | :--- | :--- | @@ -283,6 +280,16 @@ sequenceDiagram > - Replaces [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) with [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322), using `is_root_layer` to distinguish the bottom-most background layer from full-screen overlay layers above a platform view. > - [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) implements `SupportsDynamicThreadMerging()` and `PostPrerollAction()` when `merge_raster_thread_on_platform_views` is `true`, dynamically merging the raster task runner onto the platform thread during Hybrid Composition (HC) frames. +
+Why is_root_layer and merge_raster_thread_on_platform_views are needed + +- **Root vs. overlay backing stores (`is_root_layer`)**: During `create_backing_store_callback(config, store_out)`, `config->size` equals the full window size for both the bottom-most background layer and any full-screen overlay layer above a platform view (`[Flutter_Root, PlatformView, Flutter_Overlay]`), and `present_view_callback` runs only after all layers are rendered. `is_root_layer` lets the embedder bind the bottom-most layer directly to the on-screen `ANativeWindow*` swapchain or FBO (`avoid_backing_store_cache = true`) while allocating off-screen overlay surfaces ([`SurfacePool`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h) / [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L38)) for layers above a platform view. +- **Dynamic thread merging (`merge_raster_thread_on_platform_views`)**: HCPP (`SurfaceControl`, API 34+ with Impeller Vulkan) is opt-in and `SurfaceView`-backed platform views (`VIEW_TYPES_REQUIRE_NON_TLHC`) cannot use TLHC or VD, so standard Hybrid Composition (HC) remains the default for non-TLHC views. In HC, [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L951-L1084) mutates live Android `View` instances (`FlutterMutatorView`, `FlutterImageView.convertToImageView()`) on the platform thread during frame presentation, which requires [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) to dynamically merge the raster task runner onto the platform thread. +
+ +
+Platform View Selection + ```mermaid flowchart TD Req["Platform View Request in PlatformViewsController"] --> IsHCPP{"enable_surface_control AND API 34+ AND Impeller Vulkan?"} @@ -294,6 +301,7 @@ flowchart TD HasSurfaceView -- "Yes, TEXTURE_WITH_VIRTUAL_FALLBACK" --> VD["VD: configureForVirtualDisplay (External Texture C-API)"] HasSurfaceView -- "Yes, TEXTURE_WITH_HYBRID_FALLBACK" --> HC ``` +
```c typedef struct { @@ -410,6 +418,12 @@ sequenceDiagram > - Arbitrary path clips serialize into an ABI-stable structure-of-arrays `FlutterPath` layout instead of exposing `SkPath` or `//flutter/flow` headers. > - On Android API 34+ (`android.graphics.Path.conicTo`), the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` to receive exact conic path verbs (`kFlutterPathVerbConic`), and `AndroidMutatorsMapper` translates `FlutterPlatformView.mutations` into Java [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) and [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) transforms. +
+Why FlutterPlatformViewMutation needs FlutterPath and capability negotiation + +[`EmbedderLayers::PushPlatformViewLayer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_layers.cc#L102-L103) currently drops `MutatorType::kClipPath` (`"Unsupported mutation"`), whereas Android's [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1834-L1855) and HCPP apply arbitrary path clips from [`flutter::MutatorsStack`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow/embedded_views.h#L211). Serializing `DlPath` into a structure-of-arrays `FlutterPath` (`verbs`, `points`, `conic_weights`, `bounds`) avoids exposing internal C++ path types across `embedder.h`, and `kFlutterPlatformViewMutationCapabilityPathConics` lets Android API 34+ (`Path.conicTo`) consume native conic verbs while older API levels receive quadratic Beziers converted by the engine. +
+ ```c typedef enum { kFlutterPlatformViewMutationTypeOpacity, @@ -506,6 +520,13 @@ typedef struct { > - **Embedder API additions (`embedder.h`)**: Adds `FlutterVulkanYcbcrConversionInfo` (`FlutterVulkanComponentMapping`, `FlutterVulkanComponentSwizzle`) and `FlutterVulkanExternalTexture.ycbcr_conversion_info` (extending [`FlutterVulkanExternalTexture` from #188855](https://github.com/flutter/flutter/pull/188855)); adds `FlutterOpenGLExternalTexture` (with `FlutterTransformation uv_transform`) and `FlutterOpenGLRendererConfig.external_texture_frame_callback` (`FlutterOpenGLTextureFrameCallback`, deprecating `gl_external_texture_frame_callback`). > - **Behavioral changes**: The Vulkan renderer samples multi-planar and vendor-specific external YCbCr camera and video frames (`VkSamplerYcbcrConversion`) without exposing Android `AHardwareBuffer` types in `embedder.h`, and the OpenGL ES renderer applies `SurfaceTexture.getTransformMatrix()` UV transforms while keeping [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) unchanged for backing store render targets. +
+Why FlutterVulkanYcbcrConversionInfo and FlutterOpenGLExternalTexture are needed + +- **Vulkan YCbCr conversion**: Android hardware video decoders and camera pipelines produce multi-planar or vendor-proprietary YCbCr `AHardwareBuffer` frames (`VK_FORMAT_UNDEFINED` with a non-zero `VkAndroidHardwareBufferFormatPropertiesANDROID.externalFormat`). Sampling those `VkImage` handles in Impeller Vulkan requires a `VkSamplerYcbcrConversion` configured with the buffer's chroma offsets, range, model, and `externalFormat`, which [`FlutterVulkanExternalTexture`](https://github.com/flutter/flutter/pull/188855) previously lacked. +- **OpenGL ES UV transformation**: On OpenGL ES, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float%5B%5D)) supplies a $4 \times 4$ UV crop and rotation matrix per frame, which [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) (a frozen struct without `struct_size` shared with `FlutterOpenGLBackingStore.texture`) cannot carry without breaking ABI compatibility. +
+ ```mermaid flowchart LR subgraph Producers["Android TextureRegistry Producers"] @@ -636,6 +657,13 @@ FlutterOpenGLTextureFrameCallback external_texture_frame_callback; > - **Embedder API additions (`embedder.h`)**: Adds `FlutterSemanticsRole` and appends nine node fields (`max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count`) in place to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783). > - **Behavioral changes**: `hit_test_child_count` separates the length of `children_in_hit_test_order` from `child_count` (`children_in_traversal_order`, which can differ when the framework grafts nodes such as `OverlayPortal`). `AndroidSemanticsMapper` consumes `FlutterSemanticsUpdate2` via `FlutterProjectArgs.update_semantics_callback2` to pack the direct `ByteBuffer` and string pool arrays expected by `AccessibilityBridge`, while accessibility actions route back through `FlutterEngineSendSemanticsAction`. +
+Why FlutterSemanticsNode2 is extended in place + +- **Missing node attributes**: Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L2614-L2720) reads `maxValueLength`, `currentValueLength`, `linkUrl`, `locale`, `minValue`, `maxValue`, `hitTestTransform`, and `role` from [`flutter::SemanticsNode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h#L101), which were omitted from [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783). +- **Separate child array counts (`hit_test_child_count`)**: [`EmbedderSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc#L96-L108) previously used `child_count` (`children_in_traversal_order.size()`) as the length of both child arrays, even though `children_in_hit_test_order` can have a different length when the framework grafts nodes such as `OverlayPortal`. +
+ ```c typedef enum { kFlutterSemanticsRoleNone = 0, @@ -699,6 +727,12 @@ size_t hit_test_child_count; > - **Embedder API additions (`embedder.h`)**: Adds `FlutterRequestDartDeferredLibraryCallback` (`FlutterProjectArgs.request_dart_deferred_library_callback`), `FlutterDartDeferredLibrary` (with `destruction_callback`), `FlutterEngineLoadDartDeferredLibrary`, and `FlutterEngineNotifyDartDeferredLibraryLoadError` (`FlutterDartDeferredLibraryLoadError`). > - **Behavioral changes**: The engine invokes `request_dart_deferred_library_callback` on the platform task runner when Dart requests a `loading_unit_id`, wraps the embedder's `snapshot_data` and `snapshot_instructions` buffers in a shared-ownership `fml::NonOwnedMapping`, and fires `destruction_callback` once the Dart VM releases both buffers. +
+Why deferred library callbacks and destruction_callback are needed + +When Dart executes `loadLibrary()` on a deferred import, the engine calls `PlatformView::RequestDartDeferredLibrary(loading_unit_id)`, which [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36) did not expose in `embedder.h`. Once Play Feature Delivery installs the split APK and [`FlutterJNI.loadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1395-L1486) resolves the `_kDartIsolateSnapshotData` and `_kDartIsolateSnapshotInstructions` symbols, the Dart VM registers those pages directly in the isolate group heap without copying them, so `FlutterDartDeferredLibrary.destruction_callback` notifies the embedder when the engine releases both mappings. +
+ ```c /// Callback invoked by the engine when Dart requests loading of a deferred /// library / loading unit. @@ -800,6 +834,12 @@ sequenceDiagram > - **Embedder API additions (`embedder.h`)**: Adds `FlutterAssetMapping`, `FlutterCustomAssetResolver` (`FlutterAssetResolverGetAsMappingCallback`), and `FlutterProjectArgs.custom_asset_resolvers` (`custom_asset_resolvers_count`). > - **Behavioral changes**: The engine's internal `CustomAssetResolver` wrapper returns `true` from `IsValidAfterAssetManagerChange()` so custom resolvers stay attached across hot reload and hot restart. [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) supplies a `FlutterCustomAssetResolver` that maps assets directly from the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) in `get_as_mapping_callback` and atomically swaps its `AAssetManager*` pointer inside `user_data` when `FlutterJNI.updateJavaAssetManager` runs after a split APK install. +
+Why FlutterCustomAssetResolver is needed + +`FlutterProjectArgs.assets_path` requires an uncompressed directory on disk (`DirectoryAssetBundle`), whereas Android packages Flutter assets inside APK and AAB archives accessed via the NDK `AAssetManager`. Adding `FlutterCustomAssetResolver` to `embedder.h` lets [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) resolve assets through a C callback without subclassing `flutter::AssetResolver` or extracting APK assets to disk on first launch. +
+ ```c typedef struct { size_t struct_size; @@ -834,6 +874,12 @@ const FlutterCustomAssetResolver** custom_asset_resolvers; > - **Embedder API additions (`embedder.h`)**: Adds `FlutterEngineSpawn` and `FlutterEngineSpawnInfo`. > - **Behavioral changes**: `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285), spawning child `FlutterEngine` instances that share the parent engine's Dart VM isolate group, `io_manager`, and resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) (including when the parent engine initialized with `type = kDeferred` or when `renderer_config` is `nullptr`). +
+Why FlutterEngineSpawn is needed + +[`FlutterEngineGroup.createAndRunEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) relies on [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) so secondary engines share the parent engine's Dart VM isolate group heap, `io_manager`, and GPU context (~180 KB per spawned engine vs. ~15–20 MB for an independent `FlutterEngineInitialize` call). Exposing `FlutterEngineSpawn` in `embedder.h` preserves that sharing without calling `Shell::Spawn` directly. +
+ ```c typedef struct { size_t struct_size; @@ -862,6 +908,14 @@ FlutterEngineResult FlutterEngineSpawn( > - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`, `FlutterImageGenerator`, `FlutterImageGeneratorInfo`), `FlutterEnginePrefetchDefaultFontManager`, and `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`). > - **Behavioral changes**: The Android embedder registers its fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats at priority `-1`) without subclassing `flutter::ImageGenerator`, and the engine keeps the encoded byte buffer alive until `destruction_callback` runs. `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists, and `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. +
+Why these three C-APIs are needed + +- [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L166-L171) registers [`AndroidImageGenerator`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_image_generator.h#L18) via `Shell::RegisterImageDecoder` so Android's NDK `AImageDecoder` can decode HEIF and platform image formats not handled by built-in engine codecs. +- [`FlutterMain::PrefetchDefaultFontManager`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L211-L214) calls `txt::GetDefaultFontManager()` (`//flutter/txt`) during `FlutterLoader.initAsync` before any `FlutterEngine` instance exists. +- [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L186-L195) includes `//flutter/runtime/dart_service_isolate.h` directly to register a callback for `FlutterJNI.getVMServiceUri()`. +
+ ```c typedef enum { kFlutterImageGeneratorColorTypeRGBA8888, @@ -940,6 +994,12 @@ FlutterVMServiceUriCallback vm_service_uri_callback; > - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.enable_direct_platform_message_dispatch` (`bool`, defaulting to `false`). > - **Behavioral changes**: When `enable_direct_platform_message_dispatch` is `true`, [`EmbedderPlatformMessageHandler`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) invokes `platform_message_callback` directly on the calling isolate's thread (and returns `false` from `DoesHandlePlatformMessageOnPlatformThread()`), so root-isolate messages avoid an extra main-`Looper` post and background-isolate messages route directly to [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) executors without bouncing through the Android main thread. +
+Why enable_direct_platform_message_dispatch is needed + +By default, [`EmbedderPlatformMessageHandler`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) posts every Dart-to-platform message onto the platform thread before invoking `platform_message_callback`. On Android, where the UI and platform threads are merged (`MergedPlatformUIThread::kEnabled`) and [`DartMessenger.dispatchMessageToQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L279-L315) already routes each channel to either the main `Looper` or a background `TaskQueue` executor, that unconditional platform-thread post adds a redundant `Looper` hop for root-isolate messages and forces background-isolate messages destined for a background `TaskQueue` to wait on the Android UI thread first. +
+ ```mermaid flowchart LR subgraph Engine["Engine Isolate Threads"] @@ -988,6 +1048,13 @@ flowchart LR > - [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433) populates the extended [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) and dispatches it via [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077); `embedder.cc` reads the new fields using `SAFE_ACCESS(event, member, default_value)`. > - Android 14+ (API 34+) non-linear font scaling replaces synchronous UI-thread JNI calls ([`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1023-L1024) -> [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) and [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176)) by sending the 9-point `FontScaleConverter` lookup table (`fontScaleCurves`) over the `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) and interpolating in `PlatformDispatcher.scaleFontSize`. +
+Why FlutterWindowMetricsEvent is extended and font scaling moves to flutter/settings + +- **Extended viewport metrics**: [`PlatformViewAndroid::SetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L341) constructs an internal [`flutter::ViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h#L16) struct containing system bar padding, gesture insets, touch slop, display corner radii (API 31+), and foldable display features (`WindowInfoTracker`), which [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) previously lacked. +- **Channel-driven non-linear font scaling**: On Android 14+ (API 34+), [`PlatformViewAndroid::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L516-L522) previously crossed JNI into [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) during text layout; sending AOSP's 9-point piecewise-linear `FontScaleConverter` lookup table over `flutter/settings` eliminates both the `PlatformView` override and per-font-size JNI round-trips. +
+ #### `FlutterWindowMetricsEvent` In-Place Extensions ```c @@ -1280,7 +1347,7 @@ flowchart TD subgraph SourceSets["Partitioned Source Sets in BUILD.gn"] CAPISourceSet["source_set flutter_embedder_native_src
check_includes = true, FLUTTER_ENGINE_NO_PROTOTYPES
----------------------------------------
flutter_embedder_native.*, jni_router.*, jni_delegate.h, jvm_invoker.*
android_surface_control.*, android_platform_views_controller.*, android_mutators_mapper.*
android_hardware_buffer.*, android_vulkan_external_texture.*
android_semantics_mapper.*, android_window_metrics_mapper.*
android_vsync_waiter.*, android_vm_init.*, android_engine_group.*, apk_asset_provider.*"] - LegacySourceSet["Transitional Phase 2-3 Legacy Files (Deleted in Phase 4)
----------------------------------------
android_shell_holder.*, platform_view_android.*, platform_view_android_jni_impl.*
shell_android_engine.*, embedder_android_engine.*, external_view_embedder/"] + LegacySourceSet["Transitional Phase 2-3 Legacy Files (Deleted in Phase 4)
----------------------------------------
android_shell_holder.*, platform_view_android.*, platform_view_android_jni_impl.*
external_view_embedder/"] end subgraph AllowedCAPIDeps["Allowed Dependencies"] @@ -1300,16 +1367,6 @@ flowchart TD LegacySourceSet --> ForbiddenCAPIDeps ``` -```mermaid -flowchart TD - Pre["Pre-Migration: Monolithic Production
AndroidShellHolder & PlatformViewAndroid active in flutter_shell_native_src (98 internal headers)."] - P2["Phase 2: Partitioning & Strangler-Fig Seam
Modular C-API components built in flutter_embedder_native_src.
Transitional AndroidEngine (ShellAndroidEngine / EmbedderAndroidEngine) bridges AndroidShellHolder."] - P3["Phase 3: C-API Cutover
FlutterEmbedderNative::RegisterJni(env) owns FlutterJNI bindings and drives FlutterEngineProcTable.
Core engine runs PlatformViewEmbedder and EmbedderExternalViewEmbedder."] - P4["Phase 4: Legacy Class Purge & Build Decoupling
Delete AndroidShellHolder, PlatformViewAndroid, PlatformViewAndroidJNIImpl, AndroidEngine, VsyncWaiterAndroid,
PlatformMessageHandlerAndroid, SurfaceTextureExternalTexture, ImageExternalTexture, plus external_view_embedder/.
Prune internal GN deps and lock CI header ratchet to 0."] - - Pre --> P2 --> P3 --> P4 -``` - ### The Dependency Ratchet Mechanism > [!IMPORTANT] From d2c3d4e6267a2988d723a41a228d71723a8e7cfd Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 21 Oct 2026 19:26:43 +0000 Subject: [PATCH 33/43] Clarify phase 3 and 4 --- ...flutter-android-embedder-c-api-migration.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index f42c238..69675ab 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -1271,9 +1271,9 @@ Implements all C-API extensions and `shell/platform/embedder/` engine changes, v - Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) in `EmbedderExternalViewEmbedder` and Impeller Vulkan backing store presentation ([#187525](https://github.com/flutter/flutter/issues/187525)). - Platform view mutator serialization (`FlutterPlatformViewMutation`), external texture callbacks (`FlutterVulkanTextureFrameCallback`, `FlutterOpenGLTextureFrameCallback`), `FlutterSemanticsNode2` and `FlutterWindowMetricsEvent` in-place extensions, deferred library callbacks, `FlutterCustomAssetResolver`, `FlutterEngineSpawn`, direct UI-thread platform message dispatch, and process-scoped utilities. -### Phases 2–4: GN Target Partitioning, C-API Cutover, and Legacy Class Purge +### Phase 2: Android Shell Modularization and GN Target Partitioning -In Phase 2, [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all new C-API components inside `source_set("flutter_embedder_native_src")` with `check_includes = true` and `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`, blocking private engine headers at compile time: +Phase 2 decomposes [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) and [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) into focused C-API components (`FlutterEmbedderNative`, `JniRouter`, `AndroidSurfaceControl`, `AndroidPlatformViewsController`, `AndroidMutatorsMapper`, `AndroidHardwareBuffer`, `AndroidVulkanExternalTexture`, `AndroidSemanticsMapper`, `AndroidWindowMetricsMapper`, `AndroidVsyncWaiter`, `AndroidVMInit`, `AndroidEngineGroup`, and [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)) validated on the host by `flutter_embedder_native_unittests`. [`shell/platform/android/BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) isolates all new C-API components inside `source_set("flutter_embedder_native_src")` with `check_includes = true` and `defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`, blocking private engine headers at compile time: ```gn # 1. Modular C-API Source Set (Compile-Time Firewall) @@ -1367,6 +1367,20 @@ flowchart TD LegacySourceSet --> ForbiddenCAPIDeps ``` +### Phase 3: C-API Cutover Behind Feature Flag + +In Phase 3, the cutover from [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38) to `FlutterEmbedderNative` is gated behind the `--enable-embedder-api` shell flag (`io.flutter.embedding.android.EnableEmbedderAPI` manifest metadata) and flipped to `true` by default once CI and DeviceLab suites pass. When enabled, `FlutterEmbedderNative` initializes [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) via [`FlutterEngineInitialize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2970) and [`FlutterEngineRunInitialized`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3006) and drives all runtime events exclusively through [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827): + +- **Event and channel dispatch**: Routes touch events (`FlutterEngineSendPointerEvent`), window metrics (`FlutterEngineSendWindowMetricsEvent`), platform messages (`FlutterEngineSendPlatformMessage`), and accessibility actions (`FlutterEngineSendSemanticsAction`) through `FlutterEngineProcTable`. +- **Textures and multi-surface composition**: Registers external textures (`FlutterEngineRegisterExternalTexture`) and presents multi-surface frames through [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322) (`present_view_callback`), while [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h) and `EmbedderExternalViewEmbedder` fulfill the internal [`Shell`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L113) contracts. + +### Phase 4: Legacy Class Purge and Build Decoupling + +Once `FlutterEmbedderNative` is validated as the default across all CI and DeviceLab tests, the `--enable-embedder-api` flag is removed and [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) and [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) are permanently decoupled: + +- **Delete legacy classes**: Remove `--enable-embedder-api`, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), [`VsyncWaiterAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/vsync_waiter_android.h#L19), [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18), [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33), [`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36), and the [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) directory, reducing [`:flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) to [`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc), [`flutter_main.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17), and [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12) (which calls `FlutterEmbedderNative::RegisterJni(env)` directly). +- **Prune internal GN dependencies and lock the CI ratchet**: Remove [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) from `BUILD.gn`, restricting dependencies to `embedder_headers`, `embedder_as_internal_library`, `//flutter/fml`, `//flutter/common`, and `//flutter/assets`, and locking `allowed_internal_headers.yaml` to `total_allowed_internal_headers: 0`. + ### The Dependency Ratchet Mechanism > [!IMPORTANT] From b04462a7ae2919d1b408e8795f5014f2ca306a63 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 21 Oct 2026 20:13:28 +0000 Subject: [PATCH 34/43] Remove rejection path of vulkan checks in preference to moving checks into the Android embedder --- ...lutter-android-embedder-c-api-migration.md | 399 +++++++++--------- 1 file changed, 205 insertions(+), 194 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 69675ab..b62a40b 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -41,9 +41,8 @@ The dependency graph of [`shell/platform/android/`](https://github.com/flutter/f |[`//flutter/shell/platform/embedder:embedder_as_internal_library`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L206)|Enforce dynamic `FlutterEngineProcTable` resolution with`defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ]`| |[`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml)|Optional| |[`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common)|Optional| -|[`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets)|Optional| -All direct dependencies on internal engine subsystems ([`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), and [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt)) will be eliminated. +All direct dependencies on internal engine subsystems ([`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets)) will be eliminated. ### Goals @@ -100,10 +99,10 @@ flowchart TD ```mermaid sequenceDiagram autonumber - participant FlutterJNI as "FlutterJNI (platform_view_android_jni_impl.cc)" - participant Holder as "AndroidShellHolder" - participant PV as "PlatformViewAndroid" - participant Shell as "Shell" + participant FlutterJNI as "jni : FlutterJNI" + participant Holder as "holder : AndroidShellHolder" + participant PV as "platformView : PlatformViewAndroid" + participant Shell as "shell : Shell" participant Raster as "Raster Thread" FlutterJNI->>Holder: AttachJNI (construct AndroidShellHolder) @@ -144,20 +143,21 @@ The following sections specify the [`embedder.h`](https://github.com/flutter/flu ### Initialization > [!IMPORTANT] -> - **Embedder API additions (`embedder.h`)**: Adds `kDeferred` (`FlutterDeferredRendererConfig`), `FlutterRendererConfigResolveCallback`, `FlutterRendererResolveInfo`, and `FlutterRendererRejectReason` to [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055). +> - **Embedder API additions (`embedder.h`)**: Adds `kDeferred` (`FlutterDeferredRendererConfig`) and `FlutterRendererConfigResolveCallback` to [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055). > - **Behavioral changes**: > - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36), [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h), and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h) move [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) creation from their UI-thread constructors into `PlatformViewEmbedder::SetupImpellerContext()` on the raster thread, and `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the FIFO raster task runner instead of blocking the UI thread on a latch. -> - If the engine rejects a `kVulkan` configuration returned by `resolve_callback` (because context creation fails or the driver matches [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L377)), it re-invokes `resolve_callback` with the rejection reason so the embedder can destroy its `VkDevice` and return `kOpenGL`. +> - `AndroidSurfaceControl` performs all Android Vulkan eligibility checks inside `resolve_callback` before creating a `VkDevice` and returns either `kVulkan` or `kOpenGL` in a single call. To make those checks available without depending on `//flutter/impeller`, the Android GPU blocklist in [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L379-L431) and the required Android Vulkan capability checks in [`CapabilitiesVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529) are moved into `AndroidSurfaceControl` alongside the existing Android system-property and SoC checks from [`android_context_dynamic_impeller.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L23-L119).
-Why shell/platform/embedder/ must change to preserve non-blocking startup +Why shell/platform/embedder/ must change and what Impeller checks move to the Android embedder [`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added in [#167765](https://github.com/flutter/flutter/pull/167765) because Vulkan driver and context initialization can take 100+ ms on Android devices, but [#167765](https://github.com/flutter/flutter/pull/167765) only wired it up in `PlatformViewAndroid`: - [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) currently construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread during `FlutterEngineRunInitialized`. - [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) (called by `FlutterEngineRunInitialized` right after `LaunchShell()`) posts `CreateRenderingSurface()` to the FIFO raster task runner behind `CreateImpellerContext` and blocks the UI thread on `latch.Wait()`. - -Moving context creation into `PlatformViewEmbedder::SetupImpellerContext()` and making `PlatformViewEmbedder::NotifyCreated()` queue [`Shell::OnPlatformViewCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.cc#L973-L1062) on the raster task runner without a latch keeps both backend probing (`resolve_callback`) and `impeller::Context` creation off the UI thread. +- **Impeller Vulkan checks moved to the Android embedder**: Today, [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L139) checks Android system properties (`IsDeviceEmulator`, `ro.com.google.clientidbase == "android-huawei"`, MediaTek API < 32, and `kBadSocs` in `ro.product.board`) and then calls `ContextVK::Create` and `DriverInfoVK::IsKnownBadDriver()`. Because `AndroidSurfaceControl` owns `VkInstance`, `VkPhysicalDevice`, and `VkDevice` creation under `embedder.h`, the following Impeller checks are made available directly to `AndroidSurfaceControl`: + 1. **Physical device and driver blocklist ([`driver_info_vk.cc#L20-L431`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L20-L431))**: Inspecting `VkPhysicalDeviceProperties` (`vendorID`, `deviceID`, `driverVersion`, `apiVersion`, `deviceType`, `deviceName`) to block Adreno <= 650 (`GetAdrenoVersion`), Huawei Maleoon (`0x19E5`), Samsung Xclipse (`0x144D`) with Vulkan < 1.3, PowerVR series older than `kCXT` (`GetPowerVRVersion`), Pixel 10 (`0x71061212`) with driver < `6794074`, and SwiftShader CPU emulators (`IsEmulator`). + 2. **Required Vulkan extensions and physical device capabilities ([`capabilities_vk.cc#L98-L529`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529))**: Verifying `VK_KHR_surface` and `VK_KHR_android_surface`; verifying required Android device extensions (`VK_KHR_swapchain`, `VK_ANDROID_external_memory_android_hardware_buffer`, `VK_KHR_sampler_ycbcr_conversion`, `VK_KHR_external_memory`, `VK_EXT_queue_family_foreign`, `VK_KHR_dedicated_allocation`); checking `VK_FORMAT_R8G8B8A8_UNORM` color and `D32_SFLOAT_S8_UINT` / `D24_UNORM_S8_UINT` depth-stencil attachment support, `1x`/`4x` sample counts, graphics/compute/transfer queue families, and enabling `samplerYcbcrConversion` and `uniformAndStorageBuffer16BitAccess` when calling `vkCreateDevice`.
```c @@ -167,62 +167,43 @@ typedef struct FlutterRendererConfig FlutterRendererConfig; typedef enum { // ... existing values ... - /// The embedder chooses the renderer on the raster thread while the engine - /// launches. See |FlutterDeferredRendererConfig|. + /// Optional renderer type where the embedder chooses the concrete renderer on + /// the raster thread while the engine launches. See + /// |FlutterDeferredRendererConfig|. kDeferred, } FlutterRendererType; -typedef enum { - kFlutterRendererRejectReasonNone = 0, - /// The configuration failed validation (missing callbacks, a type this build - /// or these settings do not support, or `kDeferred` itself). - kFlutterRendererRejectReasonInvalidConfig, - /// The engine could not create a rendering context from the configuration, - /// for example because the Vulkan device lacks a required extension. - kFlutterRendererRejectReasonContextCreationFailed, - /// The Vulkan driver is on the engine's list of known-bad drivers. - kFlutterRendererRejectReasonKnownBadDriver, -} FlutterRendererRejectReason; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterRendererResolveInfo). - size_t struct_size; - /// 0 on the first call; incremented after each rejected configuration. - size_t attempt; - /// The type of the previously returned configuration. Unset on attempt 0. - FlutterRendererType rejected_type; - /// Why the previous configuration was rejected. None on attempt 0. - FlutterRendererRejectReason reject_reason; -} FlutterRendererResolveInfo; - +/// Callback invoked by the engine on the raster thread to resolve a deferred +/// renderer configuration into a concrete one. +/// +/// Embedders should prefer `kVulkan` (or `kMetal` on Apple platforms) when +/// supported by the device and treat `kOpenGL` as a fallback. +/// /// @param[in] user_data The `user_data` passed to `FlutterEngineInitialize` /// or `FlutterEngineRun`. -/// @param[in] info Which attempt this is and, after a rejection, why. -/// @param[out] config_out Zero-initialized, engine-owned. Set `type` to -/// `kOpenGL`, `kSoftware`, `kMetal` or `kVulkan` and -/// fill in that union member. Memory it references must -/// stay valid until `FlutterEngineDeinitialize` returns -/// for this engine and every engine spawned from it. +/// @param[out] config_out Zero-initialized, engine-owned storage. Set `type` +/// to `kVulkan`, `kMetal`, `kOpenGL`, or `kSoftware` +/// and populate the corresponding union member +/// (`kDeferred` is not allowed). All memory referenced +/// by the configuration must remain valid until +/// `FlutterEngineDeinitialize` or +/// `FlutterEngineShutdown` returns for this engine and +/// any engine spawned from it. /// @return `false` if no renderer can be created. typedef bool (*FlutterRendererConfigResolveCallback)( void* /* user_data */, - const FlutterRendererResolveInfo* /* info */, FlutterRendererConfig* /* config_out */); typedef struct { /// The size of this struct. Must be sizeof(FlutterDeferredRendererConfig). size_t struct_size; - /// Required. Called on the raster task runner before the engine creates its - /// rendering context, while the engine launches. If the engine rejects the - /// returned configuration, it releases every object it created from it - /// (embedder-provided handles are never destroyed by the engine) and calls - /// this again with the reason; return `false` to stop. The engine creates - /// its rendering surface after this returns, so only + /// Required. Invoked once on the raster task runner before the engine creates + /// its rendering context, while the engine launches. The engine creates its + /// rendering surface after this callback returns, so only /// `FlutterEngineDeinitialize` and `FlutterEngineShutdown` wait for it. The - /// callback may block, but it must not wait on the platform thread. - /// Engines created with `FlutterEngineSpawn` reuse the resolved - /// configuration and rendering context; the callback is not called for - /// them. + /// callback may block, but it must not wait on the platform thread. Engines + /// created with `FlutterEngineSpawn` reuse the resolved configuration and + /// rendering context; the callback is not invoked for them. FlutterRendererConfigResolveCallback resolve_callback; } FlutterDeferredRendererConfig; @@ -241,10 +222,10 @@ struct FlutterRendererConfig { ```mermaid sequenceDiagram autonumber - participant UI as "Android UI Thread" - participant Emb as "embedder.cc & PlatformViewEmbedder" - participant Raster as "Engine Raster Thread" - participant Cb as "Android resolve_callback" + participant UI as "UI Thread" + participant Emb as "engine : PlatformViewEmbedder" + participant Raster as "Raster Thread" + participant SC as "surfaceControl : AndroidSurfaceControl" UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig{kDeferred}) UI->>Emb: FlutterEngineRunInitialized() @@ -253,17 +234,16 @@ sequenceDiagram Emb->>Emb: RunRootIsolate() Emb-->>UI: Return without waiting for the raster thread par Raster thread resolves the renderer - Raster->>Cb: resolve_callback(attempt 0) - Cb->>Cb: System property and SoC checks, then VkInstance, VkDevice and VkQueue - Cb-->>Raster: kVulkan configuration - Raster->>Raster: Validate, build EmbedderSurfaceVulkanImpeller, ContextVK::Create - Raster->>Raster: Known-bad driver check (DriverInfoVK::IsKnownBadDriver) - opt Configuration rejected - Raster->>Cb: resolve_callback(attempt 1, reject_reason) - Cb-->>Raster: Destroy VkDevice, return kOpenGL configuration - Raster->>Raster: Build EmbedderSurfaceGLImpeller, ContextGLES::Create + Raster->>SC: resolve_callback(user_data, config_out) + SC->>SC: Check SoC, system properties, VkPhysicalDeviceProperties blocklist, and extensions + alt Vulkan supported and not blocklisted + SC-->>Raster: Populate config_out with kVulkan (VkInstance, VkDevice, VkQueue) + Raster->>Raster: Build EmbedderSurfaceVulkanImpeller and ContextVK::Create + else Fallback to OpenGL ES + SC-->>Raster: Populate config_out with kOpenGL (EGLDisplay, EGLContext) + Raster->>Raster: Build EmbedderSurfaceGLImpeller and ContextGLES::Create end - Raster->>Raster: CreateRenderingSurface (context only), Rasterizer::Setup + Raster->>Raster: CreateRenderingSurface (context only) and Rasterizer::Setup Raster-)UI: ScheduleFrame() and UI Thread Inflates Activity View Hierarchy UI->>UI: Activity.onStart() / onResume() / WindowManager layout @@ -312,17 +292,20 @@ typedef struct { /// The identifier for the view that the engine will use this backing store to /// render into. FlutterViewId view_id; - /// True if this backing store is for the bottom-most layer of the frame for + /// True only when `FlutterCompositor.avoid_backing_store_cache` is true and + /// this backing store is for the bottom-most layer of the frame for /// `view_id`, with no platform view composited beneath it. The engine /// requests at most one such backing store per view per frame, and none when - /// all Flutter contents are composited above platform views. False for all - /// other layers. Read this field only if `struct_size` covers it, and treat - /// it as false otherwise. + /// all Flutter contents are composited above platform views. Always false + /// when `avoid_backing_store_cache` is false or for non-root layers. Read + /// this field only if `struct_size` covers it, and treat it as false + /// otherwise. /// - /// An embedder may return the view's on-screen render target (for example, - /// the next swapchain image or default framebuffer) when this is true. The - /// engine's backing store cache ignores this field, so such an embedder must - /// set `FlutterCompositor.avoid_backing_store_cache` to true. + /// An embedder that returns the view's on-screen render target (for example, + /// the next swapchain image or default framebuffer) when this is true must + /// resize that target to `size` inside `create_backing_store_callback` + /// before returning it (or in `present_view_callback` on frames that have no + /// root backing store). bool is_root_layer; } FlutterBackingStoreConfig; ``` @@ -354,10 +337,10 @@ typedef struct { ```mermaid sequenceDiagram autonumber - participant Raster as "Raster Thread (Rasterizer)" - participant Plat as "Platform Thread (Merged Queue)" - participant Emb as "EmbedderExternalViewEmbedder" - participant PVC as "PlatformViewsController & FlutterView (Java)" + participant Raster as "Raster Thread" + participant Plat as "Platform Thread" + participant Emb as "embedder : EmbedderExternalViewEmbedder" + participant PVC as "controller : PlatformViewsController" Note over Raster,PVC: First Frame with HC Platform View (previous_frame_view_count_ == 0) Raster->>Emb: Preroll() + PostPrerollAction() (!IsMerged) @@ -392,11 +375,11 @@ When HCPP (`enable_surface_control` on API 34+ with Impeller Vulkan) is active, ```mermaid sequenceDiagram autonumber - participant Impeller as Impeller Vulkan Backend - participant Engine as Flutter Engine Core - participant Embedder as AndroidSurfaceControl C++ - participant SC as SurfaceControl.Transaction Java and NDK - participant SF as SurfaceFlinger Android OS + participant Impeller as "impeller : ContextVK" + participant Engine as "engine : PlatformViewEmbedder" + participant Embedder as "surfaceControl : AndroidSurfaceControl" + participant SC as "transaction : SurfaceControl.Transaction" + participant SF as "compositor : SurfaceFlinger" Impeller->>Impeller: Record and submit render pass to VkQueue (FlushCommandBuffers) Impeller->>Engine: Finish backing store render @@ -440,7 +423,7 @@ typedef struct { FlutterRect clip_rect; FlutterRoundedRect clip_rounded_rect; FlutterTransformation transformation; - const FlutterPath* clip_path; + FlutterPath clip_path; }; } FlutterPlatformViewMutation; @@ -623,18 +606,22 @@ typedef struct { /// The texture format (for example GL_RGBA8). Use GL_RGBA8 when `target` /// is GL_TEXTURE_EXTERNAL_OES. uint32_t format; - /// User data to be returned on the invocation of the destruction callback. + /// Embedder-owned context pointer (for example, the per-frame texture or + /// EGLImage handle) passed to `destruction_callback`. void* user_data; - /// Callback invoked (on an engine-managed thread) that asks the embedder - /// to collect the texture. + /// Callback invoked with `user_data` (on an engine-managed thread) when the + /// engine is done sampling this texture frame. VoidCallback destruction_callback; /// Width of the texture in pixels. size_t width; /// Height of the texture in pixels. size_t height; + /// Whether `uv_transform` is set. When false (including zero-initialized + /// structs), the texture is sampled with the identity UV transformation. + bool has_uv_transform; /// Transformation applied to normalized texture coordinates before - /// sampling, for example from SurfaceTexture.getTransformMatrix(). An - /// all-zero or identity matrix draws the texture untransformed. + /// sampling (for example, from SurfaceTexture.getTransformMatrix()) when + /// `has_uv_transform` is true. FlutterTransformation uv_transform; } FlutterOpenGLExternalTexture; @@ -699,10 +686,9 @@ typedef enum { kFlutterSemanticsRoleMain = 30, kFlutterSemanticsRoleNavigation = 31, kFlutterSemanticsRoleRegion = 32, - kFlutterSemanticsRoleSearch = 33, - kFlutterSemanticsRoleSlider = 34, - kFlutterSemanticsRoleListBox = 35, - kFlutterSemanticsRoleOption = 36, + kFlutterSemanticsRoleSlider = 33, + kFlutterSemanticsRoleListBox = 34, + kFlutterSemanticsRoleOption = 35, } FlutterSemanticsRole; // Appended to FlutterSemanticsNode2 in embedder.h: @@ -772,10 +758,11 @@ typedef struct { /// Optional callback invoked when the engine releases its references to /// `snapshot_data` and `snapshot_instructions`. /// - /// If null, the embedder must ensure the buffers remain valid until - /// `FlutterEngineShutdown` returns. If non-null, this callback is invoked - /// once both snapshot buffers are no longer referenced by the engine (for - /// example, on isolate shutdown or if loading fails). + /// The Dart VM maps `snapshot_data` and `snapshot_instructions` directly into + /// the isolate group heap without copying them, so the buffers must remain + /// valid for the lifetime of the isolate group. If non-null, this callback is + /// invoked when the isolate group shuts down (or immediately if loading + /// fails). VoidCallback destruction_callback; } FlutterDartDeferredLibrary; @@ -814,11 +801,11 @@ sequenceDiagram Emb->>Play: installDeferredComponent(loading_unit_id) alt Split APK installed & symbols resolved Play->>Emb: loadAssets() -> updateJavaAssetManager() + loadDartDeferredLibrary() - Emb->>Emb: dlopen / mmap or AAsset map snapshot_data & snapshot_instructions + Emb->>Emb: dlopen split .so and resolve snapshot_data & snapshot_instructions Emb->>Engine: FlutterEngineLoadDartDeferredLibrary(engine, &library) Engine->>VM: LoadDartDeferredLibrary(loading_unit_id, data_mapping, instr_mapping) - Note over Engine,Emb: When Dart VM drops both mappings (or on isolate shutdown) - Engine->>Emb: destruction_callback(user_data) -> munmap / AAsset_close + Note over Engine,Emb: On isolate group shutdown (or immediately if load fails) + Engine->>Emb: destruction_callback(user_data) else Download or extraction failed Play->>Emb: deferredComponentInstallFailure() Emb->>Engine: FlutterEngineNotifyDartDeferredLibraryLoadError(engine, &error) @@ -872,12 +859,12 @@ const FlutterCustomAssetResolver** custom_asset_resolvers; > To support [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) and Add-to-App multi-engine sharing without calling internal [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) methods: > > - **Embedder API additions (`embedder.h`)**: Adds `FlutterEngineSpawn` and `FlutterEngineSpawnInfo`. -> - **Behavioral changes**: `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285), spawning child `FlutterEngine` instances that share the parent engine's Dart VM isolate group, `io_manager`, and resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) (including when the parent engine initialized with `type = kDeferred` or when `renderer_config` is `nullptr`). +> - **Behavioral changes**: `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285), spawning child `FlutterEngine` instances that automatically inherit the parent engine's Dart VM isolate group, `io_manager`, resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70), task runners, asset resolvers, and `FlutterProjectArgs` callbacks (invoked with the child engine's `user_data`).
-Why FlutterEngineSpawn is needed +Why FlutterEngineSpawn is exposed in embedder.h while FlutterEngineGroup remains the public Java/Objective-C API -[`FlutterEngineGroup.createAndRunEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) relies on [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) so secondary engines share the parent engine's Dart VM isolate group heap, `io_manager`, and GPU context (~180 KB per spawned engine vs. ~15–20 MB for an independent `FlutterEngineInitialize` call). Exposing `FlutterEngineSpawn` in `embedder.h` preserves that sharing without calling `Shell::Spawn` directly. +[`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) in Java (and `FlutterEngineGroup` in Objective-C on iOS) is a thin collection (`activeEngines`) that initializes the first `FlutterEngine` normally and then calls `activeEngines.get(0).spawn(...)` -> [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) so secondary engines share the parent engine's Dart VM isolate group heap, `io_manager`, and GPU context (~180 KB per spawned engine vs. ~15–20 MB for an independent `FlutterEngineInitialize` call). Because the C++ engine core has no `EngineGroup` class and shares resources across `Shell::Spawn` via `std::shared_ptr`, exposing `FlutterEngineSpawn` in `embedder.h` maps 1:1 to `Shell::Spawn` while keeping `FlutterEngineGroup` as the public developer-facing API.
```c @@ -889,8 +876,7 @@ typedef struct { const char* const* entrypoint_argv; int entrypoint_argc; void* user_data; - const FlutterProjectArgs* project_args; - const FlutterRendererConfig* renderer_config; + const FlutterCompositor* compositor; } FlutterEngineSpawnInfo; FLUTTER_EXPORT @@ -900,73 +886,93 @@ FlutterEngineResult FlutterEngineSpawn( FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); ``` -### Image Generators, Fonts, and VM URI +### Image Decoders / Font Prefetching / VM Service URI > [!IMPORTANT] > To eliminate the remaining [`//flutter/lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h#L27), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime/dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h#L22) inclusions in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108): > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.image_generators` (`FlutterImageGeneratorRegistration`, `FlutterImageGenerator`, `FlutterImageGeneratorInfo`), `FlutterEnginePrefetchDefaultFontManager`, and `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`). -> - **Behavioral changes**: The Android embedder registers its fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats at priority `-1`) without subclassing `flutter::ImageGenerator`, and the engine keeps the encoded byte buffer alive until `destruction_callback` runs. `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists, and `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. +> - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.image_decoders` (`FlutterImageDecoderRegistration`, `FlutterImageDecoder`, `FlutterImageDecoderInfo`, `FlutterImageDecoderColorSpace`), `FlutterEnginePrefetchDefaultFontManager`, and `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`). +> - **Behavioral changes**: The Android embedder registers its single-frame fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats at priority `-1`) without subclassing `flutter::ImageGenerator`, and the engine keeps the encoded byte buffer alive until `destruction_callback` runs. `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists, and `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`.
Why these three C-APIs are needed -- [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L166-L171) registers [`AndroidImageGenerator`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_image_generator.h#L18) via `Shell::RegisterImageDecoder` so Android's NDK `AImageDecoder` can decode HEIF and platform image formats not handled by built-in engine codecs. +- [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L166-L171) registers [`AndroidImageGenerator`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_image_generator.h#L18) via `Shell::RegisterImageDecoder` so Android's NDK `AImageDecoder` can decode single-frame HEIF and platform image formats not handled by built-in engine codecs (while multi-frame formats such as GIF, animated WebP, and APNG are decoded by the engine's built-in priority-`0` codecs). - [`FlutterMain::PrefetchDefaultFontManager`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L211-L214) calls `txt::GetDefaultFontManager()` (`//flutter/txt`) during `FlutterLoader.initAsync` before any `FlutterEngine` instance exists. - [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L186-L195) includes `//flutter/runtime/dart_service_isolate.h` directly to register a callback for `FlutterJNI.getVMServiceUri()`.
```c typedef enum { - kFlutterImageGeneratorColorTypeRGBA8888, - kFlutterImageGeneratorColorTypeBGRA8888, -} FlutterImageGeneratorColorType; + kFlutterImageDecoderColorTypeRGBA8888, + kFlutterImageDecoderColorTypeBGRA8888, +} FlutterImageDecoderColorType; + +typedef enum { + kFlutterImageDecoderAlphaTypeOpaque, + kFlutterImageDecoderAlphaTypePremul, + kFlutterImageDecoderAlphaTypeUnpremul, +} FlutterImageDecoderAlphaType; typedef enum { - kFlutterImageGeneratorAlphaTypeOpaque, - kFlutterImageGeneratorAlphaTypePremul, - kFlutterImageGeneratorAlphaTypeUnpremul, -} FlutterImageGeneratorAlphaType; + kFlutterImageDecoderColorSpaceSRGB = 0, +} FlutterImageDecoderColorSpace; typedef struct { size_t struct_size; uint32_t width; uint32_t height; - FlutterImageGeneratorColorType color_type; - FlutterImageGeneratorAlphaType alpha_type; -} FlutterImageGeneratorInfo; - -typedef bool (*FlutterImageGeneratorGetInfoCallback)( + FlutterImageDecoderColorType color_type; + FlutterImageDecoderAlphaType alpha_type; + FlutterImageDecoderColorSpace color_space; +} FlutterImageDecoderInfo; + +/// Invoked on the UI thread (during ImageDescriptor creation) and on an engine +/// concurrent worker thread (during decoding). +typedef bool (*FlutterImageDecoderGetInfoCallback)( void* user_data, - FlutterImageGeneratorInfo* info_out); + FlutterImageDecoderInfo* info_out); -typedef bool (*FlutterImageGeneratorGetPixelsCallback)( +/// Invoked on an engine concurrent worker thread; never invoked on the UI thread. +typedef bool (*FlutterImageDecoderGetPixelsCallback)( void* user_data, - const FlutterImageGeneratorInfo* info, + const FlutterImageDecoderInfo* info, void* pixels, size_t row_bytes); +/// Single-frame fallback image decoder (multi-frame formats such as GIF, +/// animated WebP, and APNG are handled by the engine's built-in priority-0 +/// codecs). typedef struct { size_t struct_size; void* user_data; - FlutterImageGeneratorGetInfoCallback get_info; - FlutterImageGeneratorGetPixelsCallback get_pixels; + FlutterImageDecoderGetInfoCallback get_info; + FlutterImageDecoderGetPixelsCallback get_pixels; + /// May be invoked on any thread (UI, IO, or concurrent worker) when the last + /// reference to the decoder is released. VoidCallback destruction_callback; -} FlutterImageGenerator; +} FlutterImageDecoder; -typedef bool (*FlutterImageGeneratorFactoryCallback)( +/// Invoked on the UI thread to sniff the encoded header and construct a +/// decoder. `data` remains valid until `FlutterImageDecoder.destruction_callback` runs. +typedef bool (*FlutterImageDecoderFactoryCallback)( void* user_data, const uint8_t* data, size_t data_size, - FlutterImageGenerator* generator_out); + FlutterImageDecoder* decoder_out); typedef struct { size_t struct_size; - FlutterImageGeneratorFactoryCallback create_generator; + FlutterImageDecoderFactoryCallback create_decoder; + /// Factories are tried in descending `priority` order (ties broken by + /// registration order). Built-in engine codecs register at `0`, so + /// `priority > 0` overrides built-in codecs and `priority < 0` (such as `-1` + /// on Android) is a fallback. int32_t priority; void* user_data; + /// Invoked on the UI thread during engine teardown. VoidCallback destruction_callback; -} FlutterImageGeneratorRegistration; +} FlutterImageDecoderRegistration; typedef struct { size_t struct_size; @@ -981,8 +987,8 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEnginePrefetchDefaultFontManager(void); // Appended to FlutterProjectArgs: -size_t image_generators_count; -const FlutterImageGeneratorRegistration** image_generators; +size_t image_decoders_count; +const FlutterImageDecoderRegistration** image_decoders; FlutterVMServiceUriCallback vm_service_uri_callback; ``` @@ -1038,21 +1044,25 @@ flowchart LR bool enable_direct_platform_message_dispatch; ``` -### Viewport Metrics and Non-Linear Font Scaling +### Viewport Metrics, Pointer Events, and Non-Linear Font Scaling > [!IMPORTANT] -> To deliver Android window insets, display cutouts, foldable postures, and Android 14+ non-linear font scaling without internal [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) headers or synchronous per-layout JNI calls: +> To deliver Android window insets, display cutouts, foldable postures, touch/stylus pointer fields, and Android 14+ non-linear font scaling without internal [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) or [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1023-L1024) overrides: > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, and `FlutterDisplayFeature` (`FlutterDisplayFeatureType`, `FlutterDisplayFeatureState`), and appends `physical_padding`, `physical_system_gesture_insets`, `physical_touch_slop`, `display_corner_radii`, and `display_features` (`display_features_count`) in place to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). +> - **Embedder API additions (`embedder.h`)**: +> - Adds `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, and `FlutterDisplayFeature` (`FlutterDisplayFeatureType`, `FlutterDisplayFeatureState`), and appends `physical_padding`, `physical_system_gesture_insets`, `physical_touch_slop`, `display_corner_radii`, and `display_features` (`display_features_count`) in place to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). +> - Appends `distance`, `distance_max`, `size`, `radius_major`, `radius_minor`, `radius_min`, `radius_max`, `orientation`, `tilt`, `platform_data`, and `embedder_id` in place to [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447). +> - Adds `FlutterScaleFontSizeInfo` and `FlutterScaleFontSizeCallback` (`FlutterProjectArgs.scale_font_size_callback`). > - **Behavioral changes**: -> - [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433) populates the extended [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) and dispatches it via [`FlutterEngineSendWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3077); `embedder.cc` reads the new fields using `SAFE_ACCESS(event, member, default_value)`. -> - Android 14+ (API 34+) non-linear font scaling replaces synchronous UI-thread JNI calls ([`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1023-L1024) -> [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) and [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176)) by sending the 9-point `FontScaleConverter` lookup table (`fontScaleCurves`) over the `flutter/settings` channel ([`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java)) and interpolating in `PlatformDispatcher.scaleFontSize`. +> - [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433) and [`AndroidTouchProcessor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L358-L479) populate the extended [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) and [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447) structs; `embedder.cc` reads the new fields using `SAFE_ACCESS(event, member, default_value)`. +> - `PlatformViewEmbedder::GetScaledFontSize` delegates to `FlutterProjectArgs.scale_font_size_callback` when set, preserving Android 14+ (API 34+) non-linear font scaling via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) with zero changes to [`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176) or `PlatformDispatcher`.
-Why FlutterWindowMetricsEvent is extended and font scaling moves to flutter/settings +Why FlutterWindowMetricsEvent, FlutterPointerEvent, and scale_font_size_callback are needed -- **Extended viewport metrics**: [`PlatformViewAndroid::SetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L341) constructs an internal [`flutter::ViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h#L16) struct containing system bar padding, gesture insets, touch slop, display corner radii (API 31+), and foldable display features (`WindowInfoTracker`), which [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) previously lacked. -- **Channel-driven non-linear font scaling**: On Android 14+ (API 34+), [`PlatformViewAndroid::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L516-L522) previously crossed JNI into [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) during text layout; sending AOSP's 9-point piecewise-linear `FontScaleConverter` lookup table over `flutter/settings` eliminates both the `PlatformView` override and per-font-size JNI round-trips. +- [`PlatformViewAndroid::SetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L341) constructs an internal [`flutter::ViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h#L16) struct containing system bar padding, gesture insets, touch slop, display corner radii (API 31+), and foldable display features (`WindowInfoTracker`), which [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) previously lacked. +- [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L358-L479) populates stylus tilt/orientation, touch contact radii, `platform_data` (multi-pointer batching flags), and `embedder_id` ([`MotionEventTracker`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L730-L800) lookup ID for platform view touch forwarding), which were missing from [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447). +- On Android 14+ (API 34+), [`PlatformViewAndroid::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L516-L522) overrides `PlatformView::GetScaledFontSize` to evaluate `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, font_size, displayMetrics)` via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544). Adding `FlutterProjectArgs.scale_font_size_callback` wires `PlatformViewEmbedder::GetScaledFontSize` to that existing implementation without subclassing `PlatformView`.
#### `FlutterWindowMetricsEvent` In-Place Extensions @@ -1124,42 +1134,44 @@ Appended to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blo const FlutterDisplayFeature** display_features; ``` -#### Android 14+ Non-Linear Font Scaling +#### `FlutterPointerEvent` In-Place Extensions -```mermaid -sequenceDiagram - autonumber - participant Settings as "SettingsChannel.MessageBuilder (Java, API 34+)" - participant Disp as "PlatformDispatcher (dart:ui)" - participant Layout as "Flutter Text Layout" - - Settings->>Settings: Sample TypedValue.applyDimension across 9 sp breakpoints (8..100 sp) - Settings-)Disp: flutter/settings JSON payload (textScaleFactor + fontScaleCurves) - Disp->>Disp: _updateUserSettingsData() stores fontScaleCurves, invalidates _cachedFontSizes, fires onTextScaleFactorChanged - loop Every Text Layout Pass (Zero JNI Calls) - Layout->>Disp: scaleFontSize(unscaledFontSize) - Disp->>Disp: Binary-search fontScaleCurves & linearly interpolate (matching FontScaleConverterImpl) - Disp-->>Layout: scaledFontSize - end +Appended to [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447): + +```c + double distance; + double distance_max; + double size; + double radius_major; + double radius_minor; + double radius_min; + double radius_max; + double orientation; + double tilt; + int64_t platform_data; + int64_t embedder_id; ``` -```json -{ - "textScaleFactor": 1.15, - "platformBrightness": "light", - "alwaysUse24HourFormat": false, - "fontScaleCurves": [ - {"unscaled": 8.0, "scaled": 9.2}, - {"unscaled": 10.0, "scaled": 11.5}, - {"unscaled": 12.0, "scaled": 13.8}, - {"unscaled": 14.0, "scaled": 16.4}, - {"unscaled": 18.0, "scaled": 20.6}, - {"unscaled": 20.0, "scaled": 22.4}, - {"unscaled": 24.0, "scaled": 26.2}, - {"unscaled": 30.0, "scaled": 31.8}, - {"unscaled": 100.0, "scaled": 100.0} - ] -} +#### Android 14+ Non-Linear Font Scaling + +```c +typedef struct { + size_t struct_size; + double font_size; + double text_scale_factor; + int64_t configuration_id; +} FlutterScaleFontSizeInfo; + +/// Runs on the UI thread. Returns false on failure. +typedef bool (*FlutterScaleFontSizeCallback)( + const FlutterScaleFontSizeInfo* info, + double* scaled_font_size_out, + void* user_data); + +// Appended to FlutterProjectArgs. Optional; if NULL, the engine falls back to +// linear scaling. Only invoked after the embedder sets a configurationId +// through a `flutter/settings` message. +FlutterScaleFontSizeCallback scale_font_size_callback; ``` ## Target Architecture and Design Principles @@ -1211,10 +1223,10 @@ flowchart TD ```mermaid sequenceDiagram autonumber - participant FlutterJNI - participant FlutterEmbedderNative as "FlutterEmbedderNative (C-API Client)" - participant EmbedderAPI as "embedder.h ProcTable" - participant Core as "PlatformViewEmbedder & Shell" + participant FlutterJNI as "jni : FlutterJNI" + participant FlutterEmbedderNative as "embedder : FlutterEmbedderNative" + participant EmbedderAPI as "procTable : FlutterEngineProcTable" + participant Core as "engine : PlatformViewEmbedder" participant Raster as "Raster Thread" FlutterJNI->>FlutterEmbedderNative: attachToNative (create FlutterEmbedderNative) @@ -1228,8 +1240,8 @@ sequenceDiagram EmbedderAPI-->>FlutterEmbedderNative: Return without waiting for Raster Thread par Raster Thread Resolves Renderer via C-API Callback Raster->>Core: PlatformViewEmbedder::SetupImpellerContext() - Core->>FlutterEmbedderNative: resolve_callback(attempt 0) - FlutterEmbedderNative-->>Core: kVulkan (or kOpenGL fallback on retry) + Core->>FlutterEmbedderNative: resolve_callback(user_data, config_out) + FlutterEmbedderNative-->>Core: kVulkan (or kOpenGL fallback) Raster->>Core: CreateRenderingSurface (context-only) & Rasterizer::Setup and Platform Thread Flushes Queued Calls FlutterEmbedderNative->>EmbedderAPI: Flush queued pre-run calls @@ -1240,11 +1252,11 @@ sequenceDiagram | Target Component | Eliminated Internal Engine Dependencies | Embedder C-API Additions Used | Architectural Role | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app) (`FlutterEngineSpawnInfo`)
- [`FlutterProjectArgs.enable_direct_platform_message_dispatch`](#platform-channel-messaging-background-handlers-and-task-queues)
- [`FlutterProjectArgs.request_dart_deferred_library_callback`](#dart-deferred-library-loading), `FlutterEngineLoadDartDeferredLibrary`, `FlutterEngineNotifyDartDeferredLibraryLoadError`
- [`FlutterProjectArgs.image_generators`](#image-generators-fonts-and-vm-uri) (`FlutterImageGeneratorRegistration`), `FlutterEnginePrefetchDefaultFontManager`, `FlutterProjectArgs.vm_service_uri_callback` | Owns the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and `FlutterEngineProcTable`; replaces `AndroidShellHolder` and `PlatformViewAndroid`. | -| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | - [`kDeferred`](#initialization) (`FlutterDeferredRendererConfig`, `FlutterRendererResolveInfo`) | Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` and resolves the renderer asynchronously during startup. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app) (`FlutterEngineSpawnInfo`)
- [`FlutterProjectArgs.enable_direct_platform_message_dispatch`](#platform-channel-messaging-background-handlers-and-task-queues)
- [`FlutterProjectArgs.request_dart_deferred_library_callback`](#dart-deferred-library-loading), `FlutterEngineLoadDartDeferredLibrary`, `FlutterEngineNotifyDartDeferredLibraryLoadError`
- [`FlutterProjectArgs.image_decoders`](#image-decoders-fonts-and-vm-uri) (`FlutterImageDecoderRegistration`), `FlutterEnginePrefetchDefaultFontManager`, `FlutterProjectArgs.vm_service_uri_callback` | Owns the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and `FlutterEngineProcTable`; replaces `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | - [`kDeferred`](#initialization) (`FlutterDeferredRendererConfig`, `FlutterRendererConfigResolveCallback`) | Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` and resolves the renderer asynchronously during startup. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | - [`FlutterCompositor.merge_raster_thread_on_platform_views`](#platform-views) and `FlutterBackingStoreConfig.is_root_layer`
- [`FlutterCompositor.platform_view_mutation_capabilities`](#platform-view-mutator-stack-serialization), `FlutterPlatformView.mutations`, `FlutterPlatformViewMutation`, `FlutterPath` | Unifies HC, HCPP (`SurfaceControl`), TLHC, and VD behind `FlutterCompositor` and deletes `external_view_embedder/`. | -| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) | - [`FlutterVulkanExternalTexture`](#external-textures) (`ycbcr_conversion_info`), `FlutterVulkanYcbcrConversionInfo`, `FlutterVulkanTextureFrameCallback`
- [`FlutterOpenGLExternalTexture`](#external-textures) (`uv_transform`), `FlutterOpenGLTextureFrameCallback` | Imports zero-copy `AHardwareBuffer` (`VkImage` / `EGLImageKHR`) and `SurfaceTexture` UV transforms without subclassing `flutter::Texture`. | -| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](#accessibility-and-semantics-parity) (`role`, `hit_test_child_count`, `FlutterSemanticsRole`)
- [`FlutterWindowMetricsEvent`](#viewport-metrics-and-non-linear-font-scaling) (`FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, `FlutterDisplayFeature`) | Serializes semantics trees for Java `AccessibilityBridge` and packs insets, cutouts, and foldable features into `FlutterWindowMetricsEvent`. | +| **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) | - [`FlutterVulkanExternalTexture`](#external-textures) (`ycbcr_conversion_info`), `FlutterVulkanYcbcrConversionInfo`, `FlutterVulkanTextureFrameCallback`
- [`FlutterOpenGLExternalTexture`](#external-textures) (`has_uv_transform`, `uv_transform`), `FlutterOpenGLTextureFrameCallback` | Imports zero-copy `AHardwareBuffer` (`VkImage` / `EGLImageKHR`) and `SurfaceTexture` UV transforms without subclassing `flutter::Texture`. | +| **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](#accessibility-and-semantics-parity) (`role`, `hit_test_child_count`, `FlutterSemanticsRole`)
- [`FlutterWindowMetricsEvent`](#viewport-metrics-pointer-events-and-non-linear-font-scaling), `FlutterPointerEvent`, `FlutterProjectArgs.scale_font_size_callback` | Serializes semantics trees for Java `AccessibilityBridge`, packs insets/cutouts/foldable features and pointer data, and delegates non-linear font scaling. | | **[`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)** | [`//flutter/assets/asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h) | - [`FlutterProjectArgs.custom_asset_resolvers`](#custom-asset-and-kernel-resolution), `FlutterCustomAssetResolver`, `FlutterAssetMapping` | Maps APK and split-AAB assets via NDK `AAssetManager` without subclassing `flutter::AssetResolver`. | ## Phased Implementation Strategy @@ -1267,9 +1279,9 @@ flowchart LR Implements all C-API extensions and `shell/platform/embedder/` engine changes, validated by [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) with zero modifications to `shell/platform/android/`: -- `kDeferred` renderer resolution (`PlatformViewEmbedder::SetupImpellerContext()` with fallback retry and Impeller's bad-driver blocklist check) and non-blocking surface initialization (`PlatformViewEmbedder::NotifyCreated()` -> `Shell::OnPlatformViewCreatedAsync()`). +- `kDeferred` renderer resolution (`PlatformViewEmbedder::SetupImpellerContext()`) and non-blocking surface initialization (`PlatformViewEmbedder::NotifyCreated()` -> `Shell::OnPlatformViewCreatedAsync()`). - Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) in `EmbedderExternalViewEmbedder` and Impeller Vulkan backing store presentation ([#187525](https://github.com/flutter/flutter/issues/187525)). -- Platform view mutator serialization (`FlutterPlatformViewMutation`), external texture callbacks (`FlutterVulkanTextureFrameCallback`, `FlutterOpenGLTextureFrameCallback`), `FlutterSemanticsNode2` and `FlutterWindowMetricsEvent` in-place extensions, deferred library callbacks, `FlutterCustomAssetResolver`, `FlutterEngineSpawn`, direct UI-thread platform message dispatch, and process-scoped utilities. +- Platform view mutator serialization (`FlutterPlatformViewMutation`), external texture callbacks (`FlutterVulkanTextureFrameCallback`, `FlutterOpenGLTextureFrameCallback`), `FlutterSemanticsNode2`, `FlutterWindowMetricsEvent`, and `FlutterPointerEvent` in-place extensions, `scale_font_size_callback`, deferred library callbacks, `FlutterCustomAssetResolver`, `FlutterEngineSpawn`, direct UI-thread platform message dispatch, and process-scoped utilities. ### Phase 2: Android Shell Modularization and GN Target Partitioning @@ -1315,7 +1327,6 @@ source_set("flutter_embedder_native_src") { ] deps = [ - "//flutter/assets", "//flutter/common", "//flutter/fml", "//flutter/shell/platform/embedder:embedder_headers", @@ -1352,11 +1363,11 @@ flowchart TD subgraph AllowedCAPIDeps["Allowed Dependencies"] EmbedderHeaders["//flutter/shell/platform/embedder:embedder_headers & embedder_as_internal_library"] - FML["//flutter/fml, //flutter/common, //flutter/assets"] + FML["//flutter/fml & //flutter/common"] end subgraph ForbiddenCAPIDeps["Forbidden Internal Dependencies (Blocked by GN check_includes)"] - Internal["//flutter/flow, //flutter/runtime, //flutter/shell/common,
//flutter/skia, //flutter/impeller, //flutter/lib/ui, //flutter/txt"] + Internal["//flutter/flow, //flutter/runtime, //flutter/shell/common,
//flutter/skia, //flutter/impeller, //flutter/lib/ui, //flutter/txt, //flutter/assets"] end SharedLib --> ShellSrc @@ -1379,7 +1390,7 @@ In Phase 3, the cutover from [`AndroidShellHolder`](https://github.com/flutter/f Once `FlutterEmbedderNative` is validated as the default across all CI and DeviceLab tests, the `--enable-embedder-api` flag is removed and [`shell/platform/android/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android) and [`BUILD.gn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn) are permanently decoupled: - **Delete legacy classes**: Remove `--enable-embedder-api`, [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.h#L38), [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43), [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc), [`VsyncWaiterAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/vsync_waiter_android.h#L19), [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18), [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33), [`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36), and the [`external_view_embedder/`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder) directory, reducing [`:flutter_shell_native_src`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L85) to [`flutter_main.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc), [`flutter_main.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17), and [`library_loader.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/library_loader.cc#L12) (which calls `FlutterEmbedderNative::RegisterJni(env)` directly). -- **Prune internal GN dependencies and lock the CI ratchet**: Remove [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) from `BUILD.gn`, restricting dependencies to `embedder_headers`, `embedder_as_internal_library`, `//flutter/fml`, `//flutter/common`, and `//flutter/assets`, and locking `allowed_internal_headers.yaml` to `total_allowed_internal_headers: 0`. +- **Prune internal GN dependencies and lock the CI ratchet**: Remove [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/skia`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/skia), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets), and [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common) from `BUILD.gn`, restricting dependencies to `embedder_headers`, `embedder_as_internal_library`, `//flutter/fml`, and `//flutter/common`, and locking `allowed_internal_headers.yaml` to `total_allowed_internal_headers: 0`. ### The Dependency Ratchet Mechanism @@ -1390,7 +1401,7 @@ Once `FlutterEmbedderNative` is validated as the default across all CI and Devic flowchart TD subgraph Layer1["Layer 1: GN Target Partitioning (Compile-Time Firewall)"] NewCAPI["flutter_embedder_native_src (check_includes = true, FLUTTER_ENGINE_NO_PROTOTYPES)"] - StrictDeps["Only embedder_headers, fml, common, assets allowed"] + StrictDeps["Only embedder_headers + fml + common allowed"] NewCAPI --> StrictDeps end @@ -1449,10 +1460,10 @@ subsystems: ### Engine Unit Tests - Host-executable C++ unit tests (`flutter_embedder_native_unittests`) test `FlutterEmbedderNative` and each modular component using mock [`FlutterEngineProcTable`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3651-L3827) entries and injectable `JniDelegate` / `JvmInvoker` providers without an Android JVM or device. -- [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) cover `kDeferred` renderer resolution and fallback retry, non-blocking `FlutterEngineRunInitialized` while `resolve_renderer_callback` runs on the raster thread, opt-in dynamic raster thread merging (`merge_raster_thread_on_platform_views`), [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) serialization, and per-`FlutterViewId` frame state capture. +- [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) cover `kDeferred` renderer resolution, non-blocking `FlutterEngineRunInitialized` while `resolve_callback` runs on the raster thread, opt-in dynamic raster thread merging (`merge_raster_thread_on_platform_views`), [`FlutterPlatformViewMutation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2083-L2092) serialization, and per-`FlutterViewId` frame state capture. ### Android Integration and DeviceLab Tests - Run [`flutter_shell_native_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/BUILD.gn#L42-L67), platform view composition tests across all four Android composition modes (HCPP, HC, TLHC, VD), activity stop/start and configuration-change loops (`surfaceDestroyed()` / `surfaceCreated()`), JNI platform channels, background isolate callbacks, and deferred library loading. - Verify with golden tests that clip paths, rounded rectangles, and opacity mutators render identically across all four composition modes (HCPP, HC, TLHC, VD), and validate external texture playback across camera and video player plugins. -- Compare legacy vs C-API DeviceLab benchmarks for frame pacing, rasterization latency, HCPP/HC scrolling and fence timing, [`FlutterCustomAssetResolver`](#custom-asset-and-kernel-resolution) startup latency, cold-start time-to-first-frame (`CreateImpellerContext`), and post-`surfaceDestroyed()` GPU memory reclamation (`FlutterEngineNotifyLowMemoryWarning`). +- Monitor the existing DeviceLab benchmarks across the Phase 3 cutover to verify zero regressions in frame pacing, rasterization latency, HCPP/HC scrolling and fence timing, [`FlutterCustomAssetResolver`](#custom-asset-and-kernel-resolution) startup latency, cold-start time-to-first-frame (`CreateImpellerContext`), and post-`surfaceDestroyed()` GPU memory reclamation (`FlutterEngineNotifyLowMemoryWarning`). From d447fb04de28064767eeb9543e51fa511c464fbf Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 21 Oct 2026 22:21:44 +0000 Subject: [PATCH 35/43] Clarify backing_store_cache is disabled and made addition to existing structs more clear. --- ...lutter-android-embedder-c-api-migration.md | 106 ++++++++++++------ 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index b62a40b..5528539 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -147,9 +147,10 @@ The following sections specify the [`embedder.h`](https://github.com/flutter/flu > - **Behavioral changes**: > - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36), [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h), and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h) move [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) creation from their UI-thread constructors into `PlatformViewEmbedder::SetupImpellerContext()` on the raster thread, and `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the FIFO raster task runner instead of blocking the UI thread on a latch. > - `AndroidSurfaceControl` performs all Android Vulkan eligibility checks inside `resolve_callback` before creating a `VkDevice` and returns either `kVulkan` or `kOpenGL` in a single call. To make those checks available without depending on `//flutter/impeller`, the Android GPU blocklist in [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L379-L431) and the required Android Vulkan capability checks in [`CapabilitiesVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529) are moved into `AndroidSurfaceControl` alongside the existing Android system-property and SoC checks from [`android_context_dynamic_impeller.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L23-L119). +> - During `FlutterEngineInitialize`, the Android embedder configures [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2266-L2322) (`FlutterProjectArgs.compositor`) with [`avoid_backing_store_cache = true`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2312) to turn off the engine's backing store cache ([`EmbedderRenderTargetCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_render_target_cache.h)) so the Android embedder is responsible for managing surfaces.
-Why shell/platform/embedder/ must change and what Impeller checks move to the Android embedder +Why shell/platform/embedder/ must change, what Impeller checks move to the Android embedder, and why backing store caches are disabled [`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added in [#167765](https://github.com/flutter/flutter/pull/167765) because Vulkan driver and context initialization can take 100+ ms on Android devices, but [#167765](https://github.com/flutter/flutter/pull/167765) only wired it up in `PlatformViewAndroid`: @@ -158,6 +159,7 @@ The following sections specify the [`embedder.h`](https://github.com/flutter/flu - **Impeller Vulkan checks moved to the Android embedder**: Today, [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L139) checks Android system properties (`IsDeviceEmulator`, `ro.com.google.clientidbase == "android-huawei"`, MediaTek API < 32, and `kBadSocs` in `ro.product.board`) and then calls `ContextVK::Create` and `DriverInfoVK::IsKnownBadDriver()`. Because `AndroidSurfaceControl` owns `VkInstance`, `VkPhysicalDevice`, and `VkDevice` creation under `embedder.h`, the following Impeller checks are made available directly to `AndroidSurfaceControl`: 1. **Physical device and driver blocklist ([`driver_info_vk.cc#L20-L431`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L20-L431))**: Inspecting `VkPhysicalDeviceProperties` (`vendorID`, `deviceID`, `driverVersion`, `apiVersion`, `deviceType`, `deviceName`) to block Adreno <= 650 (`GetAdrenoVersion`), Huawei Maleoon (`0x19E5`), Samsung Xclipse (`0x144D`) with Vulkan < 1.3, PowerVR series older than `kCXT` (`GetPowerVRVersion`), Pixel 10 (`0x71061212`) with driver < `6794074`, and SwiftShader CPU emulators (`IsEmulator`). 2. **Required Vulkan extensions and physical device capabilities ([`capabilities_vk.cc#L98-L529`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529))**: Verifying `VK_KHR_surface` and `VK_KHR_android_surface`; verifying required Android device extensions (`VK_KHR_swapchain`, `VK_ANDROID_external_memory_android_hardware_buffer`, `VK_KHR_sampler_ycbcr_conversion`, `VK_KHR_external_memory`, `VK_EXT_queue_family_foreign`, `VK_KHR_dedicated_allocation`); checking `VK_FORMAT_R8G8B8A8_UNORM` color and `D32_SFLOAT_S8_UINT` / `D24_UNORM_S8_UINT` depth-stencil attachment support, `1x`/`4x` sample counts, graphics/compute/transfer queue families, and enabling `samplerYcbcrConversion` and `uniformAndStorageBuffer16BitAccess` when calling `vkCreateDevice`. +- **Disabling backing store caches (`avoid_backing_store_cache = true`) so the Android embedder owns surfaces**: In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89) whenever an `ANativeWindow*` attached or detached because `AndroidSurface` rendered directly into the window. Passing [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2266-L2322) with `avoid_backing_store_cache = true` during `FlutterEngineInitialize` configures the engine's internal GPU surface as context-only (`render_to_surface = false`, [#193000](https://github.com/flutter/flutter/pull/193000)) and bypasses [`EmbedderRenderTargetCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_render_target_cache.h). The engine requests each backing store (`FlutterBackingStoreConfig`) inside the frame that uses it ([`embedder_external_view_embedder.cc#L561-L572`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L561-L572)) and collects it as soon as `present_view_callback` returns ([`#L627-L643`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L627-L643)). When no window is attached or the OS invalidates the surface mid-frame, `create_backing_store_callback` returns `false` and the engine skips that layer ([`#L261-L265`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L261-L265)). `AndroidSurfaceControl` therefore manages `ANativeWindow*` swapchains and overlay surfaces across `surfaceCreated`, `surfaceChanged`, and `surfaceDestroyed` (and calls [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) on `onTrimMemory` and `surfaceDestroyed` to release Impeller's transient offscreen render-target and texture caches).
```c @@ -227,7 +229,7 @@ sequenceDiagram participant Raster as "Raster Thread" participant SC as "surfaceControl : AndroidSurfaceControl" - UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig{kDeferred}) + UI->>Emb: FlutterEngineInitialize(FlutterRendererConfig{kDeferred}, FlutterCompositor{avoid_backing_store_cache = true}) UI->>Emb: FlutterEngineRunInitialized() Emb-)Raster: LaunchShell() posts CreateImpellerContext Emb-)Raster: NotifyCreated() queues surface creation (FIFO, no latch) @@ -484,6 +486,13 @@ typedef enum { kFlutterPlatformViewMutationCapabilityPathConics = 1 << 0, } FlutterPlatformViewMutationCapability; +typedef struct { + // ... existing fields ... + /// Bitmask of `FlutterPlatformViewMutationCapability` values supported by + /// this compositor. + uint64_t platform_view_mutation_capabilities; +} FlutterCompositor; + typedef struct { size_t struct_size; FlutterPlatformViewIdentifier identifier; @@ -632,8 +641,10 @@ typedef bool (*FlutterOpenGLTextureFrameCallback)( size_t /* height */, FlutterOpenGLExternalTexture* /* texture out */); -// Appended to FlutterOpenGLRendererConfig: -FlutterOpenGLTextureFrameCallback external_texture_frame_callback; +typedef struct { + // ... existing fields ... + FlutterOpenGLTextureFrameCallback external_texture_frame_callback; +} FlutterOpenGLRendererConfig; ``` ### Accessibility and Semantics Parity @@ -691,18 +702,30 @@ typedef enum { kFlutterSemanticsRoleOption = 35, } FlutterSemanticsRole; -// Appended to FlutterSemanticsNode2 in embedder.h: -int32_t max_value_length; -int32_t current_value_length; -const char* link_url; -const char* locale; -const char* min_value; -const char* max_value; -FlutterTransformation hit_test_transform; -FlutterSemanticsRole role; -/// The number of entries in `children_in_hit_test_order` (which may differ -/// from `child_count`, the length of `children_in_traversal_order`). -size_t hit_test_child_count; +typedef struct { + // ... existing fields ... + + /// The maximum character length of an editable text field, or -1 if none. + int32_t max_value_length; + /// The current character length of an editable text field, or -1 if none. + int32_t current_value_length; + /// The target URL when the node represents a link, or NULL if none. + const char* link_url; + /// The BCP-47 locale tag for the node's text, or NULL if inherited. + const char* locale; + /// The minimum possible value for a range or slider node, or NULL if none. + const char* min_value; + /// The maximum possible value for a range or slider node, or NULL if none. + const char* max_value; + /// The transform from this node's coordinate system to its parent's + /// coordinate system for hit testing. + FlutterTransformation hit_test_transform; + /// The semantic role of this node. + FlutterSemanticsRole role; + /// The number of entries in `children_in_hit_test_order` (which may differ + /// from `child_count`, the length of `children_in_traversal_order`). + size_t hit_test_child_count; +} FlutterSemanticsNode2; ``` ### Dart Deferred Library Loading @@ -783,9 +806,11 @@ FlutterEngineResult FlutterEngineNotifyDartDeferredLibraryLoadError( FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterDartDeferredLibraryLoadError* error); -// Appended to FlutterProjectArgs: -FlutterRequestDartDeferredLibraryCallback - request_dart_deferred_library_callback; +typedef struct { + // ... existing fields ... + FlutterRequestDartDeferredLibraryCallback + request_dart_deferred_library_callback; +} FlutterProjectArgs; ``` ```mermaid @@ -848,9 +873,11 @@ typedef struct { VoidCallback destruction_callback; } FlutterCustomAssetResolver; -// Appended to FlutterProjectArgs: -size_t custom_asset_resolvers_count; -const FlutterCustomAssetResolver** custom_asset_resolvers; +typedef struct { + // ... existing fields ... + size_t custom_asset_resolvers_count; + const FlutterCustomAssetResolver** custom_asset_resolvers; +} FlutterProjectArgs; ``` ### Multi-Engine Spawning and Add-to-App @@ -986,10 +1013,12 @@ typedef void (*FlutterVMServiceUriCallback)( FLUTTER_EXPORT FlutterEngineResult FlutterEnginePrefetchDefaultFontManager(void); -// Appended to FlutterProjectArgs: -size_t image_decoders_count; -const FlutterImageDecoderRegistration** image_decoders; -FlutterVMServiceUriCallback vm_service_uri_callback; +typedef struct { + // ... existing fields ... + size_t image_decoders_count; + const FlutterImageDecoderRegistration** image_decoders; + FlutterVMServiceUriCallback vm_service_uri_callback; +} FlutterProjectArgs; ``` ### Platform Channel Messaging, Background Handlers, and Task Queues @@ -1036,12 +1065,14 @@ flowchart LR ``` ```c - // Appended to FlutterProjectArgs: +typedef struct { + // ... existing fields ... /// If true, the engine invokes `platform_message_callback` directly on the /// thread that sent the message (typically the UI thread) instead of posting /// a task to the platform thread. Defaults to false, which preserves the /// existing platform thread delivery. bool enable_direct_platform_message_dispatch; +} FlutterProjectArgs; ``` ### Viewport Metrics, Pointer Events, and Non-Linear Font Scaling @@ -1108,9 +1139,9 @@ typedef struct { } FlutterDisplayFeature; ``` -Appended to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110): +typedef struct { + // ... existing fields ... -```c /// Physical window padding (for example, status bar, navigation bar, cutouts). FlutterEdgeInsets physical_padding; @@ -1132,13 +1163,14 @@ Appended to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blo /// Array of pointers to physical display features (foldable hinges, cutouts). const FlutterDisplayFeature** display_features; +} FlutterWindowMetricsEvent; ``` #### `FlutterPointerEvent` In-Place Extensions -Appended to [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447): - ```c +typedef struct { + // ... existing fields ... double distance; double distance_max; double size; @@ -1150,6 +1182,7 @@ Appended to [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762 double tilt; int64_t platform_data; int64_t embedder_id; +} FlutterPointerEvent; ``` #### Android 14+ Non-Linear Font Scaling @@ -1168,10 +1201,13 @@ typedef bool (*FlutterScaleFontSizeCallback)( double* scaled_font_size_out, void* user_data); -// Appended to FlutterProjectArgs. Optional; if NULL, the engine falls back to -// linear scaling. Only invoked after the embedder sets a configurationId -// through a `flutter/settings` message. -FlutterScaleFontSizeCallback scale_font_size_callback; +typedef struct { + // ... existing fields ... + /// Optional; if NULL, the engine falls back to linear scaling. Only invoked + /// after the embedder sets a configurationId through a `flutter/settings` + /// message. + FlutterScaleFontSizeCallback scale_font_size_callback; +} FlutterProjectArgs; ``` ## Target Architecture and Design Principles From c9e97d7997eabeb3b1981301f3f4f54f635cc297 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 21 Oct 2026 22:46:47 +0000 Subject: [PATCH 36/43] lint fixes --- ...lutter-android-embedder-c-api-migration.md | 127 +++++++++++++----- 1 file changed, 93 insertions(+), 34 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 5528539..ab97b7c 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -134,6 +134,7 @@ sequenceDiagram | **Asset Management & Packaging**
(1 header) | [`//flutter/assets`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets) | [`assets/asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h) | Resolving assets and kernel blobs packaged within the Android APK via [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L17). | | **GPU Backends & Surface Delegates**
(5 headers) | [`//flutter/shell/gpu`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu)
[`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | [`shell/gpu/gpu_surface_gl_impeller.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu/gpu_surface_gl_impeller.h)
[`gpu_surface_vulkan_impeller.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/gpu/gpu_surface_vulkan_impeller.h)
[`vulkan/vulkan_native_surface_android.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan/vulkan_native_surface_android.h) | Implementing platform window surface delegates for OpenGL and Vulkan render pipelines. | | **Foundational Utilities & Concurrency**
(24 headers) | [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | [`fml/raster_thread_merger.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/raster_thread_merger.h)
[`fml/task_runner.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/task_runner.h)
[`fml/message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/message_loop.h)
[`fml/concurrent_message_loop.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/concurrent_message_loop.h)
[`fml/platform/android/jni_util.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/jni_util.h)
[`fml/platform/android/scoped_java_ref.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/platform/android/scoped_java_ref.h) | Message loops, JNI handle wrappers, task scheduling, and raster thread leasing. | +
## Detailed Design @@ -143,10 +144,15 @@ The following sections specify the [`embedder.h`](https://github.com/flutter/flu ### Initialization > [!IMPORTANT] -> - **Embedder API additions (`embedder.h`)**: Adds `kDeferred` (`FlutterDeferredRendererConfig`) and `FlutterRendererConfigResolveCallback` to [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055). +> +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterRendererType::kDeferred` (`FlutterDeferredRendererConfig`) on [`FlutterRendererConfig`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1055) +> - `FlutterRendererConfigResolveCallback` > - **Behavioral changes**: -> - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36), [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h), and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h) move [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) creation from their UI-thread constructors into `PlatformViewEmbedder::SetupImpellerContext()` on the raster thread, and `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the FIFO raster task runner instead of blocking the UI thread on a latch. -> - `AndroidSurfaceControl` performs all Android Vulkan eligibility checks inside `resolve_callback` before creating a `VkDevice` and returns either `kVulkan` or `kOpenGL` in a single call. To make those checks available without depending on `//flutter/impeller`, the Android GPU blocklist in [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L379-L431) and the required Android Vulkan capability checks in [`CapabilitiesVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529) are moved into `AndroidSurfaceControl` alongside the existing Android system-property and SoC checks from [`android_context_dynamic_impeller.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L23-L119). +> - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36), [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h), and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h) move [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) creation from their UI-thread constructors into `PlatformViewEmbedder::SetupImpellerContext()` on the raster thread. +> - `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the FIFO raster task runner instead of blocking the UI thread on a latch. +> - `AndroidSurfaceControl` performs all Android Vulkan eligibility checks inside `resolve_callback` before creating a `VkDevice` to return `kVulkan` or `kOpenGL` in a single call. +> - The Android GPU blocklist in [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L379-L431) and the required Android Vulkan capability checks in [`CapabilitiesVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529) move into `AndroidSurfaceControl` alongside the Android system-property and SoC checks from [`android_context_dynamic_impeller.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L23-L119). > - During `FlutterEngineInitialize`, the Android embedder configures [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2266-L2322) (`FlutterProjectArgs.compositor`) with [`avoid_backing_store_cache = true`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2312) to turn off the engine's backing store cache ([`EmbedderRenderTargetCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_render_target_cache.h)) so the Android embedder is responsible for managing surfaces.
@@ -160,6 +166,7 @@ The following sections specify the [`embedder.h`](https://github.com/flutter/flu 1. **Physical device and driver blocklist ([`driver_info_vk.cc#L20-L431`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L20-L431))**: Inspecting `VkPhysicalDeviceProperties` (`vendorID`, `deviceID`, `driverVersion`, `apiVersion`, `deviceType`, `deviceName`) to block Adreno <= 650 (`GetAdrenoVersion`), Huawei Maleoon (`0x19E5`), Samsung Xclipse (`0x144D`) with Vulkan < 1.3, PowerVR series older than `kCXT` (`GetPowerVRVersion`), Pixel 10 (`0x71061212`) with driver < `6794074`, and SwiftShader CPU emulators (`IsEmulator`). 2. **Required Vulkan extensions and physical device capabilities ([`capabilities_vk.cc#L98-L529`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529))**: Verifying `VK_KHR_surface` and `VK_KHR_android_surface`; verifying required Android device extensions (`VK_KHR_swapchain`, `VK_ANDROID_external_memory_android_hardware_buffer`, `VK_KHR_sampler_ycbcr_conversion`, `VK_KHR_external_memory`, `VK_EXT_queue_family_foreign`, `VK_KHR_dedicated_allocation`); checking `VK_FORMAT_R8G8B8A8_UNORM` color and `D32_SFLOAT_S8_UINT` / `D24_UNORM_S8_UINT` depth-stencil attachment support, `1x`/`4x` sample counts, graphics/compute/transfer queue families, and enabling `samplerYcbcrConversion` and `uniformAndStorageBuffer16BitAccess` when calling `vkCreateDevice`. - **Disabling backing store caches (`avoid_backing_store_cache = true`) so the Android embedder owns surfaces**: In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89) whenever an `ANativeWindow*` attached or detached because `AndroidSurface` rendered directly into the window. Passing [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2266-L2322) with `avoid_backing_store_cache = true` during `FlutterEngineInitialize` configures the engine's internal GPU surface as context-only (`render_to_surface = false`, [#193000](https://github.com/flutter/flutter/pull/193000)) and bypasses [`EmbedderRenderTargetCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_render_target_cache.h). The engine requests each backing store (`FlutterBackingStoreConfig`) inside the frame that uses it ([`embedder_external_view_embedder.cc#L561-L572`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L561-L572)) and collects it as soon as `present_view_callback` returns ([`#L627-L643`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L627-L643)). When no window is attached or the OS invalidates the surface mid-frame, `create_backing_store_callback` returns `false` and the engine skips that layer ([`#L261-L265`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L261-L265)). `AndroidSurfaceControl` therefore manages `ANativeWindow*` swapchains and overlay surfaces across `surfaceCreated`, `surfaceChanged`, and `surfaceDestroyed` (and calls [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) on `onTrimMemory` and `surfaceDestroyed` to release Impeller's transient offscreen render-target and texture caches). +
```c @@ -257,7 +264,10 @@ sequenceDiagram ### Platform Views > [!IMPORTANT] -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterBackingStoreConfig.is_root_layer` (`bool`) and `FlutterCompositor.merge_raster_thread_on_platform_views` (`bool`). +> +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterBackingStoreConfig.is_root_layer` (`bool`) +> - `FlutterCompositor.merge_raster_thread_on_platform_views` (`bool`) > - **Behavioral changes**: > - Replaces [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) with [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322), using `is_root_layer` to distinguish the bottom-most background layer from full-screen overlay layers above a platform view. > - [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) implements `SupportsDynamicThreadMerging()` and `PostPrerollAction()` when `merge_raster_thread_on_platform_views` is `true`, dynamically merging the raster task runner onto the platform thread during Hybrid Composition (HC) frames. @@ -267,6 +277,7 @@ sequenceDiagram - **Root vs. overlay backing stores (`is_root_layer`)**: During `create_backing_store_callback(config, store_out)`, `config->size` equals the full window size for both the bottom-most background layer and any full-screen overlay layer above a platform view (`[Flutter_Root, PlatformView, Flutter_Overlay]`), and `present_view_callback` runs only after all layers are rendered. `is_root_layer` lets the embedder bind the bottom-most layer directly to the on-screen `ANativeWindow*` swapchain or FBO (`avoid_backing_store_cache = true`) while allocating off-screen overlay surfaces ([`SurfacePool`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h) / [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L38)) for layers above a platform view. - **Dynamic thread merging (`merge_raster_thread_on_platform_views`)**: HCPP (`SurfaceControl`, API 34+ with Impeller Vulkan) is opt-in and `SurfaceView`-backed platform views (`VIEW_TYPES_REQUIRE_NON_TLHC`) cannot use TLHC or VD, so standard Hybrid Composition (HC) remains the default for non-TLHC views. In HC, [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L951-L1084) mutates live Android `View` instances (`FlutterMutatorView`, `FlutterImageView.convertToImageView()`) on the platform thread during frame presentation, which requires [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) to dynamically merge the raster task runner onto the platform thread. +
@@ -283,6 +294,7 @@ flowchart TD HasSurfaceView -- "Yes, TEXTURE_WITH_VIRTUAL_FALLBACK" --> VD["VD: configureForVirtualDisplay (External Texture C-API)"] HasSurfaceView -- "Yes, TEXTURE_WITH_HYBRID_FALLBACK" --> HC ``` +
```c @@ -398,10 +410,15 @@ sequenceDiagram ### Platform View Mutator Stack Serialization > [!IMPORTANT] -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterPath` (`FlutterPathFillType`, `FlutterPathVerb`), `kFlutterPlatformViewMutationTypeClipPath`, and `FlutterCompositor.platform_view_mutation_capabilities` (`FlutterPlatformViewMutationCapability`). +> +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterPath` (`FlutterPathFillType`, `FlutterPathVerb`) +> - `kFlutterPlatformViewMutationTypeClipPath` on `FlutterPlatformViewMutationType` (`FlutterPlatformViewMutation.clip_path`) +> - `FlutterCompositor.platform_view_mutation_capabilities` (`FlutterPlatformViewMutationCapability`) > - **Behavioral changes**: > - Arbitrary path clips serialize into an ABI-stable structure-of-arrays `FlutterPath` layout instead of exposing `SkPath` or `//flutter/flow` headers. -> - On Android API 34+ (`android.graphics.Path.conicTo`), the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` to receive exact conic path verbs (`kFlutterPathVerbConic`), and `AndroidMutatorsMapper` translates `FlutterPlatformView.mutations` into Java [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) and [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) transforms. +> - On Android API 34+ (`android.graphics.Path.conicTo`), the Android compositor sets `kFlutterPlatformViewMutationCapabilityPathConics` to receive exact conic path verbs (`kFlutterPathVerbConic`). +> - `AndroidMutatorsMapper` translates `FlutterPlatformView.mutations` into Java [`FlutterMutatorView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java#L32) and [`SurfaceControl.Transaction`](https://developer.android.com/reference/android/view/SurfaceControl.Transaction) transforms.
Why FlutterPlatformViewMutation needs FlutterPath and capability negotiation @@ -507,16 +524,23 @@ typedef struct { ### External Textures > [!IMPORTANT] -> To replace the internal `flutter::Texture` subclasses ([`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36) and [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33)) across both [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) and [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`GL_TEXTURE_EXTERNAL_OES`) pipelines: > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterVulkanYcbcrConversionInfo` (`FlutterVulkanComponentMapping`, `FlutterVulkanComponentSwizzle`) and `FlutterVulkanExternalTexture.ycbcr_conversion_info` (extending [`FlutterVulkanExternalTexture` from #188855](https://github.com/flutter/flutter/pull/188855)); adds `FlutterOpenGLExternalTexture` (with `FlutterTransformation uv_transform`) and `FlutterOpenGLRendererConfig.external_texture_frame_callback` (`FlutterOpenGLTextureFrameCallback`, deprecating `gl_external_texture_frame_callback`). -> - **Behavioral changes**: The Vulkan renderer samples multi-planar and vendor-specific external YCbCr camera and video frames (`VkSamplerYcbcrConversion`) without exposing Android `AHardwareBuffer` types in `embedder.h`, and the OpenGL ES renderer applies `SurfaceTexture.getTransformMatrix()` UV transforms while keeping [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) unchanged for backing store render targets. +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterVulkanYcbcrConversionInfo` (`FlutterVulkanComponentMapping`, `FlutterVulkanComponentSwizzle`) +> - `FlutterVulkanExternalTexture.ycbcr_conversion_info` (extending [`FlutterVulkanExternalTexture` from #188855](https://github.com/flutter/flutter/pull/188855)) +> - `FlutterOpenGLExternalTexture` (with `has_uv_transform` and `FlutterTransformation uv_transform`) +> - `FlutterOpenGLRendererConfig.external_texture_frame_callback` (`FlutterOpenGLTextureFrameCallback`, deprecating `gl_external_texture_frame_callback`) +> - **Behavioral changes**: +> - Replaces the internal `flutter::Texture` subclasses ([`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36) and [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33)) across [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) and [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`GL_TEXTURE_EXTERNAL_OES`). +> - The Vulkan renderer samples multi-planar and vendor-specific external YCbCr camera and video frames (`VkSamplerYcbcrConversion`) without exposing Android `AHardwareBuffer` types in `embedder.h`. +> - The OpenGL ES renderer applies `SurfaceTexture.getTransformMatrix()` UV transforms while keeping [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) unchanged for backing store render targets.
Why FlutterVulkanYcbcrConversionInfo and FlutterOpenGLExternalTexture are needed - **Vulkan YCbCr conversion**: Android hardware video decoders and camera pipelines produce multi-planar or vendor-proprietary YCbCr `AHardwareBuffer` frames (`VK_FORMAT_UNDEFINED` with a non-zero `VkAndroidHardwareBufferFormatPropertiesANDROID.externalFormat`). Sampling those `VkImage` handles in Impeller Vulkan requires a `VkSamplerYcbcrConversion` configured with the buffer's chroma offsets, range, model, and `externalFormat`, which [`FlutterVulkanExternalTexture`](https://github.com/flutter/flutter/pull/188855) previously lacked. - **OpenGL ES UV transformation**: On OpenGL ES, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float%5B%5D)) supplies a $4 \times 4$ UV crop and rotation matrix per frame, which [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) (a frozen struct without `struct_size` shared with `FlutterOpenGLBackingStore.texture`) cannot carry without breaking ABI compatibility. +
```mermaid @@ -650,16 +674,21 @@ typedef struct { ### Accessibility and Semantics Parity > [!IMPORTANT] -> To deliver the complete semantics tree to Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80) without including internal [`lib/ui/semantics/semantics_node.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h) headers: > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterSemanticsRole` and appends nine node fields (`max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count`) in place to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783). -> - **Behavioral changes**: `hit_test_child_count` separates the length of `children_in_hit_test_order` from `child_count` (`children_in_traversal_order`, which can differ when the framework grafts nodes such as `OverlayPortal`). `AndroidSemanticsMapper` consumes `FlutterSemanticsUpdate2` via `FlutterProjectArgs.update_semantics_callback2` to pack the direct `ByteBuffer` and string pool arrays expected by `AccessibilityBridge`, while accessibility actions route back through `FlutterEngineSendSemanticsAction`. +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterSemanticsRole` +> - Appends nine node fields (`max_value_length`, `current_value_length`, `link_url`, `locale`, `min_value`, `max_value`, `hit_test_transform`, `role`, and `hit_test_child_count`) in place to [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783) +> - **Behavioral changes**: +> - `hit_test_child_count` separates the length of `children_in_hit_test_order` from `child_count` (`children_in_traversal_order`, which can differ when the framework grafts nodes such as `OverlayPortal`). +> - `AndroidSemanticsMapper` consumes `FlutterSemanticsUpdate2` via `FlutterProjectArgs.update_semantics_callback2` to pack the direct `ByteBuffer` and string pool arrays expected by Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L80). +> - Accessibility actions route back through `FlutterEngineSendSemanticsAction`.
Why FlutterSemanticsNode2 is extended in place - **Missing node attributes**: Java [`AccessibilityBridge`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L2614-L2720) reads `maxValueLength`, `currentValueLength`, `linkUrl`, `locale`, `minValue`, `maxValue`, `hitTestTransform`, and `role` from [`flutter::SemanticsNode`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/semantics/semantics_node.h#L101), which were omitted from [`FlutterSemanticsNode2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1677-L1783). - **Separate child array counts (`hit_test_child_count`)**: [`EmbedderSemanticsUpdate2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc#L96-L108) previously used `child_count` (`children_in_traversal_order.size()`) as the length of both child arrays, even though `children_in_hit_test_order` can have a different length when the framework grafts nodes such as `OverlayPortal`. +
```c @@ -731,10 +760,16 @@ typedef struct { ### Dart Deferred Library Loading > [!IMPORTANT] -> To support Google Play Feature Delivery / split APK deferred loading through [`embedder.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h) without calling internal [`PlatformViewAndroid::LoadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L508-L514) methods: > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterRequestDartDeferredLibraryCallback` (`FlutterProjectArgs.request_dart_deferred_library_callback`), `FlutterDartDeferredLibrary` (with `destruction_callback`), `FlutterEngineLoadDartDeferredLibrary`, and `FlutterEngineNotifyDartDeferredLibraryLoadError` (`FlutterDartDeferredLibraryLoadError`). -> - **Behavioral changes**: The engine invokes `request_dart_deferred_library_callback` on the platform task runner when Dart requests a `loading_unit_id`, wraps the embedder's `snapshot_data` and `snapshot_instructions` buffers in a shared-ownership `fml::NonOwnedMapping`, and fires `destruction_callback` once the Dart VM releases both buffers. +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterRequestDartDeferredLibraryCallback` (`FlutterProjectArgs.request_dart_deferred_library_callback`) +> - `FlutterDartDeferredLibrary` (with `destruction_callback`) +> - `FlutterEngineLoadDartDeferredLibrary` +> - `FlutterEngineNotifyDartDeferredLibraryLoadError` (`FlutterDartDeferredLibraryLoadError`) +> - **Behavioral changes**: +> - The engine invokes `request_dart_deferred_library_callback` on the platform task runner when Dart requests a `loading_unit_id`. +> - The engine wraps the embedder's `snapshot_data` and `snapshot_instructions` buffers in a shared-ownership `fml::NonOwnedMapping`. +> - The engine fires `destruction_callback` once the Dart VM releases both buffers.
Why deferred library callbacks and destruction_callback are needed @@ -841,10 +876,15 @@ sequenceDiagram ### Custom Asset and Kernel Resolution > [!IMPORTANT] -> To resolve assets packaged inside APK or AAB archives without subclassing internal C++ [`flutter::AssetResolver`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h#L23) or extracting files to disk: > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterAssetMapping`, `FlutterCustomAssetResolver` (`FlutterAssetResolverGetAsMappingCallback`), and `FlutterProjectArgs.custom_asset_resolvers` (`custom_asset_resolvers_count`). -> - **Behavioral changes**: The engine's internal `CustomAssetResolver` wrapper returns `true` from `IsValidAfterAssetManagerChange()` so custom resolvers stay attached across hot reload and hot restart. [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) supplies a `FlutterCustomAssetResolver` that maps assets directly from the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) in `get_as_mapping_callback` and atomically swaps its `AAssetManager*` pointer inside `user_data` when `FlutterJNI.updateJavaAssetManager` runs after a split APK install. +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterAssetMapping` +> - `FlutterCustomAssetResolver` (`FlutterAssetResolverGetAsMappingCallback`) +> - `FlutterProjectArgs.custom_asset_resolvers` (`custom_asset_resolvers_count`) +> - **Behavioral changes**: +> - The engine's internal `CustomAssetResolver` wrapper returns `true` from `IsValidAfterAssetManagerChange()` so custom resolvers stay attached across hot reload and hot restart. +> - [`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26) supplies a `FlutterCustomAssetResolver` that maps assets directly from the NDK [`AAssetManager`](https://developer.android.com/ndk/reference/group/asset) in `get_as_mapping_callback`. +> - `APKAssetProvider` atomically swaps its `AAssetManager*` pointer inside `user_data` when `FlutterJNI.updateJavaAssetManager` runs after a split APK install.
Why FlutterCustomAssetResolver is needed @@ -883,10 +923,13 @@ typedef struct { ### Multi-Engine Spawning and Add-to-App > [!IMPORTANT] -> To support [`FlutterEngineGroup`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java#L41) and Add-to-App multi-engine sharing without calling internal [`Shell::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/shell.h#L204-L210) methods: > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterEngineSpawn` and `FlutterEngineSpawnInfo`. -> - **Behavioral changes**: `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285), spawning child `FlutterEngine` instances that automatically inherit the parent engine's Dart VM isolate group, `io_manager`, resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70), task runners, asset resolvers, and `FlutterProjectArgs` callbacks (invoked with the child engine's `user_data`). +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterEngineSpawnInfo` +> - `FlutterEngineSpawn` +> - **Behavioral changes**: +> - `AndroidEngineGroup` replaces [`AndroidShellHolder::Spawn`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L222-L285) by calling `FlutterEngineSpawn`. +> - Spawned child `FlutterEngine` instances inherit the parent engine's Dart VM isolate group, `io_manager`, resolved [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70), task runners, asset resolvers, and `FlutterProjectArgs` callbacks (invoked with the child engine's `user_data`).
Why FlutterEngineSpawn is exposed in embedder.h while FlutterEngineGroup remains the public Java/Objective-C API @@ -916,10 +959,16 @@ FlutterEngineResult FlutterEngineSpawn( ### Image Decoders / Font Prefetching / VM Service URI > [!IMPORTANT] -> To eliminate the remaining [`//flutter/lib/ui/painting/image_generator.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/painting/image_generator.h#L27), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt), and [`//flutter/runtime/dart_service_isolate.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime/dart_service_isolate.h#L22) inclusions in [`FlutterMain`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.h#L17) and [`FlutterJNI`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L108): > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.image_decoders` (`FlutterImageDecoderRegistration`, `FlutterImageDecoder`, `FlutterImageDecoderInfo`, `FlutterImageDecoderColorSpace`), `FlutterEnginePrefetchDefaultFontManager`, and `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`). -> - **Behavioral changes**: The Android embedder registers its single-frame fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats at priority `-1`) without subclassing `flutter::ImageGenerator`, and the engine keeps the encoded byte buffer alive until `destruction_callback` runs. `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists, and `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterProjectArgs.image_decoders` (`FlutterImageDecoderRegistration`, `FlutterImageDecoder`, `FlutterImageDecoderInfo`, `FlutterImageDecoderColorSpace`) +> - `FlutterEnginePrefetchDefaultFontManager` +> - `FlutterProjectArgs.vm_service_uri_callback` (`FlutterVMServiceUriInfo`) +> - **Behavioral changes**: +> - The Android embedder registers its single-frame fallback [`ImageDecoder`](https://developer.android.com/reference/android/graphics/ImageDecoder) (for HEIF and platform formats at priority `-1`) without subclassing `flutter::ImageGenerator`. +> - The engine keeps the encoded byte buffer alive until `FlutterImageDecoder.destruction_callback` runs. +> - `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists. +> - `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`.
Why these three C-APIs are needed @@ -927,6 +976,7 @@ FlutterEngineResult FlutterEngineSpawn( - [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L166-L171) registers [`AndroidImageGenerator`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_image_generator.h#L18) via `Shell::RegisterImageDecoder` so Android's NDK `AImageDecoder` can decode single-frame HEIF and platform image formats not handled by built-in engine codecs (while multi-frame formats such as GIF, animated WebP, and APNG are decoded by the engine's built-in priority-`0` codecs). - [`FlutterMain::PrefetchDefaultFontManager`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L211-L214) calls `txt::GetDefaultFontManager()` (`//flutter/txt`) during `FlutterLoader.initAsync` before any `FlutterEngine` instance exists. - [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L186-L195) includes `//flutter/runtime/dart_service_isolate.h` directly to register a callback for `FlutterJNI.getVMServiceUri()`. +
```c @@ -1024,10 +1074,14 @@ typedef struct { ### Platform Channel Messaging, Background Handlers, and Task Queues > [!IMPORTANT] -> To replace the internal [`PlatformMessageHandlerAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_message_handler_android.h#L18) subclass while preserving background `TaskQueue` dispatch and startup message ordering: > -> - **Embedder API additions (`embedder.h`)**: Adds `FlutterProjectArgs.enable_direct_platform_message_dispatch` (`bool`, defaulting to `false`). -> - **Behavioral changes**: When `enable_direct_platform_message_dispatch` is `true`, [`EmbedderPlatformMessageHandler`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) invokes `platform_message_callback` directly on the calling isolate's thread (and returns `false` from `DoesHandlePlatformMessageOnPlatformThread()`), so root-isolate messages avoid an extra main-`Looper` post and background-isolate messages route directly to [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) executors without bouncing through the Android main thread. +> - **Embedder API additions (`embedder.h`)**: +> - `FlutterProjectArgs.enable_direct_platform_message_dispatch` (`bool`, defaulting to `false`) +> - **Behavioral changes**: +> - When `enable_direct_platform_message_dispatch` is `true`, [`EmbedderPlatformMessageHandler`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc#L22-L36) invokes `platform_message_callback` directly on the calling isolate's thread. +> - `EmbedderPlatformMessageHandler::DoesHandlePlatformMessageOnPlatformThread()` returns `false` when `enable_direct_platform_message_dispatch` is `true`. +> - Root-isolate messages avoid an extra main-`Looper` post. +> - Background-isolate messages route directly to [`makeBackgroundTaskQueue()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java#L184-L190) executors without bouncing through the Android main thread.
Why enable_direct_platform_message_dispatch is needed @@ -1078,14 +1132,19 @@ typedef struct { ### Viewport Metrics, Pointer Events, and Non-Linear Font Scaling > [!IMPORTANT] -> To deliver Android window insets, display cutouts, foldable postures, touch/stylus pointer fields, and Android 14+ non-linear font scaling without internal [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) or [`PlatformView::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1023-L1024) overrides: > > - **Embedder API additions (`embedder.h`)**: -> - Adds `FlutterEdgeInsets`, `FlutterDisplayCornerRadii`, and `FlutterDisplayFeature` (`FlutterDisplayFeatureType`, `FlutterDisplayFeatureState`), and appends `physical_padding`, `physical_system_gesture_insets`, `physical_touch_slop`, `display_corner_radii`, and `display_features` (`display_features_count`) in place to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110). -> - Appends `distance`, `distance_max`, `size`, `radius_major`, `radius_minor`, `radius_min`, `radius_max`, `orientation`, `tilt`, `platform_data`, and `embedder_id` in place to [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447). -> - Adds `FlutterScaleFontSizeInfo` and `FlutterScaleFontSizeCallback` (`FlutterProjectArgs.scale_font_size_callback`). +> - `FlutterEdgeInsets` +> - `FlutterDisplayCornerRadii` +> - `FlutterDisplayFeature` (`FlutterDisplayFeatureType`, `FlutterDisplayFeatureState`) +> - Appends `physical_padding`, `physical_system_gesture_insets`, `physical_touch_slop`, `display_corner_radii`, and `display_features` (`display_features_count`) in place to [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) +> - Appends `distance`, `distance_max`, `size`, `radius_major`, `radius_minor`, `radius_min`, `radius_max`, `orientation`, `tilt`, `platform_data`, and `embedder_id` in place to [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447) +> - `FlutterScaleFontSizeInfo` +> - `FlutterScaleFontSizeCallback` (`FlutterProjectArgs.scale_font_size_callback`) > - **Behavioral changes**: -> - [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433) and [`AndroidTouchProcessor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L358-L479) populate the extended [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) and [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447) structs; `embedder.cc` reads the new fields using `SAFE_ACCESS(event, member, default_value)`. +> - [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433) populates the extended [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) struct. +> - [`AndroidTouchProcessor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L358-L479) populates the extended [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447) struct. +> - `embedder.cc` reads the new fields using `SAFE_ACCESS(event, member, default_value)`. > - `PlatformViewEmbedder::GetScaledFontSize` delegates to `FlutterProjectArgs.scale_font_size_callback` when set, preserving Android 14+ (API 34+) non-linear font scaling via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) with zero changes to [`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176) or `PlatformDispatcher`.
@@ -1094,6 +1153,7 @@ typedef struct { - [`PlatformViewAndroid::SetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L341) constructs an internal [`flutter::ViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h#L16) struct containing system bar padding, gesture insets, touch slop, display corner radii (API 31+), and foldable display features (`WindowInfoTracker`), which [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) previously lacked. - [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L358-L479) populates stylus tilt/orientation, touch contact radii, `platform_data` (multi-pointer batching flags), and `embedder_id` ([`MotionEventTracker`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L730-L800) lookup ID for platform view touch forwarding), which were missing from [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447). - On Android 14+ (API 34+), [`PlatformViewAndroid::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L516-L522) overrides `PlatformView::GetScaledFontSize` to evaluate `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, font_size, displayMetrics)` via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544). Adding `FlutterProjectArgs.scale_font_size_callback` wires `PlatformViewEmbedder::GetScaledFontSize` to that existing implementation without subclassing `PlatformView`. +
#### `FlutterWindowMetricsEvent` In-Place Extensions @@ -1137,7 +1197,6 @@ typedef struct { FlutterDisplayFeatureType type; FlutterDisplayFeatureState state; } FlutterDisplayFeature; -``` typedef struct { // ... existing fields ... @@ -1288,7 +1347,7 @@ sequenceDiagram | Target Component | Eliminated Internal Engine Dependencies | Embedder C-API Additions Used | Architectural Role | | :--- | :--- | :--- | :--- | -| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app) (`FlutterEngineSpawnInfo`)
- [`FlutterProjectArgs.enable_direct_platform_message_dispatch`](#platform-channel-messaging-background-handlers-and-task-queues)
- [`FlutterProjectArgs.request_dart_deferred_library_callback`](#dart-deferred-library-loading), `FlutterEngineLoadDartDeferredLibrary`, `FlutterEngineNotifyDartDeferredLibraryLoadError`
- [`FlutterProjectArgs.image_decoders`](#image-decoders-fonts-and-vm-uri) (`FlutterImageDecoderRegistration`), `FlutterEnginePrefetchDefaultFontManager`, `FlutterProjectArgs.vm_service_uri_callback` | Owns the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and `FlutterEngineProcTable`; replaces `AndroidShellHolder` and `PlatformViewAndroid`. | +| **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app) (`FlutterEngineSpawnInfo`)
- [`FlutterProjectArgs.enable_direct_platform_message_dispatch`](#platform-channel-messaging-background-handlers-and-task-queues)
- [`FlutterProjectArgs.request_dart_deferred_library_callback`](#dart-deferred-library-loading), `FlutterEngineLoadDartDeferredLibrary`, `FlutterEngineNotifyDartDeferredLibraryLoadError`
- [`FlutterProjectArgs.image_decoders`](#image-decoders--font-prefetching--vm-service-uri) (`FlutterImageDecoderRegistration`), `FlutterEnginePrefetchDefaultFontManager`, `FlutterProjectArgs.vm_service_uri_callback` | Owns the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and `FlutterEngineProcTable`; replaces `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | - [`kDeferred`](#initialization) (`FlutterDeferredRendererConfig`, `FlutterRendererConfigResolveCallback`) | Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` and resolves the renderer asynchronously during startup. | | **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | - [`FlutterCompositor.merge_raster_thread_on_platform_views`](#platform-views) and `FlutterBackingStoreConfig.is_root_layer`
- [`FlutterCompositor.platform_view_mutation_capabilities`](#platform-view-mutator-stack-serialization), `FlutterPlatformView.mutations`, `FlutterPlatformViewMutation`, `FlutterPath` | Unifies HC, HCPP (`SurfaceControl`), TLHC, and VD behind `FlutterCompositor` and deletes `external_view_embedder/`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) | - [`FlutterVulkanExternalTexture`](#external-textures) (`ycbcr_conversion_info`), `FlutterVulkanYcbcrConversionInfo`, `FlutterVulkanTextureFrameCallback`
- [`FlutterOpenGLExternalTexture`](#external-textures) (`has_uv_transform`, `uv_transform`), `FlutterOpenGLTextureFrameCallback` | Imports zero-copy `AHardwareBuffer` (`VkImage` / `EGLImageKHR`) and `SurfaceTexture` UV transforms without subclassing `flutter::Texture`. | From af956fd194c645bd06459a80db40746e66fbec57 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 22 Oct 2026 15:53:15 +0000 Subject: [PATCH 37/43] Update to 4x4 uv matrix --- ...00-flutter-android-embedder-c-api-migration.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index ab97b7c..4d48324 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -528,7 +528,7 @@ typedef struct { > - **Embedder API additions (`embedder.h`)**: > - `FlutterVulkanYcbcrConversionInfo` (`FlutterVulkanComponentMapping`, `FlutterVulkanComponentSwizzle`) > - `FlutterVulkanExternalTexture.ycbcr_conversion_info` (extending [`FlutterVulkanExternalTexture` from #188855](https://github.com/flutter/flutter/pull/188855)) -> - `FlutterOpenGLExternalTexture` (with `has_uv_transform` and `FlutterTransformation uv_transform`) +> - `FlutterOpenGLExternalTexture` (with `has_uv_transform` and column-major `float uv_transform[16]`) > - `FlutterOpenGLRendererConfig.external_texture_frame_callback` (`FlutterOpenGLTextureFrameCallback`, deprecating `gl_external_texture_frame_callback`) > - **Behavioral changes**: > - Replaces the internal `flutter::Texture` subclasses ([`ImageExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/image_external_texture.h#L36) and [`SurfaceTextureExternalTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/surface_texture_external_texture.h#L33)) across [`SurfaceProducer`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/view/TextureRegistry.java#L21) (`AHardwareBuffer`) and [`SurfaceTexture`](https://developer.android.com/reference/android/graphics/SurfaceTexture) (`GL_TEXTURE_EXTERNAL_OES`). @@ -539,7 +539,7 @@ typedef struct { Why FlutterVulkanYcbcrConversionInfo and FlutterOpenGLExternalTexture are needed - **Vulkan YCbCr conversion**: Android hardware video decoders and camera pipelines produce multi-planar or vendor-proprietary YCbCr `AHardwareBuffer` frames (`VK_FORMAT_UNDEFINED` with a non-zero `VkAndroidHardwareBufferFormatPropertiesANDROID.externalFormat`). Sampling those `VkImage` handles in Impeller Vulkan requires a `VkSamplerYcbcrConversion` configured with the buffer's chroma offsets, range, model, and `externalFormat`, which [`FlutterVulkanExternalTexture`](https://github.com/flutter/flutter/pull/188855) previously lacked. -- **OpenGL ES UV transformation**: On OpenGL ES, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float%5B%5D)) supplies a $4 \times 4$ UV crop and rotation matrix per frame, which [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) (a frozen struct without `struct_size` shared with `FlutterOpenGLBackingStore.texture`) cannot carry without breaking ABI compatibility. +- **OpenGL ES UV transformation**: On OpenGL ES, [`SurfaceTexture.getTransformMatrix()`](https://developer.android.com/reference/android/graphics/SurfaceTexture#getTransformMatrix(float%5B%5D)) supplies a column-major $4 \times 4$ `float[16]` UV crop and rotation matrix per frame, which [`FlutterOpenGLTexture`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L519-L540) (a frozen struct without `struct_size` shared with `FlutterOpenGLBackingStore.texture`) cannot carry without breaking ABI compatibility.
@@ -559,7 +559,7 @@ flowchart LR subgraph CAPI["embedder.h External Texture Callbacks"] VK_OUT["FlutterVulkanExternalTexture (+ optional FlutterVulkanYcbcrConversionInfo)"] - GL_OUT["FlutterOpenGLExternalTexture (+ FlutterTransformation uv_transform)"] + GL_OUT["FlutterOpenGLExternalTexture (+ float uv_transform[16])"] end SP -- "Impeller Vulkan" --> VK_AHB --> VK_OUT @@ -652,10 +652,11 @@ typedef struct { /// Whether `uv_transform` is set. When false (including zero-initialized /// structs), the texture is sampled with the identity UV transformation. bool has_uv_transform; - /// Transformation applied to normalized texture coordinates before - /// sampling (for example, from SurfaceTexture.getTransformMatrix()) when - /// `has_uv_transform` is true. - FlutterTransformation uv_transform; + /// A 4x4 column-major OpenGL ES transformation matrix applied to + /// homogeneous normalized texture coordinates `(s, t, 0, 1)^T` before + /// sampling (matching the `float[16]` layout written by Android's + /// `SurfaceTexture.getTransformMatrix()`) when `has_uv_transform` is true. + float uv_transform[16]; } FlutterOpenGLExternalTexture; typedef bool (*FlutterOpenGLTextureFrameCallback)( From d2e7323b67641df14fa4200694dc11d40b261f07 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 22 Oct 2026 16:16:58 +0000 Subject: [PATCH 38/43] Adding alternatives sections to track decision making - but keep it hidden :) --- ...lutter-android-embedder-c-api-migration.md | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 4d48324..d331300 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -261,6 +261,15 @@ sequenceDiagram UI->>Emb: FlutterEngineScheduleFrame(engine) ``` +
+Alternatives + +- Probing Vulkan on a Java background thread in `FlutterLoader.initAsync` and caching the result in `SharedPreferences` was rejected because it duplicates Vulkan initialization in Java/JNI before `FlutterEngineInitialize` and cached driver eligibility can become stale across system OTA updates. +- Letting `ContextVK::Create` reject a `kVulkan` renderer config and re-invoke `resolve_callback` was rejected in favor of moving the `DriverInfoVK` blocklist and `CapabilitiesVK` checks into `AndroidSurfaceControl` so `resolve_callback` resolves `kVulkan` or `kOpenGL` in a single pass. +- Adding `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` C-API functions for `ANativeWindow*` lifecycle was rejected because setting `FlutterCompositor.avoid_backing_store_cache = true` and calling `FlutterEngineNotifyLowMemoryWarning` already lets `AndroidSurfaceControl` own surface creation and teardown without new `embedder.h` entry points. + +
+ ### Platform Views > [!IMPORTANT] @@ -407,6 +416,14 @@ sequenceDiagram Embedder->>Embedder: Return buffer to AndroidSurfaceControl for recycling ``` +
+Alternatives + +- Running Hybrid Composition (HC) with unmerged threads and blocking the raster thread on a platform-thread latch during `present_view_callback` was rejected because `FlutterView.convertToImageView()` on the first HC frame calls `NotifySurfaceWindowChanged`, which deadlocks if the raster thread is blocked waiting on the platform thread. +- Adding a `synchronization_fence_fd` field to `FlutterBackingStorePresentInfo` for HCPP was rejected because `AndroidSurfaceControl` owns the `VkDevice` and `VkQueue` and can submit and export its own `VkSemaphore` via `vkGetSemaphoreFdKHR` inside `present_view_callback`. + +
+ ### Platform View Mutator Stack Serialization > [!IMPORTANT] @@ -521,6 +538,15 @@ typedef struct { } FlutterPlatformView; ``` +
+Alternatives + +- Approximating arbitrary path clips with `bounds` (`kFlutterPlatformViewMutationTypeClipRect`) was rejected because legacy Android HC and HCPP already clip platform views to arbitrary paths. +- Using an array of tagged union command structs (`FlutterPathCommand`) instead of structure-of-arrays (`FlutterPath`) was rejected because a union per verb pads every `moveTo` and `lineTo` to the size of a cubic segment and prevents bulk-copying verb and point buffers. +- Unconditionally converting conics to quadratic Beziers or unconditionally emitting conics to all embedders was rejected in favor of `kFlutterPlatformViewMutationCapabilityPathConics` so Android API 34+ uses native `Path.conicTo` while older Android versions and other embedders receive engine-approximated quadratics. + +
+ ### External Textures > [!IMPORTANT] @@ -672,6 +698,15 @@ typedef struct { } FlutterOpenGLRendererConfig; ``` +
+Alternatives + +- Adding Android-specific `AHardwareBuffer*` callbacks to `embedder.h` was rejected to keep `embedder.h` platform-agnostic by extending `FlutterVulkanExternalTexture` with `FlutterVulkanYcbcrConversionInfo` instead. +- Appending `uv_transform` to `FlutterOpenGLTexture` was rejected because `FlutterOpenGLTexture` has no `struct_size` field and is embedded by value inside `FlutterOpenGLBackingStore`, so growing it would break ABI compatibility. +- Using `FlutterTransformation` ($3 \times 3$ `double` matrix) for `uv_transform` was rejected in favor of column-major `float uv_transform[16]` with `bool has_uv_transform` so the embedder can pass `SurfaceTexture.getTransformMatrix(float[16])` directly to Impeller's `DlMatrix` without element swizzling or `float`-to-`double` conversion. + +
+ ### Accessibility and Semantics Parity > [!IMPORTANT] @@ -758,6 +793,13 @@ typedef struct { } FlutterSemanticsNode2; ``` +
+Alternatives + +- Introducing a new `FlutterSemanticsNode3` struct and `FlutterSemanticsUpdate3` callback was rejected because `FlutterSemanticsNode2` begins with `size_t struct_size` and is passed as an array of pointers (`const FlutterSemanticsNode2** nodes`) in `FlutterSemanticsUpdate2`, allowing new fields to be appended in place without breaking ABI compatibility. + +
+ ### Dart Deferred Library Loading > [!IMPORTANT] @@ -775,7 +817,7 @@ typedef struct {
Why deferred library callbacks and destruction_callback are needed -When Dart executes `loadLibrary()` on a deferred import, the engine calls `PlatformView::RequestDartDeferredLibrary(loading_unit_id)`, which [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36) did not expose in `embedder.h`. Once Play Feature Delivery installs the split APK and [`FlutterJNI.loadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1395-L1486) resolves the `_kDartIsolateSnapshotData` and `_kDartIsolateSnapshotInstructions` symbols, the Dart VM registers those pages directly in the isolate group heap without copying them, so `FlutterDartDeferredLibrary.destruction_callback` notifies the embedder when the engine releases both mappings. +When Dart executes `loadLibrary()` on a deferred import, the engine calls `PlatformView::RequestDartDeferredLibrary(loading_unit_id)`, which [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36) did not expose in `embedder.h`. Once Play Feature Delivery installs the split APK and [`FlutterJNI.loadDartDeferredLibrary`](https://github.com/flutter/flutter/blob/4762dec4fc9072dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L1395-L1486) resolves the `_kDartIsolateSnapshotData` and `_kDartIsolateSnapshotInstructions` symbols, the Dart VM registers those pages directly in the isolate group heap without copying them, so `FlutterDartDeferredLibrary.destruction_callback` notifies the embedder when the engine releases both mappings.
```c @@ -874,6 +916,14 @@ sequenceDiagram end ``` +
+Alternatives + +- Freeing or `dlclose`-ing the split shared library immediately after `FlutterEngineLoadDartDeferredLibrary` returns was rejected because the Dart VM maps `snapshot_data` and `snapshot_instructions` directly into the isolate group heap without copying them, so the mapped memory must remain valid until the isolate group shuts down and invokes `destruction_callback`. +- Having the engine open split APK shared libraries directly by file path was rejected because Play Feature Delivery split APK installation, path resolution, and `dlopen` symbol lookup are Android-specific concerns managed by `PlayStoreDeferredComponentManager` and `FlutterJNI`. + +
+ ### Custom Asset and Kernel Resolution > [!IMPORTANT] @@ -921,6 +971,15 @@ typedef struct { } FlutterProjectArgs; ``` +
+Alternatives + +- Keeping `//flutter/assets` as an allowed C++ dependency of the Android embedder was rejected because subclassing `flutter::AssetResolver` links the Android embedder directly to internal engine C++ classes and breaks C-API isolation. +- Extracting APK assets to the device filesystem on first launch and passing `FlutterProjectArgs.assets_path` was rejected due to first-launch disk I/O latency and duplicate storage usage. +- Adding runtime `FlutterEngineRegisterAssetResolver` or `FlutterEngineUpdateAssetResolverByType` functions was rejected because `CustomAssetResolver` stays valid across asset manager changes (`IsValidAfterAssetManagerChange() == true`) and `APKAssetProvider` can atomically swap its `AAssetManager*` pointer inside `user_data` when split APKs are installed. + +
+ ### Multi-Engine Spawning and Add-to-App > [!IMPORTANT] @@ -957,6 +1016,14 @@ FlutterEngineResult FlutterEngineSpawn( FLUTTER_API_SYMBOL(FlutterEngine)* spawned_engine_out); ``` +
+Alternatives + +- Introducing a `FlutterEngineGroup` handle and lifecycle API in `embedder.h` was rejected because the C++ engine has no `EngineGroup` class (`Shell::Spawn` clones directly from an existing `Shell` via `std::shared_ptr`), so a C-API group object would duplicate the collection wrapper already in `FlutterEngineGroup.java`. +- Accepting a separate `FlutterRendererConfig` in `FlutterEngineSpawnInfo` was rejected because spawned engines share the parent engine's `impeller::Context` and `io_manager`, so a per-child renderer configuration would either be ignored or create a conflicting GPU context. + +
+ ### Image Decoders / Font Prefetching / VM Service URI > [!IMPORTANT] @@ -1072,6 +1139,14 @@ typedef struct { } FlutterProjectArgs; ``` +
+Alternatives + +- Adding multi-frame animation callbacks (`get_frame_count`, `get_frame_info`) to `FlutterImageDecoder` was rejected because the engine's built-in priority-`0` codecs already decode GIF, animated WebP, and APNG, while Android's fallback `AImageDecoder` (`priority = -1`) only decodes single-frame platform formats such as HEIF. +- Adding `FlutterEngineInitializeCallbackCache` and `FlutterEngineGetCallbackInformation` to `embedder.h` for `FlutterCallbackInformation` was rejected because the `{name, class_name, library_path}` JSON file cache has no interaction with `Shell` or the Dart VM and can live inside the Android embedder or `//flutter/fml`. + +
+ ### Platform Channel Messaging, Background Handlers, and Task Queues > [!IMPORTANT] @@ -1130,6 +1205,14 @@ typedef struct { } FlutterProjectArgs; ``` +
+Alternatives + +- Changing `EmbedderPlatformMessageHandler` to dispatch on the calling thread for all embedders by default was rejected because existing desktop and custom embedders assume `platform_message_callback` is always invoked on the platform thread. +- Adding a `does_handle_platform_messages_on_platform_thread` boolean that defaults to `true` was rejected because zero-initialized `FlutterProjectArgs` structs set boolean fields to `false`, which would silently change the default behavior for existing callers. + +
+ ### Viewport Metrics, Pointer Events, and Non-Linear Font Scaling > [!IMPORTANT] @@ -1270,6 +1353,14 @@ typedef struct { } FlutterProjectArgs; ``` +
+Alternatives + +- Passing `const FlutterDisplayFeature* display_features` as a contiguous value array instead of `const FlutterDisplayFeature**` was rejected because indexing a contiguous array of structs uses the compile-time `sizeof(FlutterDisplayFeature)`, which would break ABI compatibility if fields are appended to `FlutterDisplayFeature` in the future. +- Serializing Android 14+ non-linear font-scale lookup tables over the `flutter/settings` platform channel (`fontScaleCurves`) and interpolating in Dart was rejected in favor of `FlutterProjectArgs.scale_font_size_callback`, which delegates directly to `FlutterJNI.getScaledFontSize` (`TypedValue.applyDimension`) with zero changes to `SettingsChannel.java` or `PlatformDispatcher`. + +
+ ## Target Architecture and Design Principles > [!IMPORTANT] From 8a93880dcb9b8225904e888b21affce4cfdda3c5 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 22 Oct 2026 16:55:43 +0000 Subject: [PATCH 39/43] Add back some documentation on work needed for Callback Caches. --- rfc/410.0000-flutter-android-embedder-c-api-migration.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index d331300..a108dd8 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -1037,13 +1037,16 @@ FlutterEngineResult FlutterEngineSpawn( > - The engine keeps the encoded byte buffer alive until `FlutterImageDecoder.destruction_callback` runs. > - `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists. > - `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. +> - The non-Dart `{name, class_name, library_path}` JSON cache table in [`DartCallbackCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) moves from [`//flutter/lib/ui/plugins/callback_cache.{h,cc}`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h) into [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) (keeping only `LookupDartClosure` in `//flutter/lib/ui`). +> - `DartCallbackCache` lazily loads `flutter_callback_cache.json` from [`fml::paths::GetCachesDirectory()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/paths.h#L27) on first lookup or insert.
-Why these three C-APIs are needed +Why these three C-APIs and the DartCallbackCache move are needed - [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L166-L171) registers [`AndroidImageGenerator`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_image_generator.h#L18) via `Shell::RegisterImageDecoder` so Android's NDK `AImageDecoder` can decode single-frame HEIF and platform image formats not handled by built-in engine codecs (while multi-frame formats such as GIF, animated WebP, and APNG are decoded by the engine's built-in priority-`0` codecs). - [`FlutterMain::PrefetchDefaultFontManager`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L211-L214) calls `txt::GetDefaultFontManager()` (`//flutter/txt`) during `FlutterLoader.initAsync` before any `FlutterEngine` instance exists. - [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L186-L195) includes `//flutter/runtime/dart_service_isolate.h` directly to register a callback for `FlutterJNI.getVMServiceUri()`. +- [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143-L157) and [`PlatformViewAndroid::LookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L483-L486) include `//flutter/lib/ui/plugins/callback_cache.h` only to access the `{name, class_name, library_path}` JSON file map, which has no dependency on `dart:ui` or `dart_api.h` (as noted by the existing `TODO` in [`flutter_main.cc#L143`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143)).
From 24b7f80c6650167a48aade095065a45b2c81c136 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 22 Oct 2026 17:12:16 +0000 Subject: [PATCH 40/43] Added link to description of the Callback cache. --- ...0-flutter-android-embedder-c-api-migration.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index a108dd8..4a41af1 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -1038,7 +1038,6 @@ FlutterEngineResult FlutterEngineSpawn( > - `FlutterEnginePrefetchDefaultFontManager` warms up the process-wide default font manager on a background thread during `FlutterLoader.initAsync` before any engine exists. > - `vm_service_uri_callback` delivers the Dart VM Service URI to `FlutterJNI.getVMServiceUri()`. > - The non-Dart `{name, class_name, library_path}` JSON cache table in [`DartCallbackCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h#L24) moves from [`//flutter/lib/ui/plugins/callback_cache.{h,cc}`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/plugins/callback_cache.h) into [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) (keeping only `LookupDartClosure` in `//flutter/lib/ui`). -> - `DartCallbackCache` lazily loads `flutter_callback_cache.json` from [`fml::paths::GetCachesDirectory()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml/paths.h#L27) on first lookup or insert.
Why these three C-APIs and the DartCallbackCache move are needed @@ -1046,7 +1045,7 @@ FlutterEngineResult FlutterEngineSpawn( - [`AndroidShellHolder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_shell_holder.cc#L166-L171) registers [`AndroidImageGenerator`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_image_generator.h#L18) via `Shell::RegisterImageDecoder` so Android's NDK `AImageDecoder` can decode single-frame HEIF and platform image formats not handled by built-in engine codecs (while multi-frame formats such as GIF, animated WebP, and APNG are decoded by the engine's built-in priority-`0` codecs). - [`FlutterMain::PrefetchDefaultFontManager`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L211-L214) calls `txt::GetDefaultFontManager()` (`//flutter/txt`) during `FlutterLoader.initAsync` before any `FlutterEngine` instance exists. - [`PlatformViewAndroidJNIImpl`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L186-L195) includes `//flutter/runtime/dart_service_isolate.h` directly to register a callback for `FlutterJNI.getVMServiceUri()`. -- [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143-L157) and [`PlatformViewAndroid::LookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L483-L486) include `//flutter/lib/ui/plugins/callback_cache.h` only to access the `{name, class_name, library_path}` JSON file map, which has no dependency on `dart:ui` or `dart_api.h` (as noted by the existing `TODO` in [`flutter_main.cc#L143`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143)). +- [`FlutterMain::Init`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143-L157) and [`PlatformViewAndroid::LookupCallbackInformation`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L483-L486) include `//flutter/lib/ui/plugins/callback_cache.h` only to access the `{name, class_name, library_path}` JSON file map used for [background Dart execution from plugins](https://flutter.dev/blog/executing-dart-in-the-background-with-flutter-plugins-and-geofencing), which has no dependency on `dart:ui` or `dart_api.h` (as noted by the existing `TODO` in [`flutter_main.cc#L143`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/flutter_main.cc#L143)).
@@ -1232,14 +1231,15 @@ typedef struct { > - [`FlutterJNI.nativeSetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc#L334-L433) populates the extended [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) struct. > - [`AndroidTouchProcessor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L358-L479) populates the extended [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447) struct. > - `embedder.cc` reads the new fields using `SAFE_ACCESS(event, member, default_value)`. -> - `PlatformViewEmbedder::GetScaledFontSize` delegates to `FlutterProjectArgs.scale_font_size_callback` when set, preserving Android 14+ (API 34+) non-linear font scaling via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544) with zero changes to [`SettingsChannel.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L176) or `PlatformDispatcher`. +> - `PlatformViewEmbedder::GetScaledFontSize` delegates to `FlutterProjectArgs.scale_font_size_callback` with `(font_size, text_scale_factor)` when set, preserving Android 14+ (API 34+) non-linear font scaling via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544). +> - Passing `text_scale_factor` directly in `FlutterScaleFontSizeInfo` decouples font scaling from [`SettingsChannel.ConfigurationQueue`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L118-L215) and the `"configurationId"` field on the `flutter/settings` platform channel.
Why FlutterWindowMetricsEvent, FlutterPointerEvent, and scale_font_size_callback are needed - [`PlatformViewAndroid::SetViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L337-L341) constructs an internal [`flutter::ViewportMetrics`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h#L16) struct containing system bar padding, gesture insets, touch slop, display corner radii (API 31+), and foldable display features (`WindowInfoTracker`), which [`FlutterWindowMetricsEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1062-L1110) previously lacked. - [`AndroidTouchProcessor.java`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java#L358-L479) populates stylus tilt/orientation, touch contact radii, `platform_data` (multi-pointer batching flags), and `embedder_id` ([`MotionEventTracker`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L730-L800) lookup ID for platform view touch forwarding), which were missing from [`FlutterPointerEvent`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L1400-L1447). -- On Android 14+ (API 34+), [`PlatformViewAndroid::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L516-L522) overrides `PlatformView::GetScaledFontSize` to evaluate `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, font_size, displayMetrics)` via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544). Adding `FlutterProjectArgs.scale_font_size_callback` wires `PlatformViewEmbedder::GetScaledFontSize` to that existing implementation without subclassing `PlatformView`. +- On Android 14+ (API 34+), [`PlatformViewAndroid::GetScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.cc#L516-L522) overrides `PlatformView::GetScaledFontSize` to evaluate `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, font_size, displayMetrics) / displayMetrics.density` via [`FlutterJNI.getScaledFontSize`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java#L1529-L1544). Because that calculation depends only on `font_size` and `text_scale_factor` ([`SettingsChannel.java#L149-L153`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java#L149-L153)), passing `(font_size, text_scale_factor)` directly in `FlutterScaleFontSizeInfo` wires `PlatformViewEmbedder::GetScaledFontSize` to `FlutterJNI.getScaledFontSize` without subclassing `PlatformView` or coupling `embedder.h` to `"configurationId"` on the `flutter/settings` platform channel.
@@ -1338,7 +1338,6 @@ typedef struct { size_t struct_size; double font_size; double text_scale_factor; - int64_t configuration_id; } FlutterScaleFontSizeInfo; /// Runs on the UI thread. Returns false on failure. @@ -1349,9 +1348,7 @@ typedef bool (*FlutterScaleFontSizeCallback)( typedef struct { // ... existing fields ... - /// Optional; if NULL, the engine falls back to linear scaling. Only invoked - /// after the embedder sets a configurationId through a `flutter/settings` - /// message. + /// Optional; if NULL, the engine falls back to linear scaling. FlutterScaleFontSizeCallback scale_font_size_callback; } FlutterProjectArgs; ``` @@ -1360,7 +1357,8 @@ typedef struct { Alternatives - Passing `const FlutterDisplayFeature* display_features` as a contiguous value array instead of `const FlutterDisplayFeature**` was rejected because indexing a contiguous array of structs uses the compile-time `sizeof(FlutterDisplayFeature)`, which would break ABI compatibility if fields are appended to `FlutterDisplayFeature` in the future. -- Serializing Android 14+ non-linear font-scale lookup tables over the `flutter/settings` platform channel (`fontScaleCurves`) and interpolating in Dart was rejected in favor of `FlutterProjectArgs.scale_font_size_callback`, which delegates directly to `FlutterJNI.getScaledFontSize` (`TypedValue.applyDimension`) with zero changes to `SettingsChannel.java` or `PlatformDispatcher`. +- Serializing Android 14+ non-linear font-scale lookup tables over the `flutter/settings` platform channel (`fontScaleCurves`) and interpolating in Dart was rejected in favor of `FlutterProjectArgs.scale_font_size_callback`, which delegates directly to `FlutterJNI.getScaledFontSize` (`TypedValue.applyDimension`). +- Passing an `int64_t configuration_id` in `FlutterScaleFontSizeInfo` and requiring a preceding `flutter/settings` message was rejected because `TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, font_size, displayMetrics) / displayMetrics.density` depends only on `(font_size, text_scale_factor)` and `embedder.h` callbacks should not depend on `flutter/settings` platform channel state (`SettingsChannel.ConfigurationQueue`).
From 86922a557d5f1b365c53e1a177711e25bc705aca Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 22 Oct 2026 17:46:23 +0000 Subject: [PATCH 41/43] Set is_root_layer no matter the backing store cache setting. Move vulkan checks to fml/common --- ...lutter-android-embedder-c-api-migration.md | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index 4a41af1..f8713ec 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -152,19 +152,19 @@ The following sections specify the [`embedder.h`](https://github.com/flutter/flu > - [`PlatformViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h#L36), [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h), and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h) move [`impeller::Context`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/context.h#L70) creation from their UI-thread constructors into `PlatformViewEmbedder::SetupImpellerContext()` on the raster thread. > - `PlatformViewEmbedder::NotifyCreated()` queues surface creation on the FIFO raster task runner instead of blocking the UI thread on a latch. > - `AndroidSurfaceControl` performs all Android Vulkan eligibility checks inside `resolve_callback` before creating a `VkDevice` to return `kVulkan` or `kOpenGL` in a single call. -> - The Android GPU blocklist in [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L379-L431) and the required Android Vulkan capability checks in [`CapabilitiesVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529) move into `AndroidSurfaceControl` alongside the Android system-property and SoC checks from [`android_context_dynamic_impeller.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L23-L119). +> - The `VkPhysicalDeviceProperties` GPU parser and known-bad-driver check in [`DriverInfoVK::IsKnownBadDriver()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L379-L431) move into [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) (so `//flutter/impeller`, `AndroidSurfaceControl`, and other embedders share a single implementation), while the required Android Vulkan capability checks in [`CapabilitiesVK`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529) move into `AndroidSurfaceControl` alongside the Android system-property and SoC checks from [`android_context_dynamic_impeller.cc`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L23-L119). > - During `FlutterEngineInitialize`, the Android embedder configures [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2266-L2322) (`FlutterProjectArgs.compositor`) with [`avoid_backing_store_cache = true`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2312) to turn off the engine's backing store cache ([`EmbedderRenderTargetCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_render_target_cache.h)) so the Android embedder is responsible for managing surfaces.
-Why shell/platform/embedder/ must change, what Impeller checks move to the Android embedder, and why backing store caches are disabled +Why shell/platform/embedder/ must change, what Impeller checks move to //flutter/common and the Android embedder, and why backing store caches are disabled [`PlatformView::SetupImpellerContext()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.h#L1040) was added in [#167765](https://github.com/flutter/flutter/pull/167765) because Vulkan driver and context initialization can take 100+ ms on Android devices, but [#167765](https://github.com/flutter/flutter/pull/167765) only wired it up in `PlatformViewAndroid`: - [`EmbedderSurfaceVulkanImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc#L76-L95) and [`EmbedderSurfaceGLImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc#L98-L125) currently construct `impeller::ContextVK` and `impeller::ContextGLES` synchronously in their constructors on the UI thread during `FlutterEngineRunInitialized`. - [`PlatformView::NotifyCreated()`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) (called by `FlutterEngineRunInitialized` right after `LaunchShell()`) posts `CreateRenderingSurface()` to the FIFO raster task runner behind `CreateImpellerContext` and blocks the UI thread on `latch.Wait()`. -- **Impeller Vulkan checks moved to the Android embedder**: Today, [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L139) checks Android system properties (`IsDeviceEmulator`, `ro.com.google.clientidbase == "android-huawei"`, MediaTek API < 32, and `kBadSocs` in `ro.product.board`) and then calls `ContextVK::Create` and `DriverInfoVK::IsKnownBadDriver()`. Because `AndroidSurfaceControl` owns `VkInstance`, `VkPhysicalDevice`, and `VkDevice` creation under `embedder.h`, the following Impeller checks are made available directly to `AndroidSurfaceControl`: - 1. **Physical device and driver blocklist ([`driver_info_vk.cc#L20-L431`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L20-L431))**: Inspecting `VkPhysicalDeviceProperties` (`vendorID`, `deviceID`, `driverVersion`, `apiVersion`, `deviceType`, `deviceName`) to block Adreno <= 650 (`GetAdrenoVersion`), Huawei Maleoon (`0x19E5`), Samsung Xclipse (`0x144D`) with Vulkan < 1.3, PowerVR series older than `kCXT` (`GetPowerVRVersion`), Pixel 10 (`0x71061212`) with driver < `6794074`, and SwiftShader CPU emulators (`IsEmulator`). - 2. **Required Vulkan extensions and physical device capabilities ([`capabilities_vk.cc#L98-L529`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529))**: Verifying `VK_KHR_surface` and `VK_KHR_android_surface`; verifying required Android device extensions (`VK_KHR_swapchain`, `VK_ANDROID_external_memory_android_hardware_buffer`, `VK_KHR_sampler_ycbcr_conversion`, `VK_KHR_external_memory`, `VK_EXT_queue_family_foreign`, `VK_KHR_dedicated_allocation`); checking `VK_FORMAT_R8G8B8A8_UNORM` color and `D32_SFLOAT_S8_UINT` / `D24_UNORM_S8_UINT` depth-stencil attachment support, `1x`/`4x` sample counts, graphics/compute/transfer queue families, and enabling `samplerYcbcrConversion` and `uniformAndStorageBuffer16BitAccess` when calling `vkCreateDevice`. +- **Impeller Vulkan checks moved to `//flutter/common` and the Android embedder**: Today, [`GetActualRenderingAPIForImpeller`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc#L79-L139) checks Android system properties (`IsDeviceEmulator`, `ro.com.google.clientidbase == "android-huawei"`, MediaTek API < 32, and `kBadSocs` in `ro.product.board`) and then calls `ContextVK::Create` and `DriverInfoVK::IsKnownBadDriver()`. Because `AndroidSurfaceControl` owns `VkInstance`, `VkPhysicalDevice`, and `VkDevice` creation under `embedder.h`, the following Impeller checks are made available directly to `AndroidSurfaceControl`: + 1. **Physical device and driver blocklist in [`//flutter/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common) (from [`driver_info_vk.cc#L20-L431`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/driver_info_vk.cc#L20-L431))**: Inspecting `VkPhysicalDeviceProperties` (`vendorID`, `deviceID`, `driverVersion`, `apiVersion`, `deviceType`, `deviceName`) to block Adreno <= 650 (`GetAdrenoVersion`), Huawei Maleoon (`0x19E5`), Samsung Xclipse (`0x144D`) with Vulkan < 1.3, PowerVR series older than `kCXT` (`GetPowerVRVersion`), Pixel 10 (`0x71061212`) with driver < `6794074`, and SwiftShader CPU emulators (`IsEmulator`). Placing this pure `VkPhysicalDeviceProperties` parser in `//flutter/common` allows `//flutter/impeller` (`workarounds_vk.cc`), `AndroidSurfaceControl`, and other embedders on Adreno/PowerVR/Mali hardware to share the same table without duplicating checks or adding a driver-query API to `embedder.h`. + 2. **Required Vulkan extensions and physical device capabilities in `AndroidSurfaceControl` ([`capabilities_vk.cc#L98-L529`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc#L98-L529))**: Verifying `VK_KHR_surface` and `VK_KHR_android_surface`; verifying required Android device extensions (`VK_KHR_swapchain`, `VK_ANDROID_external_memory_android_hardware_buffer`, `VK_KHR_sampler_ycbcr_conversion`, `VK_KHR_external_memory`, `VK_EXT_queue_family_foreign`, `VK_KHR_dedicated_allocation`); checking `VK_FORMAT_R8G8B8A8_UNORM` color and `D32_SFLOAT_S8_UINT` / `D24_UNORM_S8_UINT` depth-stencil attachment support, `1x`/`4x` sample counts, graphics/compute/transfer queue families, and enabling `samplerYcbcrConversion` and `uniformAndStorageBuffer16BitAccess` when calling `vkCreateDevice`. - **Disabling backing store caches (`avoid_backing_store_cache = true`) so the Android embedder owns surfaces**: In the legacy architecture, [`PlatformViewAndroid`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/platform_view_android.h#L43) called [`PlatformView::NotifyCreated`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L64-L85) and [`PlatformView::NotifyDestroyed`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common/platform_view.cc#L87-L89) whenever an `ANativeWindow*` attached or detached because `AndroidSurface` rendered directly into the window. Passing [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2266-L2322) with `avoid_backing_store_cache = true` during `FlutterEngineInitialize` configures the engine's internal GPU surface as context-only (`render_to_surface = false`, [#193000](https://github.com/flutter/flutter/pull/193000)) and bypasses [`EmbedderRenderTargetCache`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_render_target_cache.h). The engine requests each backing store (`FlutterBackingStoreConfig`) inside the frame that uses it ([`embedder_external_view_embedder.cc#L561-L572`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L561-L572)) and collects it as soon as `present_view_callback` returns ([`#L627-L643`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L627-L643)). When no window is attached or the OS invalidates the surface mid-frame, `create_backing_store_callback` returns `false` and the engine skips that layer ([`#L261-L265`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L261-L265)). `AndroidSurfaceControl` therefore manages `ANativeWindow*` swapchains and overlay surfaces across `surfaceCreated`, `surfaceChanged`, and `surfaceDestroyed` (and calls [`FlutterEngineNotifyLowMemoryWarning`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L3553-L3554) on `onTrimMemory` and `surfaceDestroyed` to release Impeller's transient offscreen render-target and texture caches).
@@ -265,7 +265,7 @@ sequenceDiagram Alternatives - Probing Vulkan on a Java background thread in `FlutterLoader.initAsync` and caching the result in `SharedPreferences` was rejected because it duplicates Vulkan initialization in Java/JNI before `FlutterEngineInitialize` and cached driver eligibility can become stale across system OTA updates. -- Letting `ContextVK::Create` reject a `kVulkan` renderer config and re-invoke `resolve_callback` was rejected in favor of moving the `DriverInfoVK` blocklist and `CapabilitiesVK` checks into `AndroidSurfaceControl` so `resolve_callback` resolves `kVulkan` or `kOpenGL` in a single pass. +- Letting `ContextVK::Create` reject a `kVulkan` renderer config and re-invoke `resolve_callback` was rejected in favor of moving the `DriverInfoVK` blocklist to `//flutter/common` and the `CapabilitiesVK` checks into `AndroidSurfaceControl` so `resolve_callback` resolves `kVulkan` or `kOpenGL` in a single pass. - Adding `FlutterEngineNotifyCreated` and `FlutterEngineNotifyDestroyed` C-API functions for `ANativeWindow*` lifecycle was rejected because setting `FlutterCompositor.avoid_backing_store_cache = true` and calling `FlutterEngineNotifyLowMemoryWarning` already lets `AndroidSurfaceControl` own surface creation and teardown without new `embedder.h` entry points.
@@ -315,17 +315,16 @@ typedef struct { /// The identifier for the view that the engine will use this backing store to /// render into. FlutterViewId view_id; - /// True only when `FlutterCompositor.avoid_backing_store_cache` is true and - /// this backing store is for the bottom-most layer of the frame for - /// `view_id`, with no platform view composited beneath it. The engine - /// requests at most one such backing store per view per frame, and none when - /// all Flutter contents are composited above platform views. Always false - /// when `avoid_backing_store_cache` is false or for non-root layers. Read - /// this field only if `struct_size` covers it, and treat it as false - /// otherwise. + /// True when this backing store is for the bottom-most layer of the frame + /// for `view_id`, with no platform view composited beneath it, and false + /// otherwise. The engine requests at most one such backing store per view + /// per frame, and none when all Flutter contents are composited above + /// platform views. Read this field only if `struct_size` covers it, and + /// treat it as false otherwise. /// - /// An embedder that returns the view's on-screen render target (for example, - /// the next swapchain image or default framebuffer) when this is true must + /// An embedder that returns an uncacheable on-screen render target (for + /// example, the next swapchain image or default framebuffer) when this is + /// true must set `FlutterCompositor.avoid_backing_store_cache` to `true` and /// resize that target to `size` inside `create_backing_store_callback` /// before returning it (or in `present_view_callback` on frames that have no /// root backing store). From f51658e720826053a23274465611004618e1cda0 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 22 Oct 2026 19:22:31 +0000 Subject: [PATCH 42/43] Clarify deadlock in the merge_raster_thread_on_platform_views section. --- ...lutter-android-embedder-c-api-migration.md | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index f8713ec..a31d9a4 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -284,8 +284,8 @@ sequenceDiagram
Why is_root_layer and merge_raster_thread_on_platform_views are needed -- **Root vs. overlay backing stores (`is_root_layer`)**: During `create_backing_store_callback(config, store_out)`, `config->size` equals the full window size for both the bottom-most background layer and any full-screen overlay layer above a platform view (`[Flutter_Root, PlatformView, Flutter_Overlay]`), and `present_view_callback` runs only after all layers are rendered. `is_root_layer` lets the embedder bind the bottom-most layer directly to the on-screen `ANativeWindow*` swapchain or FBO (`avoid_backing_store_cache = true`) while allocating off-screen overlay surfaces ([`SurfacePool`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h) / [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L38)) for layers above a platform view. -- **Dynamic thread merging (`merge_raster_thread_on_platform_views`)**: HCPP (`SurfaceControl`, API 34+ with Impeller Vulkan) is opt-in and `SurfaceView`-backed platform views (`VIEW_TYPES_REQUIRE_NON_TLHC`) cannot use TLHC or VD, so standard Hybrid Composition (HC) remains the default for non-TLHC views. In HC, [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L951-L1084) mutates live Android `View` instances (`FlutterMutatorView`, `FlutterImageView.convertToImageView()`) on the platform thread during frame presentation, which requires [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) to dynamically merge the raster task runner onto the platform thread. +- **Root vs. overlay backing stores (`is_root_layer`)**: During `create_backing_store_callback(config, store_out)`, `config->size` equals the full window size for both the bottom-most background layer and any full-screen overlay layer above a platform view (`[Flutter_Root, PlatformView, Flutter_Overlay]`), and `present_view_callback` runs only after all layers are rendered. `is_root_layer` lets the embedder resize and bind the bottom-most layer directly to the on-screen `ANativeWindow*` swapchain or FBO (`avoid_backing_store_cache = true`) inside `create_backing_store_callback`, while allocating off-screen overlay surfaces ([`SurfacePool`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h) / [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L38)) when `is_root_layer` is `false`. +- **Dynamic thread merging (`merge_raster_thread_on_platform_views`)**: In Hybrid Composition (HC), [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L951-L1084) mutates live Android `View` instances (`FlutterMutatorView`, `FlutterView.convertToImageView()`) on the platform thread during frame presentation. If the raster thread remained unmerged and blocked on a platform-thread latch during `present_view_callback`, `FlutterView.convertToImageView()` on the platform thread would simultaneously block waiting on the raster thread (`NotifySurfaceWindowChanged` / `waitUntilimpellerTasksFinished`), causing a deadlock. Setting `merge_raster_thread_on_platform_views = true` dynamically merges the raster task runner onto the platform thread during HC frames so both execute on the same thread without cross-thread blocking.
@@ -360,32 +360,40 @@ typedef struct { sequenceDiagram autonumber participant Raster as "Raster Thread" - participant Plat as "Platform Thread" + participant Plat as "Platform Thread (Merged)" participant Emb as "embedder : EmbedderExternalViewEmbedder" + participant SC as "surface : AndroidSurfaceControl" participant PVC as "controller : PlatformViewsController" - Note over Raster,PVC: First Frame with HC Platform View (previous_frame_view_count_ == 0) + Note over Raster,PVC: 1. Unmerged Raster Thread Detects First HC Platform View (No Latch / No Deadlock) Raster->>Emb: Preroll() + PostPrerollAction() (!IsMerged) Emb->>Emb: CancelFrame() + MergeWithLease(10 frames) - Emb-->>Raster: kSkipAndRetryFrame - Raster-)Plat: Retry frame on merged platform thread + Emb-->>Raster: kSkipAndRetryFrame (never blocks on Platform Thread) + Raster-)Plat: Retry frame on merged Platform Thread + Note over Plat,PVC: 2. First Merged Pass: Swap to FlutterImageView Inline Without Deadlock Plat->>Emb: Preroll() + PostPrerollAction() (IsMerged, previous_frame_view_count_ == 0) Emb->>Emb: ExtendLeaseTo(10) - Emb-->>Plat: kResubmitFrame (skips PaintLayerTree & create_backing_store_callback) + Emb-->>Plat: kResubmitFrame (skips PrepareBackingStore & Render) Plat->>Emb: SubmitFlutterView() -> present_view_callback(platform view layers only) - Emb->>PVC: onDisplayPlatformView() -> convertToImageView() (attaches ImageReader) + Emb->>PVC: onDisplayPlatformView() -> FlutterView.convertToImageView() + PVC->>SC: swapSurface() -> NotifySurfaceWindowChanged(FlutterImageView ANativeWindow*) + Note over Plat,SC: Runs inline on merged Platform Thread (unmerged latch would deadlock here!) PVC->>PVC: onEndFrame() keeps FlutterMutatorView View.GONE (no image queued yet) - Plat->>Plat: DrawToSurfaceUnsafe() returns kRetry -> immediate resubmit pass + Plat->>Plat: DrawToSurfaceUnsafe() sees kResubmitFrame -> immediate resubmit pass - Note over Plat,PVC: Resubmitted Pass on Merged Platform Thread (previous_frame_view_count_ > 0) + Note over Plat,PVC: 3. Resubmitted Pass: is_root_layer Sizing in create_backing_store_callback & Render Plat->>Emb: Preroll() + PostPrerollAction() -> kSuccess - Plat->>Emb: PaintLayerTree() + SubmitFlutterView() - Emb->>PVC: create_backing_store_callback (FlutterImageView root + overlay surfaces) - Emb->>PVC: present_view_callback(all layers) + onEndFrame() + Plat->>Emb: PrepareBackingStore() before Render() + Emb->>SC: create_backing_store_callback(size, is_root_layer = true) + SC->>SC: Resize on-screen FlutterImageView swapchain to config->size & acquire root image + Emb->>SC: create_backing_store_callback(size, is_root_layer = false) + SC->>PVC: Create/resize off-screen overlay surface (SurfacePool) to config->size + Emb->>Emb: Render() root layer and overlay layer into backing stores + Emb->>PVC: present_view_callback(root layer, platform view, overlay layer) + onEndFrame() PVC->>PVC: Acquire ImageReader buffer & set FlutterMutatorView View.VISIBLE - Note over Raster,PVC: Subsequent Frames After Last HC View Removed + Note over Raster,PVC: 4. Subsequent Frames After Last HC View Removed Plat->>PVC: onEndFrame() -> revertImageView() (returns to FlutterSurfaceView) loop 10 frames without platform views Plat->>Plat: Rasterizer::DoDraw() decrements lease until unmerged From ae35b4732da1640d02b202d5ff59a022bdca65ef Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 22 Oct 2026 19:35:07 +0000 Subject: [PATCH 43/43] Added boolean for HCPP to only do 1 layer --- ...lutter-android-embedder-c-api-migration.md | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/rfc/410.0000-flutter-android-embedder-c-api-migration.md b/rfc/410.0000-flutter-android-embedder-c-api-migration.md index a31d9a4..23137dd 100644 --- a/rfc/410.0000-flutter-android-embedder-c-api-migration.md +++ b/rfc/410.0000-flutter-android-embedder-c-api-migration.md @@ -277,15 +277,18 @@ sequenceDiagram > - **Embedder API additions (`embedder.h`)**: > - `FlutterBackingStoreConfig.is_root_layer` (`bool`) > - `FlutterCompositor.merge_raster_thread_on_platform_views` (`bool`) +> - `FlutterCompositor.coalesce_overlay_layers` (`bool`) > - **Behavioral changes**: > - Replaces [`AndroidExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.h#L29) and [`AndroidExternalViewEmbedder2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h#L34) with [`FlutterCompositor`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L2264-L2322), using `is_root_layer` to distinguish the bottom-most background layer from full-screen overlay layers above a platform view. > - [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.h#L30) implements `SupportsDynamicThreadMerging()` and `PostPrerollAction()` when `merge_raster_thread_on_platform_views` is `true`, dynamically merging the raster task runner onto the platform thread during Hybrid Composition (HC) frames. +> - When `coalesce_overlay_layers` is `true` (used by HCPP), [`EmbedderExternalViewEmbedder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L186-L246) collapses all non-root Flutter slices above platform views into a single top-most overlay backing store, clipping out higher-Z platform views with `DlClipOp::kDifference`.
-Why is_root_layer and merge_raster_thread_on_platform_views are needed +Why is_root_layer, merge_raster_thread_on_platform_views, and coalesce_overlay_layers are needed -- **Root vs. overlay backing stores (`is_root_layer`)**: During `create_backing_store_callback(config, store_out)`, `config->size` equals the full window size for both the bottom-most background layer and any full-screen overlay layer above a platform view (`[Flutter_Root, PlatformView, Flutter_Overlay]`), and `present_view_callback` runs only after all layers are rendered. `is_root_layer` lets the embedder resize and bind the bottom-most layer directly to the on-screen `ANativeWindow*` swapchain or FBO (`avoid_backing_store_cache = true`) inside `create_backing_store_callback`, while allocating off-screen overlay surfaces ([`SurfacePool`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h) / [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L38)) when `is_root_layer` is `false`. -- **Dynamic thread merging (`merge_raster_thread_on_platform_views`)**: In Hybrid Composition (HC), [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L951-L1084) mutates live Android `View` instances (`FlutterMutatorView`, `FlutterView.convertToImageView()`) on the platform thread during frame presentation. If the raster thread remained unmerged and blocked on a platform-thread latch during `present_view_callback`, `FlutterView.convertToImageView()` on the platform thread would simultaneously block waiting on the raster thread (`NotifySurfaceWindowChanged` / `waitUntilimpellerTasksFinished`), causing a deadlock. Setting `merge_raster_thread_on_platform_views = true` dynamically merges the raster task runner onto the platform thread during HC frames so both execute on the same thread without cross-thread blocking. +- `is_root_layer` (root vs. overlay backing stores): During `create_backing_store_callback(config, store_out)`, `config->size` equals the full window size for both the bottom-most background layer and any full-screen overlay layer above a platform view (`[Flutter_Root, PlatformView, Flutter_Overlay]`), and `present_view_callback` runs only after all layers are rendered. `is_root_layer` lets the embedder resize and bind the bottom-most layer directly to the on-screen `ANativeWindow*` swapchain or FBO (`avoid_backing_store_cache = true`) inside `create_backing_store_callback`, while allocating off-screen overlay surfaces ([`SurfacePool`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/surface_pool.h) / [`FlutterImageView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java#L38)) when `is_root_layer` is `false`. +- `merge_raster_thread_on_platform_views` (dynamic thread merging for HC): In Hybrid Composition (HC), [`PlatformViewsController`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L951-L1084) mutates live Android `View` instances (`FlutterMutatorView`, `FlutterView.convertToImageView()`) on the platform thread during frame presentation. If the raster thread remained unmerged and blocked on a platform-thread latch during `present_view_callback`, `FlutterView.convertToImageView()` on the platform thread would simultaneously block waiting on the raster thread (`NotifySurfaceWindowChanged` / `waitUntilimpellerTasksFinished`), causing a deadlock. Setting `merge_raster_thread_on_platform_views = true` dynamically merges the raster task runner onto the platform thread during HC frames so both execute on the same thread without cross-thread blocking. +- `coalesce_overlay_layers` (single overlay surface coalescing for HCPP): In HCPP, [`PlatformViewsController2`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java#L835-L843) places all platform views inside the Activity's `ViewRootImpl` surface (at Z = 0) and maintains a single persistent [`overlayerSurface`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java#L838) (`SurfaceControl` at Z = 1000) above them. By default, [`EmbedderExternalViewEmbedder::LayerBuilder`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc#L186-L246) allocates a separate overlay `FlutterBackingStore` (and clears it on the first slice) whenever a higher-Z platform view overlaps an earlier Flutter overlay slice (`[Flutter_Root, PV_1, Flutter_Mid, PV_2, Flutter_Top]`). Setting `coalesce_overlay_layers = true` instructs `EmbedderExternalViewEmbedder` to merge all non-root Flutter slices into one top-most overlay backing store and clip out the bounding rects of higher-Z platform views using `DlClipOp::kDifference` (matching [`AndroidExternalViewEmbedder2::SubmitFlutterView`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc#L168-L181)).
@@ -295,7 +298,7 @@ sequenceDiagram ```mermaid flowchart TD Req["Platform View Request in PlatformViewsController"] --> IsHCPP{"enable_surface_control AND API 34+ AND Impeller Vulkan?"} - IsHCPP -- "Yes" --> HCPP["HCPP: configureForHybridComposition++ (FlutterCompositor, unmerged threads)"] + IsHCPP -- "Yes" --> HCPP["HCPP: configureForHybridComposition++ (FlutterCompositor, coalesce_overlay_layers = true, unmerged threads)"] IsHCPP -- "No" --> CreationPath{"Plugin Creation Method"} CreationPath -- "initExpensiveAndroidView (createForPlatformViewLayer)" --> HC["HC: configureForHybridComposition (FlutterCompositor, merge_raster_thread_on_platform_views = true)"] CreationPath -- "initAndroidView (createForTextureLayer)" --> HasSurfaceView{"View hierarchy contains SurfaceView (VIEW_TYPES_REQUIRE_NON_TLHC)?"} @@ -332,9 +335,9 @@ typedef struct { } FlutterBackingStoreConfig; ``` -#### Opt-In Dynamic Raster Thread Merging for Hybrid Composition +#### Opt-In Dynamic Raster Thread Merging and Overlay Layer Coalescing -The Android embedder sets `merge_raster_thread_on_platform_views = true` when HCPP (`enable_surface_control`) is not active, and `false` when HCPP is active: +The Android embedder sets `merge_raster_thread_on_platform_views = true` and `coalesce_overlay_layers = false` when HCPP (`enable_surface_control`) is not active, and sets `merge_raster_thread_on_platform_views = false` and `coalesce_overlay_layers = true` when HCPP is active: ```c typedef struct { @@ -353,6 +356,15 @@ typedef struct { /// `render_task_runner` must be NULL so the engine binds `fml::MessageLoop` /// to the calling platform thread and creates the raster thread). bool merge_raster_thread_on_platform_views; + /// When true, the engine coalesces all non-root Flutter slices composited + /// above platform views into a single top-most overlay `FlutterBackingStore` + /// per view per frame, clipping out the bounding rectangles of any higher-Z + /// platform views using `DlClipOp::kDifference`. + /// + /// When false, the engine creates a separate overlay `FlutterBackingStore` + /// whenever a higher-Z platform view overlaps an earlier Flutter overlay + /// slice. + bool coalesce_overlay_layers; } FlutterCompositor; ``` @@ -400,7 +412,7 @@ sequenceDiagram end ``` -When HCPP (`enable_surface_control` on API 34+ with Impeller Vulkan) is active, `merge_raster_thread_on_platform_views` is `false` and `AndroidSurfaceControl` exports an acquire fence on its `VkQueue` inside `present_view_callback`: +When HCPP (`enable_surface_control` on API 34+ with Impeller Vulkan) is active, `merge_raster_thread_on_platform_views` is `false`, `coalesce_overlay_layers` is `true`, and `AndroidSurfaceControl` exports an acquire fence on its `VkQueue` inside `present_view_callback`: ```mermaid sequenceDiagram @@ -1449,7 +1461,7 @@ sequenceDiagram | :--- | :--- | :--- | :--- | | **`FlutterEmbedderNative`, `AndroidVMInit`, and `AndroidEngineGroup`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/runtime`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/runtime), [`//flutter/lib/ui`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui), [`//flutter/txt`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/txt) | - [`FlutterEngineSpawn`](#multi-engine-spawning-and-add-to-app) (`FlutterEngineSpawnInfo`)
- [`FlutterProjectArgs.enable_direct_platform_message_dispatch`](#platform-channel-messaging-background-handlers-and-task-queues)
- [`FlutterProjectArgs.request_dart_deferred_library_callback`](#dart-deferred-library-loading), `FlutterEngineLoadDartDeferredLibrary`, `FlutterEngineNotifyDartDeferredLibraryLoadError`
- [`FlutterProjectArgs.image_decoders`](#image-decoders--font-prefetching--vm-service-uri) (`FlutterImageDecoderRegistration`), `FlutterEnginePrefetchDefaultFontManager`, `FlutterProjectArgs.vm_service_uri_callback` | Owns the opaque [`FlutterEngine`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/embedder.h#L389) handle and `FlutterEngineProcTable`; replaces `AndroidShellHolder` and `PlatformViewAndroid`. | | **`AndroidSurfaceControl`** | [`//flutter/shell/common`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/common), [`//flutter/impeller`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/impeller), [`//flutter/vulkan`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/vulkan) | - [`kDeferred`](#initialization) (`FlutterDeferredRendererConfig`, `FlutterRendererConfigResolveCallback`) | Keys native surfaces (`ANativeWindow*`, `ASurfaceControl*`) by `FlutterViewId` and resolves the renderer asynchronously during startup. | -| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | - [`FlutterCompositor.merge_raster_thread_on_platform_views`](#platform-views) and `FlutterBackingStoreConfig.is_root_layer`
- [`FlutterCompositor.platform_view_mutation_capabilities`](#platform-view-mutator-stack-serialization), `FlutterPlatformView.mutations`, `FlutterPlatformViewMutation`, `FlutterPath` | Unifies HC, HCPP (`SurfaceControl`), TLHC, and VD behind `FlutterCompositor` and deletes `external_view_embedder/`. | +| **`AndroidPlatformViewsController` and `AndroidMutatorsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/fml`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/fml) | - [`FlutterCompositor.merge_raster_thread_on_platform_views`](#platform-views), `FlutterCompositor.coalesce_overlay_layers`, and `FlutterBackingStoreConfig.is_root_layer`
- [`FlutterCompositor.platform_view_mutation_capabilities`](#platform-view-mutator-stack-serialization), `FlutterPlatformView.mutations`, `FlutterPlatformViewMutation`, `FlutterPath` | Unifies HC, HCPP (`SurfaceControl`), TLHC, and VD behind `FlutterCompositor` and deletes `external_view_embedder/`. | | **`AndroidHardwareBuffer` and `AndroidVulkanExternalTexture`** | [`//flutter/common/graphics`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/common/graphics) | - [`FlutterVulkanExternalTexture`](#external-textures) (`ycbcr_conversion_info`), `FlutterVulkanYcbcrConversionInfo`, `FlutterVulkanTextureFrameCallback`
- [`FlutterOpenGLExternalTexture`](#external-textures) (`has_uv_transform`, `uv_transform`), `FlutterOpenGLTextureFrameCallback` | Imports zero-copy `AHardwareBuffer` (`VkImage` / `EGLImageKHR`) and `SurfaceTexture` UV transforms without subclassing `flutter::Texture`. | | **`AndroidSemanticsMapper` and `AndroidWindowMetricsMapper`** | [`//flutter/flow`](https://github.com/flutter/flutter/tree/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/flow), [`//flutter/lib/ui/window/viewport_metrics.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/lib/ui/window/viewport_metrics.h) | - [`FlutterSemanticsNode2`](#accessibility-and-semantics-parity) (`role`, `hit_test_child_count`, `FlutterSemanticsRole`)
- [`FlutterWindowMetricsEvent`](#viewport-metrics-pointer-events-and-non-linear-font-scaling), `FlutterPointerEvent`, `FlutterProjectArgs.scale_font_size_callback` | Serializes semantics trees for Java `AccessibilityBridge`, packs insets/cutouts/foldable features and pointer data, and delegates non-linear font scaling. | | **[`APKAssetProvider`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/android/apk_asset_provider.h#L26)** | [`//flutter/assets/asset_resolver.h`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/assets/asset_resolver.h) | - [`FlutterProjectArgs.custom_asset_resolvers`](#custom-asset-and-kernel-resolution), `FlutterCustomAssetResolver`, `FlutterAssetMapping` | Maps APK and split-AAB assets via NDK `AAssetManager` without subclassing `flutter::AssetResolver`. | @@ -1475,7 +1487,7 @@ flowchart LR Implements all C-API extensions and `shell/platform/embedder/` engine changes, validated by [`embedder_unittests`](https://github.com/flutter/flutter/blob/4762dec4fc9072cd9269814ef36364f35a9a66dc/engine/src/flutter/shell/platform/embedder/BUILD.gn#L372-L390) with zero modifications to `shell/platform/android/`: - `kDeferred` renderer resolution (`PlatformViewEmbedder::SetupImpellerContext()`) and non-blocking surface initialization (`PlatformViewEmbedder::NotifyCreated()` -> `Shell::OnPlatformViewCreatedAsync()`). -- Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) in `EmbedderExternalViewEmbedder` and Impeller Vulkan backing store presentation ([#187525](https://github.com/flutter/flutter/issues/187525)). +- Opt-in dynamic raster thread merging (`FlutterCompositor.merge_raster_thread_on_platform_views`) and overlay layer coalescing (`FlutterCompositor.coalesce_overlay_layers`) in `EmbedderExternalViewEmbedder`, plus Impeller Vulkan backing store presentation ([#187525](https://github.com/flutter/flutter/issues/187525)). - Platform view mutator serialization (`FlutterPlatformViewMutation`), external texture callbacks (`FlutterVulkanTextureFrameCallback`, `FlutterOpenGLTextureFrameCallback`), `FlutterSemanticsNode2`, `FlutterWindowMetricsEvent`, and `FlutterPointerEvent` in-place extensions, `scale_font_size_callback`, deferred library callbacks, `FlutterCustomAssetResolver`, `FlutterEngineSpawn`, direct UI-thread platform message dispatch, and process-scoped utilities. ### Phase 2: Android Shell Modularization and GN Target Partitioning