本文へ移動
rdlabo.devdocs

キャプチャとプラットフォームの制約

実際のネイティブサーフェスに合わせて、キャプチャとフォールバックを設定します。まず投影の契約と設定ガイドを確認してください。このリファレンスはプラットフォームの動作と制約を説明するもので、追加のセットアップ手順ではありません。

フィード内の不透明なカードと、半透明のプレーヤーコントロールでは、キャプチャの選び方が異なります。カードだけを描画すれば隣のコンテンツが写り込むのを避けられますが、半透明コントロールだけを描画すると、合成に必要な背景が失われます。以下の例に応じてキャプチャの動作を選び、キャプチャできないコンテンツにはWebフォールバックを維持します。これらの選択が維持するのは取得可能なピクセルであり、ライブ動画やプラットフォームのコンポジターのすべての効果ではありません。

Androidのキャプチャモード

不透明でソフトウェア描画可能なAndroid SDKビューには、captureMode = NativeUIProjection.CaptureMode.VIEWと明示的なfailedFallback方針(利用側がネイティブ配置を維持しない限りWEB)を使います。周囲の余白を含めずViewのピクセルだけを描画するため、一部だけ見えている広告をクリップしても、ツールバーや隣のコンテンツが画像に混ざりません。動画、SurfaceView、半透明ビュー、Windowでの合成が必要なコンテンツは、デフォルトのCOMPOSITEDを維持します。利用側は不透明なView全体に一致し、クリップされたDOMホストを提供する必要があります。任意のネイティブの輪郭マスク、transform、独自のクリッピングは再現されません。

VIEWのピクセルは、ネイティブViewの識別情報、寸法、コンテンツのリビジョンでキャッシュします。通常のスクロールでは、デコード済みWeb画像を維持して配置だけを更新し、キャプチャ、PNGエンコード、画像デコードは行いません。画像は、ネイティブ配置の確認応答の遅延とは独立して、ローカル原点(0, 0)からホストのborder boxを覆います。不透明な画像はスクロールとスクロールの間も、ライブのネイティブViewの下に描画されたままです。Viewを隠す前に、EngineはWebViewの表示準備完了と描画を待ちます。VIEWは移行時・復帰時のネイティブ画像の複製を作りません。復帰の確認応答はネイティブ描画と次のアニメーションフレームをまたいで待機します。世代の確認により、遅れたcoverコールバックが復元や中断の後にコンテンツを隠すことを防ぎます。

VIEWモードではPNGエンコード後にネイティブBitmapを解放し、PNGとデコード済みWeb画像はキャッシュに残します。プラグインの更新通知でコンテンツのリビジョンが進みます。Viewを置き換えずに内容が変わる場合は、必ず更新を通知してください。登録の置換、WebViewナビゲーション、reset、closeでキャッシュを破棄します。エンコードはUIスレッド外で行い、Viewの描画とキャッシュの所有権はUIスレッドに残します。既存の800万ピクセルの上限も維持します。

COMPOSITEDは、半透明コンテンツのためにネイティブのハンドオフ複製を維持します。移行時の複製はWebViewの表示準備完了と描画を待ってから破棄します。移行時の500 msの期限が切れた場合、複製を除去する前にネイティブViewを復元し、遅れた描画確認応答を拒否します。

Androidライブラリ

AndroidはGradleのAndroidライブラリを使い、Capacitorプラグインとして登録するのではなく、Mavenを通じてAARとして配布します。公開座標はio.github.webview-native-ui:engine:<version>です。ローカルのMaven公開物をビルドして検証するには、次を実行します。

android/gradlew -p android testDebugUnitTest assembleRelease connectedDebugAndroidTest publishReleasePublicationToVerificationRepository

NativeUIClippedViewは、変わっていない寸法をリセットせず、呼び出し元が指定した位置とクリッピングを適用する低レベルヘルパーです。NativeUIProjectionは画像のハンドオフと復元を管理し、このヘルパーでレンダラーのレイアウトを行うことはありません。通常のネイティブ配置とサイズ更新は利用側のプラグインが所有します。AndroidはWKWebViewのようなネイティブのoverflowバインディングには対応していません。高レベルの登録は、イベントを消費しないウィンドウのタッチ監視を共有します。downは進行中のキャプチャの採用を取り消し、up/cancel/フォーカス喪失で再び準備を許可し、終了処理はその登録だけを解除します。既存のコールバック配信と戻り値は維持します。

NativeUISnapshotsは、合成済みのPixelCopy画像とローカルPNGを準備し、そのキャッシュから被覆・復元します。エンコードはUIスレッド外で行い、所有者ごとに一度に1つだけ準備します。外観変更と操作を通知し、終了処理時にトークンを解放してください。ビューとライフサイクルの呼び出しはすべてUIスレッドで行います。リビジョン、適格性、フォールバック方針はプラグインが保持します。

準備済みのWebピクセルを表示または退場させた後、NativeUIWebReadiness.prepareは、後続のWebView描画の準備ができているか確認します。current述語は、所有するセッション、ソースの世代、画像のリビジョンを検証しなければなりません。一時的なネイティブ複製が、この非同期ハンドオフを覆います。オーバーレイの開始自体は待機しません。ブリッジの確認応答も、このコールバックも、物理ディスプレイへの表示を証明するものではありません。

不透明な画像は、ハンドオフ間も不透明なネイティブビューの下に残せますが、その方針を半透明コントロールに適用してはいけません。自身のキャプチャ画像の上に半透明ビューを描画すると、内容が二重に合成されるためです。実際のビューと背景に合わせてキャプチャ方式を選び、組み込み全体を検証してください。投影画像を通してクリックを合成してはいけません。

Androidの半透明サーフェス

空のビットマップへ半透明ビューを描画するのではなく、背景も含めた合成済みウィンドウのピクセルをキャプチャします。準備済みWeb画像はデコードしておき、非表示にします。ハンドオフ時は、cover(token, window, reference, views)で一時的な不透明の画像複製を配置してから、同じUI処理の中でネイティブビューを隠します。Web画像を表示し、Web描画の準備完了を確認して、移行時の複製を破棄します。オーバーレイは直ちに開始し、この経路で画像をエンコードすることはありません。

復帰時はrestoreで準備済みの復帰用複製を配置し、その下にネイティブビューを復元し、Web画像を隠して、Web描画の準備完了後に復帰用複製を破棄します。これにより、ライブの半透明ネイティブピクセルを、そのスナップショットの上に二重合成することを避けます。すべての非同期コールバックでトークンと現在のサーフェスの世代を検証してください。一時的な複製は操作できず、範囲が限定されます。終了処理で両方の複製とキャッシュリソースを解放します。

維持されるのはキャプチャ時の背景であり、画像の背後で変化し続けるシーンではありません。背景の変更もコンテンツ更新と同じ外観変更通知でキャッシュを無効化する必要があります。動画や未対応のキャプチャサーフェスには、利用側のフォールバック方針を維持します。

利用側の結合テストは、それぞれのリポジトリに置きます。EngineのCIは、隣接するレンダラーのチェックアウトを要求せずに、ブラウザ、Swift、Androidの契約を検証します。

iOSの画像の色精度

準備するウィンドウスナップショットは、キャプチャ時に拡張Display P3のFloat16ビットマップを使い、バックグラウンドキューで16-bit Display P3 PNGにエンコードします。Web投影はCanvasやWebGPUで変換せず、準備済み画像を直接表示します。ネイティブの移行時・復帰時の複製もデコード済みPNGを使うため、色変換後は両側で同じピクセルを共有します。エンコードとデコードはオーバーレイのハンドオフの外で行い、利用側に新しい設定や依存は不要です。

広色域コンテンツの色の損失を抑えますが、Liquid Glassの外観が完全に一致することは保証しません。ネイティブの合成や整数PNGへの変換によって、ピクセルが変わる可能性があります。精度を高めると、キャプチャ時のメモリ使用量と画像サイズも増えます。準備が失敗した場合は既存のフォールバック方針を適用します。Androidはプラットフォーム固有のキャプチャ経路を維持します。

Liquid Glassのキャプチャ差異

キャプチャしたLiquid GlassコントロールをWebViewに投影すると、ライブのネイティブコントロールと異なって見える場合があります。反射する縁のハイライトや、背景の屈折・ぼかしは、キャプチャ経路で完全には維持されません。色精度を高めるだけでは、この差は解消しません。

iPhoneとiPadでキャプチャ画像とライブのUIKitタブバーを比較した実機スクリーンショット

左はiPhone 15 Pro Max、iOS 27.2、右はiPad mini(第6世代)、iPadOS 27.2です。各実機のスクリーンショットでは、上にWKWebView内のキャプチャ済み16-bit Display P3 PNG、下にライブのUIKitタブバーを、その実機内で同じサイズで表示しています。スクリーンショットは拡縮せず、共通のカラープロファイルも変えずに横に並べています。シミュレーターのキャプチャではなく、実機の結果です。

システムのスクリーンショットはライブの外観を維持しますが、アプリケーションレベルのキャプチャでは完全には再現されません。検証したiPhoneでは、PNGエンコードやWebView表示の前に、エンコード前のキャプチャ画像をネイティブのUIImageViewへ表示しても差がありました。UIWindow全体をウィンドウ全体のビットマップへ描画してから切り抜いても、Engineの切り抜きビットマップに比べて結果は改善しませんでした。

システムのスクリーンショットはこの比較の証拠であり、Engineの代替キャプチャバックエンドではありません。これらは実機でDeveloper Modeを有効にし、開発者向けツールで取得したものです。ReplayKitなどのアプリの画面キャプチャAPIにはユーザーの同意が必要で、手動のスクリーンショットにもユーザー操作が必要です。システムのスクリーンショットを切り抜けば取得したピクセルは維持できますが、取得の要件はなくなりません。そのため、Engineは自動UIハンドオフにこの方式を採用しません。通常の投影は、Developer Mode、スクリーンショット操作、画面収録の許可フローに依存すべきではないからです。AppleのReplayKitセキュリティ文書を参照してください。

この制約はAppleへ報告し、画面収録セッションを開始せずに完全な外観を取得できる、正式にサポートされた方法を要望しています。Apple Developer Forumsの議論を参照してください。Appleは必要な背景を含む祖先をキャプチャすることを提案し、上のウィンドウ全体の比較でその方法を検証しています。これは確認されたキャプチャの制約であり、修正の確認やAppleによる約束ではありません。プラグインが対応する背景で、実機の半透明コントロールを検証してください。

一時ストレージとアプリケーションの権限

準備済み画像は、アプリ専用のプライベートな一時ストレージに保存します。iOSはFileManager.default.temporaryDirectoryを使います。Androidではcontext.cacheDir内のディレクトリでNativeUISnapshotsを作成し、共有ストレージや外部ストレージは使いません。この経路では、どちらのプラットフォームも写真ライブラリやストレージへの許可プロンプトを必要としません。Capacitor Filesystemプラグインも不要です。AppleのサンドボックスディレクトリとAndroidのアプリ専用ストレージを参照してください。

Capacitor.convertFileSrcは、返されたファイルURLを既存のWebViewローカルサーバーへ対応付けます。ファイルシステムの権限を与えたり、画像をアップロードしたりはしません。アプリがContent Security Policyを設定する場合、img-srcでそのURLが使うローカルアプリのオリジンを許可する必要があります。広範なfile:アクセスや、無関係なWebViewのファイルアクセス設定を有効にしないでください。

release(token)と所有者の終了処理は、所有するスナップショットファイルを削除します。プロセスが突然終了すると、OSによるキャッシュ消去やアプリのアンインストールまで一時画像が残る場合があり、クラッシュ直後の削除は保証されません。このストレージは非公開に保ち、永続的なアプリデータではなく破棄可能なものとして扱ってください。