2024年は8月くらいまではずっとFlutter書いてました。その中で、AndroidのViewをFlutterのUIに組み込むPlatform Viewを使ったのですが、これどうやって画面に描画されてるのだろう?という疑問がありました。
Platform Viewについて調べると、自分の前提知識が足りないせいで「なんか分かった気になってるだけな気がするんだよなぁ」という気持ちになってました。
というわけで、今回はFlutterのPlatformViewを使うと何が起きてるのかを調べてみた日記です。(Andoirdだけです、iOSには触れません)
前回の日記(AndroidでOpenGL ES - bati11 の 日記)の内容は、今回につながっています。
Platform Viewを使ってない場合
まずは、Platform View関係なくFlutterアプリがどういう仕組みになっているか。
この日記で特に書くことはなく、Flutterの公式ページを読む。
以下のような図がある。
レイアウトして描画するのはEngineの仕事。上記の公式ページの「Rendering and layout」に書いてある。詳しくは追加で以下のスライドを読む、講演を聴く。とても勉強になりました。
プラットフォーム固有の描画先を用意するのはEmbbederの仕事。上記の公式ページの「Platform embedding」に書いてある。
Androidの場合について以下のように書いてある。FlutterViewが重要。
On Android, Flutter is, by default, loaded into the embedder as an Activity. The view is controlled by a FlutterView, which renders Flutter content either as a view or a texture, depending on the composition and z-ordering requirements of the Flutter content.
Flutterのバージョン3.24.5時点でのFlutterViewの実装では、FlutterViewはFrameLayoutを継承していて、FlutterSurfaceViewというSurfaceViewを継承したviewをFlutterView自身にaddView()している。
FlutterのWidgetツリー、というよりRenderObjectツリーは、Engineによってレイアウトされ、FlutterSurfaceViewのSurfaceに対して描画されるのだろう。

Platform Viewを使ってる場合
WidgetツリーにAndroidViewがある場合、FlutterのWidgetとは別世界の、AndroidのViewツリーについてもアプリのUIとして描画されるわけだけど、これはどういう仕組みになっているのかな。
flutter/docs/platforms/android/Android-Platform-Views.md at 3.24.5 · flutter/flutter · GitHub
AndroidにおいてPlatform Viewには3つのモードがある。
- VD (Virtual Display)
- HC (Hybrid Composition)
- TLHC (Texture Layer Hybrid Composition)
実際のソースコードを見ていくことにする。Flutterのバージョンは3.24.5です。
Flutterアプリに組み込みたいAndroidのViewは、io.flutter.plugin.platform.PlatformViewというインタフェースを実装する必要がある。FlutterのWidgetツリーにはAndroidViewを書く(TLHCの場合)。
ここら辺の記述の仕方は公式ページの通り実装すればいい。
AndroidViewの実装を読むとMethodChannelでJavaのコードが実行されることが分かる。ここで利用するMethodChannelはSystemChannelsというクラスがあって SystemChannels.platform_views で用意されてる。Platform ViewだけでなくFlutterフレームワークが他の用途でJavaとやりとりするためのMethodChannelがいくつか用意されてるのが分かる。今回は 'flutter/platform_views' に注目。
static const MethodChannel platform_views = MethodChannel( 'flutter/platform_views', );
_AndroidViewControllerInternals#sendCreateMessage()で MethodChannelでcreateをコールしている。
return SystemChannels.platform_views.invokeMethod<dynamic>('create', args);
ここから先はJavaで書かれたコードの話。
MethodChannelのhandlerを登録しているのはPlatformViewsChannel.javaのここ↓
public PlatformViewsChannel(@NonNull DartExecutor dartExecutor) { channel = new MethodChannel(dartExecutor, "flutter/platform_views", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingHandler); }
そして、上記の parsingHandler の create コールバックが実行され、 PlatformViewsHandler#createForTextureLayer() が呼ばれる。このメソッドが重要そう。
PlatformViewsHandler#createForTextureLayer() に分岐がある。ドキュメント等を読んでいる感じ、AndroidのPlatform Viewは VD→TC→TLHC と改善されてきている模様。ソースコードでもTLHCが使えるなら使う、使えない場合はVDもしくはHCにフォールバックするようになってる。
// Fall back to Hybrid Composition or Virtual Display when necessary, depending on which // fallback mode is requested. if (!supportsTextureLayerMode) { if (request.displayMode == PlatformViewsChannel.PlatformViewCreationRequest.RequestedDisplayMode .TEXTURE_WITH_HYBRID_FALLBACK) { configureForHybridComposition(platformView, request); return PlatformViewsChannel.PlatformViewsHandler.NON_TEXTURE_FALLBACK; } else if (!usesSoftwareRendering) { // Virtual Display doesn't support software mode. return configureForVirtualDisplay(platformView, request); } // TODO(stuartmorgan): Consider throwing a specific exception here as a breaking change. // For now, preserve the 3.0 behavior of falling through to Texture Layer mode even // though it won't work correctly. } return configureForTextureLayerComposition(platformView, request);
3つのモードがあると言いつつ、公式のPlatformViewのページを見ると、HCとTLHCの説明しか書いてない。VDの弱点はTLHCで補うことができるからVDはもうほとんど場合で不要、ということかな。
推奨されるTLHCを見ていく。
TLHC (Texture Layer Hybrid Composition)
さっきのフォールバックの分岐処理の部分で、フォールバックせずに、TLHCが選ばれたら configureForTextureLayerComposition()を呼ぶ。そこから先はざっと以下のような流れ。
- まず、
PlatformViewRenderTargetオブジェクトをmakePlatformViewRenderTarget()で生成する- Android APIレベル29 (Android 10) 以上であれば FlutterRenderer#createSurfaceProducer()で
SurfaceProducerPlatformViewRenderTargetが生成される- Androidフレームワークの android.media.ImageReaderを生成していて、
ImageReaderがSurfaceを保持している
- Androidフレームワークの android.media.ImageReaderを生成していて、
- そうでなければ FlutterRenderer#createSurfaceTexture()で
SurfaceTexturePlatformViewRenderTargetが生成される。- Androidフレームワークの android.graphics.SurfaceTextureを生成していて、
SurfaceTextureがSurfaceを保持している
- Androidフレームワークの android.graphics.SurfaceTextureを生成していて、
- Android APIレベル29 (Android 10) 以上であれば FlutterRenderer#createSurfaceProducer()で
- 次に、PlatformViewWrapperを生成する。このとき、コンストラクタの引数に上で生成した
PlatformViewRenderTargetを指定するPlatformViewWrapperはAndroidのFrameLayoutのサブクラスである
PlatformViewWrapperのサイズや配置場所を決める- Platform Viewとして埋め込みたい自分で作ったAndroidのView(PlatformViewを継承して実装する)を
PlatformViewWrapperの子ツリーにするfinal View embeddedView = platformView.getView(); ... viewWrapper.addView(embeddedView);
FlutterViewにPlatformViewWapperを追加する
ここまででAndroidアプリのViewツリーは以下のように、FlutterViewの子にPlatformViewWrapperが、PlatformViewWrapperの子に自分で作ったAndroidのView(Flutterアプリに組み込みたいAndroidのViewツリー)がぶら下がる。

さて、FlutterとしてのWidgetツリーの方ではAndroidViewの部分に Texture が差し込まれる。
前回の記事を思い返すと、AndroidのSurfaceにはプロデューサーとコンシューマーがいると書いた。Java側で生成したPlatformViewRenderTargetが保持するSurfaceがプロデューサーとなりバッファに描画をして、Flutter側のTextureがコンシューマーとなりバッファを読み取る、という形で連携する。
AndroidアプリのViewツリーの描画先はアプリのSurfaceViewになるはずだが、どうやって描画先をPlatformViewRenderTargetが保持するSurfaceにしているのか。
PlatformViewWrapperの実装を見ると分かる。Androidアプリでは、Viewツリーのルートから順に各Viewのdraw(canvas)を辿り、各Viewが引数のcanvas|に対して自分自身を描画することで、結果的にアプリ全体を描画する。PlatformViewWrapperはdraw(canvas)をoverrideして引数のcanvasではなく、自分自身が保持するSurfaceからcanvas`を取得して、そこに描画するように差し替える。
final Canvas targetCanvas = targetSurface.lockHardwareCanvas(); ... try { // Fill the render target with transparent pixels. This is needed for platform views that // expect a transparent background. targetCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // Override the canvas that this subtree of views will use to draw. super.draw(targetCanvas); } finally { renderTarget.scheduleFrame(); targetSurface.unlockCanvasAndPost(targetCanvas); }
このようにPlatformViewWrapperはAndroidアプリとしてのcanvasではなく、自分自身が保持するcanvas、つまりSurfaceに描画する。このSurfaceのコンシューマーがFlutterのTexutureということ。

Androidアプリとしては、PlatformViewWrapper部分のViewツリーについて、アプリのSurfaceに描画はしないがViewツリーとしては保持する、という状態になる。AndroidアプリはViewツリーとしては保持しているため、VDにおけるテキスト入力やアクセシビリティの問題を回避できる。なるほどなー。
VD (Virtual Display)
flutter/docs/platforms/android/Virtual-Display.md at 3.24.5 · flutter/flutter · GitHub
TLHCが使えない場合のフォールバックとしてVDを使う場合もPlatformViewRenderTargetオブジェクトをmakePlatformViewRenderTarget()で生成するのは同じ。
PlatformViewRenderTargetが保持するSurfaceを、Androidフレームワークの DisplayManager#createVirtualDisplay() の引数に指定して android.hardware.display.VirtualDisplay を手にいれる。PlatformViewをVirtualDisplayに対して描画すると、SurfaceのコンシューマーであるFlutter WidgetツリーのTextureで利用される。
描画はTLHCと同じような流れだけど、AndroidアプリとしてのViewツリーにPlatformViewが含まれないことが大きな違い。これがVDの問題の原因となっている。
HC (Hybrid Composition)
flutter/docs/platforms/Hybrid-Composition.md at 3.24.5 · flutter/flutter · GitHub
HCはスタミナ切れでコード読んでない。ドキュメント読んでる感じだとTLHCやVDと全然違うみたい。公式PlatformViewのページの文章にはこう書いてある。
Platform Views are rendered as they are normally. Flutter content is rendered into a texture. SurfaceFlinger composes the Flutter content and the platform views.
Android platform-views | Flutter
SurfaceFlingerによって合成されると書いてある。Androidアプリとしては複数のSurfaceを持っている状態でそれぞれ描画されて、最終的にAndroidシステムサービスのSurfaceFlingerによって合成される。
HC個別のmarkdownより、Platform全般のmarkdownの方が説明がわかりやすい。
flutter/docs/platforms/android/Android-Platform-Views.md at 3.24.5 · flutter/flutter · GitHub
The Flutter widget tree is divided into two different Android Views, one below the platform view and one above it.
FlutterのWidgetが描画されるSurfaceもPlatformViewの下と上に分割されると。これらがSurfaceFlingerで合成されるのだろう。PlatformViewなしの場合とだいぶ違うし、パフォーマンスへの影響が大きい可能性があると。
以下のスライド見るとイメージしやすいです。
Androidで不安定なPlatform Viewsとの闘い - Speaker Deck
おしまい
最初よりはだいぶ分かった気がするぞー。